[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