[Fontconfig] How can a font be blacklisted for some scripts

Jay Hobson Jay.Hobson at Sun.COM
Mon Jun 12 11:24:32 PDT 2006


Nicolas,

I think that I have a solution that will work for you. As part of a 
seperate problem that I was trying to solve, I came up with restricting 
some languages from particular fonts. This essentially blacklists 
particular fonts for showing up with particular languages. It works on 
the pretense that if the font does not include the language that the 
system is asking for then it will be excluded from being part of the 
possible candidates. To do this, before doing the actual compare in 
fontconfig, I remove the restricted languages from the list of languages 
the font claims to support. After the comparison, the language list is 
returned to it's original state.

The modified language list only happens for font sets such as Serif, 
Sans and Monospace. If a user explicitly asks for a particular font, 
then no restriction takes place.

I have included the diffs file for fontconfig 2.3.2, and an example of 
the entries needed in fonts.conf. Since it is possible for a font to 
appear in more than one alias, such as Chinese, Japanese and Korean 
fonts, etc, it may be necessary to distinguish which alias the 
restriction is for, such as restricting Sans Serif English characters in 
a Chinese font from the Serif alias.

<alias>
        <family>Serif</family>
        <restrict>
                <family>Lucida Bright</family>
                <lang>en</lang>
                <lang>de</lang>
        </restrict>
</alias>
<alias>
        <family>Monospace</family>
        <restrict>
                <family>Arial</family>
                <lang>en</lang>
        </restrict>
</alias>

Hope this solves your problem. Let me know. If it does, then I'll work 
with you to get it into the open-source fontconfig.

Jay Hobson



Nicolas Mailhot wrote:

>Hi,
>
>I'm running in the following problem. I am the Fedora Extras packager of
>DejaVu, a FOSS family of typefaces with monthly releases and fast pace
>of change.
>
>Due to its FOSS nature and release early, release often process at one
>point of time the fonts may not be suitable for intensive use with some
>scripts. Moreover when unicode blocks are shared between scripts which
>have different conventions on how the glyphs may be drawn, only one
>script can be accommodated right now (even if both variants of the
>glyphs are present in the font, as is the case today for Russian
>cyrillic vs balkan cyrillic  and may be in the future for Arabic vs
>Farsi).
>
>So I need to provide an optional way to blacklist these fonts with some
>scripts in fontconfig. Optional because we do need users to test and
>report on unfinished blocks, and while splitting the fonts by unicode
>blocks would be possible, it would be a nightmare from packaging and
>user font management point of views. I absolutely refuse to be
>responsible for DejaVu-script variants as we had Foo-encoding variants
>in the bad old pre-unicode pre-fontconfig days, and have users manually
>combine them in documents because the font tools are too dumb to do font
>management transparently.
>
>Moreover users of the more mature blocks are very happy with the fonts
>and are lobbying to make it the default in various distribution (at the
>Sans... aliases level).
>
>Thanksfully Fedora Devel sports fontconfig 2.3.95 so dropping files
>in /etc/fonts/conf.d is a possibility. The question right now is what to
>put in the conf files. I'm afraid fontconfig documentation is somewhat
>esoteric for people not working with it every days, and I couldn't find
>a clean example of what we want on the net.
>
>To blacklist the font with some scripts the following snippets have been
>proposed :
>===============
><match>
>   <test name="lang">
>      <string>fa</string>
>   </test>
><edit name="family" mode="prepend" binding="same">
>   <string>Roya</string>
></edit>
></match>
>===============
>
>===============  
>  <match>
>    <test name="lang"><string>fa</string></test>
>    <test name="family"><string>DejaVu Sans</string></test>
>    <edit name="family" mode="assign" binding="same">
>      <string>Roya</string>
>    </edit>
>  </match>
>===============
>
>Which ones will work best ? Are there better way to do it ?
>
>To make DejaVu the default in aliases I've been
>processing /etc/fonts/fonts.conf via xslt so far it works but is a tad
>complex maybe now there is a way to do it by dropping a file in conf.d ?
>
>Regards,
>
>  
>
>------------------------------------------------------------------------
>
>_______________________________________________
>Fontconfig mailing list
>Fontconfig at lists.freedesktop.org
>http://lists.freedesktop.org/mailman/listinfo/fontconfig
>  
>

-------------- next part --------------
--- fcint.h.orig	Mon Jun 12 09:58:54 2006
+++ fcint.h	Mon Jun 12 10:06:24 2006
@@ -327,6 +327,21 @@
     FcChar32	*blanks;
 };
 
+#ifdef RESTRICT
+typedef struct _FcFontLang {
+    FcChar8             *family;        /* Name of font family */
+    FcStrSet            *langs;         /* list of restricted languages */
+    struct _FcFontLang  *next;          /* Next font family */
+} FcFontLang;
+
+typedef struct _FcRestrict {
+    FcChar8             *alias;         /* Name of alias Sans/Serif */
+    FcFontLang          *family;        /* Restricted languages struct */
+    FcFontLang          *az[26];        /* Quick pointers */
+    struct _FcRestrict  *next;          /* Next alias */
+} FcRestrict;
+#endif
+
 struct _FcConfig {
     /*
      * File names loaded from the configuration -- saved here as the
@@ -381,6 +396,16 @@
      */
     time_t	rescanTime;	    /* last time information was scanned */
     int		rescanInterval;	    /* interval between scans */
+#ifdef RESTRICT
+    /*
+     * Languages can be selectively turned off for some fonts to allow
+     * fonts to be used for specific languages only even if the font
+     * supports the language. This allows best fonts to be used per
+     * language where two fonts that both support the same language
+     * cannot be placed in the preference list appropriately.
+     */
+    FcRestrict *restrictFont;
+#endif
 };
  
 extern FcConfig	*_fcConfig;
@@ -645,6 +670,11 @@
 void
 FcEditDestroy (FcEdit *e);
 
+#ifdef RESTRICT
+void
+FcRestrictDestroy ( FcRestrict *r );
+#endif
+
 /* fcinit.c */
 
 void
@@ -676,6 +706,14 @@
 FcBool
 FcNameUnparseLangSet (FcStrBuf *buf, const FcLangSet *ls);
 
+#ifdef RESTRICT
+FcBool
+FcLangSetRemove (FcLangSet *ls, const FcChar8 *lang);
+
+FcBool
+FcLangSetRemoveLangs (FcLangSet *ls, FcStrSet *langs);
+#endif
+
 /* fclist.c */
 
 FcBool
--- fccfg.c.orig	Mon Jun 12 09:44:21 2006
+++ fccfg.c	Mon Jun 12 10:04:14 2006
@@ -115,6 +115,9 @@
 
     config->rescanTime = time(0);
     config->rescanInterval = 30;    
+#ifdef RESTRICT
+    config->restrictFont = NULL;
+#endif
     
     return config;
 
@@ -233,6 +236,9 @@
     for (set = FcSetSystem; set <= FcSetApplication; set++)
 	if (config->fonts[set])
 	    FcFontSetDestroy (config->fonts[set]);
+#ifdef RESTRICT
+    FcRestrictDestroy (config->restrictFont);
+#endif
 
     free (config);
     FcMemFree (FC_MEM_CONFIG, sizeof (FcConfig));
--- fclang.c.orig	Mon Jun 12 09:46:05 2006
+++ fclang.c	Mon Jun 12 10:30:09 2006
@@ -43,6 +43,9 @@
 
 #define FcLangSetBitSet(ls, id)	((ls)->map[(id)>>5] |= ((FcChar32) 1 << ((id) & 0x1f)))
 #define FcLangSetBitGet(ls, id) (((ls)->map[(id)>>5] >> ((id) & 0x1f)) & 1)
+#ifdef RESTRICT
+#define FcLangSetBitUnset(ls, id)       ((ls)->map[(id)>>5] &= ~((FcChar32) 1 << ((id) & 0x1f)))
+#endif
 
 FcLangSet *
 FcFreeTypeLangSet (const FcCharSet  *charset, 
@@ -336,6 +339,36 @@
     return FcStrSetAdd (ls->extra, lang);
 }
 
+#ifdef RESTRICT
+FcBool
+FcLangSetRemove (FcLangSet *ls, const FcChar8 *lang)
+{
+    int id;
+
+    id = FcLangSetIndex (lang);
+    if (id >= 0)
+    {
+        FcLangSetBitUnset (ls, id);
+        return FcTrue;
+    }
+    if (ls->extra)
+        return FcStrSetDel (ls->extra, lang);
+    return FcFalse;
+}
+
+FcBool
+FcLangSetRemoveLangs (FcLangSet *ls, FcStrSet *langs)
+{
+    int i;
+
+    for ( i = 0; i < langs->num; i++ )
+    {
+        FcLangSetRemove ( ls, langs->strs[i] );
+    }
+    return FcTrue;
+}
+#endif
+
 FcLangResult
 FcLangSetHasLang (const FcLangSet *ls, const FcChar8 *lang)
 {
--- fcmatch.c.orig	Mon Jun 12 09:47:34 2006
+++ fcmatch.c	Mon Jun 12 10:07:45 2006
@@ -491,6 +491,100 @@
     return new;
 }
 
+#ifdef RESTRICT
+static FcRestrict *
+FcRestrictGetList (FcConfig *config, FcPattern *p)
+{
+    FcRestrict *r = NULL;
+
+    if ( config->restrictFont )
+    {
+        FcPatternElt *pe    = FcPatternFindElt (p, "family");
+
+        r = config->restrictFont;
+
+        /*
+         * Find the base family (alias) and see if it is in the
+         * restrict list
+         */
+        if ( pe )
+        {
+            FcValueList *vlist = pe->values;
+
+            while ( vlist->next )
+                vlist = vlist->next;
+
+            while ( r )
+            {
+                if ( !strcasecmp ( (char *)r->alias, (char *)vlist->value.u.s ))                    break;
+                else
+                    r = r->next;
+            }
+        }
+
+        /*
+         * At this point, we have the list of restricted fonts
+         * for the alias. We now need to check and see if the
+         * font we are comparing is on the restrict list.
+         */
+    }
+
+    return r;
+}
+
+static int
+FcRestrictFont (FcRestrict *r, FcPatternElt *pe, FcPattern *fnt, FcPatternElt **ppe, FcLangSet **pls)
+{
+    FcFontLang *fl;
+    int         ret = 0;
+
+    if ( r && pe )
+    {
+        FcLangSet  *ls;
+        int        pos = (int)(FcToLower(pe->values->value.u.s[0])) - 'a';
+
+        fl = r->az[pos];
+
+        while ( fl )
+        {
+            FcValueList *vlist = pe->values;
+            int          cmp   = strcasecmp ( (char *)fl->family,
+                                              (char *)vlist->value.u.s );
+
+            if ( !cmp )
+                break;
+            else if ( cmp > 0 )
+                fl = NULL;
+            else
+                fl = fl->next;
+        }
+
+        if ( fl )
+        {
+            *ppe = FcPatternFindElt (fnt, "lang");
+            if ( *ppe )
+            {
+                ret = 1;
+                if ( (*ppe)->values->value.type == FcTypeLangSet )
+                {
+                    ls = FcLangSetCopy ( (*ppe)->values->value.u.l );
+                    FcLangSetRemoveLangs ( ls, fl->langs );
+
+                    (*pls) = (FcLangSet *)(*ppe)->values->value.u.l;
+                    (*ppe)->values->value.u.l = ls;
+                }
+                else
+                {
+
+                }
+            }
+        }
+    }
+
+    return ret;
+}
+#endif
+
 static void
 FcChangeFormat ( FcPattern *p )
 {
@@ -543,6 +637,9 @@
     FcPattern	    *best;
     int		    i;
     int		    set;
+#ifdef RESTRICT
+    FcRestrict      *r = NULL;
+#endif
 
     FcChangeFormat ( p );
 
@@ -563,6 +660,11 @@
 	    return 0;
 	}
     }
+
+#ifdef RESTRICT
+    r = FcRestrictGetList ( config, p );
+#endif
+
     for (set = 0; set < nsets; set++)
     {
 	s = sets[set];
@@ -570,6 +672,12 @@
 	    continue;
 	for (f = 0; f < s->nfont; f++)
 	{
+#ifdef RESTRICT
+            FcLangSet    *oldLangSet = NULL;
+            FcPatternElt *pe = FcPatternFindElt (s->fonts[f], "family"), *pe2;
+            int    restrictFont = FcRestrictFont ( r, pe, s->fonts[f], &pe2, &oldLangSet);
+            int    res = 1;
+#endif
 	    if (FcDebug () & FC_DBG_MATCHV)
 	    {
 		printf ("Font %d ", f);
@@ -576,7 +684,22 @@
 		FcPatternPrint (s->fonts[f]);
 	    }
 	    if (!FcCompare (p, s->fonts[f], score, result))
-		return 0;
+#ifdef RESTRICT
+		res = 0;
+
+            if ( restrictFont )
+            {
+                if ( pe2->values->value.type == FcTypeLangSet )
+                {
+                    FcLangSetDestroy ((FcLangSet *) pe2->values->value.u.l );
+                    pe2->values->value.u.l = oldLangSet;
+                }
+            }
+
+            if ( !res )
+#endif
+                return 0;
+
 	    if (FcDebug () & FC_DBG_MATCHV)
 	    {
 		printf ("Score");
@@ -731,7 +854,11 @@
     int		    nPatternLang;
     int    	    *patternLangSat;
     FcValue	    patternLang;
+#ifdef RESTRICT
+    FcRestrict      *r = NULL;
+#endif
 
+
     FcChangeFormat ( p );
 
     if (FcDebug () & FC_DBG_MATCH)
@@ -766,6 +893,11 @@
     
     new = nodes;
     nodep = nodeps;
+
+#ifdef RESTRICT
+    r = FcRestrictGetList ( config, p );
+#endif
+
     for (set = 0; set < nsets; set++)
     {
 	s = sets[set];
@@ -773,6 +905,12 @@
 	    continue;
 	for (f = 0; f < s->nfont; f++)
 	{
+#ifdef RESTRICT
+            FcLangSet    *oldLangSet = NULL;
+            FcPatternElt *pe = FcPatternFindElt (s->fonts[f], "family"), *pe2;
+            int    restrictFont = FcRestrictFont ( r, pe, s->fonts[f], &pe2, &oldLangSet);
+            int    res = 1;
+#endif
 	    if (FcDebug () & FC_DBG_MATCHV)
 	    {
 		printf ("Font %d ", f);
@@ -780,7 +918,22 @@
 	    }
 	    new->pattern = s->fonts[f];
 	    if (!FcCompare (p, new->pattern, new->score, result))
-		goto bail1;
+#ifdef RESTRICT
+		res = 0;
+
+            if ( restrictFont )
+            {
+                if ( pe2->values->value.type == FcTypeLangSet )
+                {
+                    FcLangSetDestroy ((FcLangSet *) pe2->values->value.u.l );
+                    pe2->values->value.u.l = oldLangSet;
+                }
+            }
+
+            if ( !res )
+#endif
+                goto bail1;
+
 	    if (FcDebug () & FC_DBG_MATCHV)
 	    {
 		printf ("Score");
--- fcxml.c.orig	Mon Jun 12 09:53:00 2006
+++ fcxml.c	Mon Jun 12 10:04:14 2006
@@ -284,6 +284,11 @@
     FcElementDefault,
     FcElementFamily,
 
+#ifdef RESTRICT
+    FcElementRestrict,
+    FcElementLang,
+#endif
+
     FcElementSelectfont,
     FcElementAcceptfont,
     FcElementRejectfont,
@@ -347,6 +352,11 @@
 	{ "default",	FcElementDefault },
 	{ "family",	FcElementFamily },
 
+#ifdef RESTRICT
+        { "restrict",   FcElementRestrict },
+        { "lang",       FcElementLang },
+#endif
+
 	{ "selectfont",	FcElementSelectfont },
 	{ "acceptfont",	FcElementAcceptfont },
 	{ "rejectfont",	FcElementRejectfont },
@@ -415,6 +425,11 @@
     FcVStackPrefer,
     FcVStackAccept,
     FcVStackDefault,
+
+#ifdef RESTRICT
+    FcVStackRestrict,
+    FcVStackLang,
+#endif
     
     FcVStackInteger,
     FcVStackDouble,
@@ -711,6 +726,9 @@
 	case FcVStackField:
 	case FcVStackConstant:
 	case FcVStackGlob:
+#ifdef RESTRICT
+        case FcVStackLang:
+#endif
 	    FcStrFree (vstack->u.string);
 	    break;
 	case FcVStackPattern:
@@ -718,6 +736,9 @@
 	    break;
 	case FcVStackInteger:
 	case FcVStackDouble:
+#ifdef RESTRICT
+        case FcVStackRestrict:
+#endif
 	    break;
 	case FcVStackMatrix:
 	    FcMatrixFree (vstack->u.matrix);
@@ -1326,7 +1347,195 @@
 	FcVStackPushExpr (parse, FcVStackFamily, expr);
 }
 
+#ifdef RESTRICT
 static void
+FcParseLang (FcConfigParse *parse)
+{
+    FcChar8 *s;
+    FcExpr  *expr;
+
+    if (!parse->pstack)
+        return;
+    s = FcStrBufDone (&parse->pstack->str);
+    if (!s)
+    {
+        FcConfigMessage (parse, FcSevereError, "out of memory");
+        return;
+    }
+    expr = FcExprCreateString (s);
+    FcStrFree (s);
+    if (expr)
+        FcVStackPushExpr (parse, FcVStackLang, expr);
+}
+
+static void
+FcParseRestrict (FcConfigParse *parse, FcVStackTag tag)
+{
+    FcExpr      *family = 0;
+    FcStrSet    *lang = 0;
+    FcVStack    *vstack;
+    FcFontLang  *fl = NULL;
+    FcExpr      *expr;
+
+    while ((vstack = FcVStackPop (parse)))
+    {
+        switch (vstack->tag) {
+        case FcVStackFamily:
+            if (family)
+                FcExprDestroy (family);
+            family = vstack->u.expr;
+            vstack->tag = FcVStackNone;
+            break;
+        case FcVStackLang:
+            if (!lang)
+                lang = FcStrSetCreate ();
+            FcStrSetAdd (lang, vstack->u.expr->u.sval);
+            break;
+        default:
+            FcConfigMessage (parse, FcSevereWarning, "bad alias");
+            break;
+        }
+        FcVStackDestroy (vstack);
+    }
+    if (!family)
+    {
+        FcConfigMessage (parse, FcSevereError, "missing family in restrict");
+    }
+    if ( lang )
+    {
+        fl = (FcFontLang *)malloc (sizeof (FcFontLang));
+
+        if ( fl )
+        {
+            int len = strlen ( (char *)family->u.sval );
+            fl->family = (FcChar8 *)malloc ( sizeof ( char ) * ( len + 1 ));
+            strncpy ( (char *)fl->family, (char *)family->u.sval, len );
+            fl->family[len] = '\0';
+            fl->langs       = lang;
+            fl->next        = NULL;
+        }
+    }
+    if ( fl )
+    {
+        expr = FcExprCreateInteger ((int)fl);
+        if (expr)
+            FcVStackPushExpr (parse, FcVStackRestrict, expr);
+    }
+}
+
+static void
+FcFontLangDestroy(FcFontLang *fl)
+{
+    FcFontLang *cur = fl;
+
+    while (fl)
+    {
+        cur = fl;
+        fl  = fl->next;
+
+        free (cur->family);
+        FcStrSetDestroy (cur->langs);
+        free (cur);
+    }
+}
+
+void
+FcRestrictDestroy ( FcRestrict *r )
+{
+    if ( r )
+    {
+        if ( r->alias )
+            free ( r->alias );
+        FcFontLangDestroy ( r->family );
+        FcRestrictDestroy ( r->next );
+        free ( r );
+    }
+}
+
+static void
+FcRestrictCreate(FcConfigParse *parse, FcFontLang *fl, FcExpr *family)
+{
+    FcRestrict *new = (FcRestrict *)malloc ( sizeof ( FcRestrict ));
+
+    if ( new )
+    {
+        int len = strlen ( (char *)family->u.sval );
+        int i;
+
+        new->alias  = (FcChar8 *)malloc ( sizeof ( char ) * ( len + 1 ));
+        if ( new->alias )
+        {
+            strncpy ((char *)new->alias, (char *)family->u.sval, len);
+            new->alias[len] = '\0';
+        }
+        else
+        {
+            free (new);
+            return;
+        }
+        new->family = NULL;
+        new->next   = parse->config->restrictFont;
+        parse->config->restrictFont = new;
+
+        /*
+         * Add FontLang structure in alphabetical order based on family
+         * to the restrict list
+         */
+        while ( fl )
+        {
+            FcFontLang *next = fl->next;
+
+            fl->next = NULL;
+
+            if ( !new->family )
+                new->family = fl;
+            else
+            {
+                FcFontLang *ptr  = new->family;
+                FcFontLang *last = NULL;
+
+                while ( ptr )
+                {
+                    if ( strcasecmp ( (char *)fl->family, (char *)ptr->family) < 0)
+                    {
+                        fl->next = ptr;
+                        if ( !last )
+                            new->family = fl;
+                        else
+                            last->next = fl;
+                        break;
+                    }
+                    else
+                    {
+                        last = ptr;
+                        ptr = ptr->next;
+
+                        if ( !ptr )
+                            last->next = fl;
+                    }
+                }
+            }
+            fl = next;
+        }
+
+        for ( i = 0; i < 26; i++ )
+            new->az[i] = NULL;
+
+        fl = new->family;
+        while ( fl )
+        {
+            int pos = (int)(fl->family[0]) - (int)'A';
+            if ( !new->az[pos] )
+            {
+                new->az[pos] = fl;
+            }
+            fl = fl->next;
+        }
+    }
+}
+#endif
+
+static void
 FcParseAlias (FcConfigParse *parse)
 {
     FcExpr	*family = 0, *accept = 0, *prefer = 0, *def = 0, *new = 0;
@@ -1333,6 +1542,10 @@
     FcEdit	*edit = 0, *next;
     FcVStack	*vstack;
     FcTest	*test;
+#ifdef RESTRICT
+    FcFontLang  *fl = NULL;
+    FcFontLang  *tmp;
+#endif
 
     while ((vstack = FcVStackPop (parse))) 
     {
@@ -1354,6 +1567,15 @@
 		vstack->tag = FcVStackNone;
 	    }
 	    break;
+#ifdef RESTRICT
+        case FcVStackRestrict:
+            tmp = (FcFontLang *)(vstack->u.expr->u.ival);
+            FcExprDestroy (vstack->u.expr);
+            tmp->next = fl;
+            fl = tmp;
+            vstack->tag = FcVStackNone;
+            break;
+#endif
 	case FcVStackPrefer:
 	    if (prefer)
 		FcExprDestroy (prefer);
@@ -1427,6 +1649,13 @@
 	else
 	    FcExprDestroy (def);
     }
+#ifdef RESTRICT
+    if (fl)
+    {
+        FcRestrictCreate(parse, fl, family);
+        FcExprDestroy (family);
+    }
+#endif
     if (edit)
     {
 	test = FcTestCreate (parse, FcMatchPattern,
@@ -1454,6 +1683,9 @@
 	break;
     case FcVStackString:
     case FcVStackFamily:
+#ifdef RESTRICT
+    case FcVStackLang:
+#endif
 	expr = FcExprCreateString (vstack->u.string);
 	break;
     case FcVStackField:
@@ -2077,6 +2309,14 @@
     case FcElementFamily:
 	FcParseFamily (parse);
 	break;
+#ifdef RESTRICT
+    case FcElementRestrict:
+        FcParseRestrict (parse, FcVStackLang);
+        break;
+    case FcElementLang:
+        FcParseLang (parse);
+        break;
+#endif
 
     case FcElementTest:
 	FcParseTest (parse);


More information about the Fontconfig mailing list