src/fccfg.c | 682 ++++++++++++++++++++++++++++++++++++++-------------------- src/fcint.h | 11 src/fcmatch.c | 183 +++++++++++++-- src/fcstr.c | 103 ++++++-- 4 files changed, 697 insertions(+), 282 deletions(-) New commits: commit ff7d314ab5f04d3ad54d5165b81d87894d7a8dfe Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Tue Aug 25 18:12:07 2020 -0400 Fix up FC_LIKELY macros __builtin_expect returns the same type as the expression, so enforce that we pass in a boolean expression. Pointed out by Emmanuele Bassi. diff --git a/src/fcint.h b/src/fcint.h index fb173b5..a3b192d 100644 --- a/src/fcint.h +++ b/src/fcint.h @@ -61,8 +61,8 @@ #define FC_LIKELY(expr) (expr) #define FC_UNLIKELY(expr) (expr) #else -#define FC_LIKELY(expr) (__builtin_expect (expr, 1)) -#define FC_UNLIKELY(expr) (__builtin_expect (expr, 0)) +#define FC_LIKELY(expr) (__builtin_expect (((expr) ? 1 : 0), 1)) +#define FC_UNLIKELY(expr) (__builtin_expect (((expr) ? 1 : 0), 0)) #endif #ifdef _WIN32 commit e117d6768c5cf5ae37405d03ed6d3b076e713dde Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Tue Aug 25 07:26:32 2020 -0400 Fixup: Handle patterns without family Pointed out by Akira Tagoh. diff --git a/src/fcmatch.c b/src/fcmatch.c index 16d03de..df6db71 100644 --- a/src/fcmatch.c +++ b/src/fcmatch.c @@ -525,25 +525,28 @@ FcCompareDataInit (FcPattern *pat, free); elt = FcPatternObjectFindElt (pat, FC_FAMILY_OBJECT); - for (l = FcPatternEltValues(elt), i = 0; l; l = FcValueListNext(l), i++) + if (elt) { - key = FcValueString (&l->value); - if (!FcHashTableFind (table, key, (void **)&e)) + for (l = FcPatternEltValues(elt), i = 0; l; l = FcValueListNext(l), i++) { - e = malloc (sizeof (FamilyEntry)); - e->strong_value = 1e99; - e->weak_value = 1e99; - FcHashTableAdd (table, (void *)key, e); - } - if (l->binding == FcValueBindingWeak) - { - if (i < e->weak_value) - e->weak_value = i; - } - else - { - if (i < e->strong_value) - e->strong_value = i; + key = FcValueString (&l->value); + if (!FcHashTableFind (table, key, (void **)&e)) + { + e = malloc (sizeof (FamilyEntry)); + e->strong_value = 1e99; + e->weak_value = 1e99; + FcHashTableAdd (table, (void *)key, e); + } + if (l->binding == FcValueBindingWeak) + { + if (i < e->weak_value) + e->weak_value = i; + } + else + { + if (i < e->strong_value) + e->strong_value = i; + } } } commit 51d40491fc483787ba66fb76344ff7d4c13a39b3 Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Mon Aug 24 07:59:50 2020 -0400 Use FC_UNLIKELY diff --git a/src/fcstr.c b/src/fcstr.c index f1f75dc..8971b71 100644 --- a/src/fcstr.c +++ b/src/fcstr.c @@ -164,7 +164,7 @@ FcStrCaseWalkerNextNonDelim (FcCaseWalker *w, const char *delims) { FcChar8 r; - if (__builtin_expect (w->read != NULL, 0)) + if (FC_UNLIKELY (w->read != NULL)) { if ((r = *w->read++)) return r; @@ -175,7 +175,7 @@ FcStrCaseWalkerNextNonDelim (FcCaseWalker *w, const char *delims) r = *w->src++; } while (r != 0 && delims && strchr (delims, r)); - if (__builtin_expect ((r & 0xc0) == 0xc0, 0)) + if (FC_UNLIKELY ((r & 0xc0) == 0xc0)) return FcStrCaseWalkerLong (w, r); if ('A' <= r && r <= 'Z') r = r - 'A' + 'a'; @@ -187,7 +187,7 @@ FcStrCaseWalkerNextNonBlank (FcCaseWalker *w) { FcChar8 r; - if (__builtin_expect (w->read != NULL, 0)) + if (FC_UNLIKELY (w->read != NULL)) { if ((r = *w->read++)) return r; @@ -198,7 +198,7 @@ FcStrCaseWalkerNextNonBlank (FcCaseWalker *w) r = *w->src++; } while (r == ' '); - if (__builtin_expect ((r & 0xc0) == 0xc0, 0)) + if (FC_UNLIKELY ((r & 0xc0) == 0xc0)) return FcStrCaseWalkerLong (w, r); if ('A' <= r && r <= 'Z') r = r - 'A' + 'a'; @@ -210,7 +210,7 @@ FcStrCaseWalkerNext (FcCaseWalker *w) { FcChar8 r; - if (__builtin_expect (w->read != NULL, 0)) + if (FC_UNLIKELY (w->read != NULL)) { if ((r = *w->read++)) return r; @@ -219,7 +219,7 @@ FcStrCaseWalkerNext (FcCaseWalker *w) r = *w->src++; - if (__builtin_expect ((r & 0xc0) == 0xc0, 0)) + if (FC_UNLIKELY ((r & 0xc0) == 0xc0)) return FcStrCaseWalkerLong (w, r); if ('A' <= r && r <= 'Z') r = r - 'A' + 'a'; commit 76699a4813a05fd89114a29965d2532adc1553b0 Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Mon Aug 24 07:59:18 2020 -0400 Add FC_LIKELY and FC_UNLIKELY macros These wrap __builtin_expect where it exists. diff --git a/src/fcint.h b/src/fcint.h index 08a0cd6..fb173b5 100644 --- a/src/fcint.h +++ b/src/fcint.h @@ -57,6 +57,14 @@ #define FC_CONFIG_PATH "fonts.conf" #endif +#ifdef _WIN32 +#define FC_LIKELY(expr) (expr) +#define FC_UNLIKELY(expr) (expr) +#else +#define FC_LIKELY(expr) (__builtin_expect (expr, 1)) +#define FC_UNLIKELY(expr) (__builtin_expect (expr, 0)) +#endif + #ifdef _WIN32 # include "fcwindows.h" typedef UINT (WINAPI *pfnGetSystemWindowsDirectory)(LPSTR, UINT); commit 835f9bbdbe7c023bce27f699c8906ace9b62a051 Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Sat Aug 22 02:35:24 2020 -0400 Fixup: Promote ints to ranges when appropriate Pointed out by Akira Tagoh. diff --git a/src/fccfg.c b/src/fccfg.c index 13bd0ff..f86f680 100644 --- a/src/fccfg.c +++ b/src/fccfg.c @@ -939,6 +939,13 @@ FcConfigPromote (FcValue v, FcValue u, FcValuePromotionBuffer *buf) case FcTypeInteger: v.type = FcTypeDouble; v.u.d = (double) v.u.i; + /* Fallthrough */ + case FcTypeDouble: + if (u.type == FcTypeRange && buf) + { + v.u.r = FcRangePromote (v.u.d, buf); + v.type = FcTypeRange; + } break; case FcTypeVoid: if (u.type == FcTypeMatrix) @@ -964,13 +971,6 @@ FcConfigPromote (FcValue v, FcValue u, FcValuePromotionBuffer *buf) v.type = FcTypeLangSet; } break; - case FcTypeDouble: - if (u.type == FcTypeRange && buf) - { - v.u.r = FcRangePromote (v.u.d, buf); - v.type = FcTypeRange; - } - break; default: break; } commit 148ebf98edb498d786f4c1107ec116e1afdf9541 Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Sat Aug 22 00:57:25 2020 -0400 Use __builtin_expect in a few places utf8 is extremely rare in the strings we see in font configuration, so this seems to be a good case for __builtin_expect. diff --git a/src/fcstr.c b/src/fcstr.c index dc9940a..f1f75dc 100644 --- a/src/fcstr.c +++ b/src/fcstr.c @@ -164,7 +164,7 @@ FcStrCaseWalkerNextNonDelim (FcCaseWalker *w, const char *delims) { FcChar8 r; - if (w->read) + if (__builtin_expect (w->read != NULL, 0)) { if ((r = *w->read++)) return r; @@ -175,7 +175,7 @@ FcStrCaseWalkerNextNonDelim (FcCaseWalker *w, const char *delims) r = *w->src++; } while (r != 0 && delims && strchr (delims, r)); - if ((r & 0xc0) == 0xc0) + if (__builtin_expect ((r & 0xc0) == 0xc0, 0)) return FcStrCaseWalkerLong (w, r); if ('A' <= r && r <= 'Z') r = r - 'A' + 'a'; @@ -187,7 +187,7 @@ FcStrCaseWalkerNextNonBlank (FcCaseWalker *w) { FcChar8 r; - if (w->read) + if (__builtin_expect (w->read != NULL, 0)) { if ((r = *w->read++)) return r; @@ -198,7 +198,7 @@ FcStrCaseWalkerNextNonBlank (FcCaseWalker *w) r = *w->src++; } while (r == ' '); - if ((r & 0xc0) == 0xc0) + if (__builtin_expect ((r & 0xc0) == 0xc0, 0)) return FcStrCaseWalkerLong (w, r); if ('A' <= r && r <= 'Z') r = r - 'A' + 'a'; @@ -210,7 +210,7 @@ FcStrCaseWalkerNext (FcCaseWalker *w) { FcChar8 r; - if (w->read) + if (__builtin_expect (w->read != NULL, 0)) { if ((r = *w->read++)) return r; @@ -219,7 +219,7 @@ FcStrCaseWalkerNext (FcCaseWalker *w) r = *w->src++; - if ((r & 0xc0) == 0xc0) + if (__builtin_expect ((r & 0xc0) == 0xc0, 0)) return FcStrCaseWalkerLong (w, r); if ('A' <= r && r <= 'Z') r = r - 'A' + 'a'; commit 13015a0a6ed15ef2e0241a92ef05233dc51ce23b Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Tue Aug 18 16:33:57 2020 -0400 Use a hash table for families in FcConfigSubstitute Use the same approach we used for FcFontMatch, and keep a hash table of family names. We only speed up the no-match case, for now. diff --git a/src/fccfg.c b/src/fccfg.c index 803cd68..13bd0ff 100644 --- a/src/fccfg.c +++ b/src/fccfg.c @@ -1579,12 +1579,128 @@ FcConfigEvaluate (FcPattern *p, FcPattern *p_pat, FcMatchKind kind, FcExpr *e) return v; } +/* The bulk of the time in FcConfigSubstitute is spent walking + * lists of family names. We speed this up with a hash table. + * Since we need to take the ignore-blanks option into account, + * we use two separate hash tables. + */ +typedef struct +{ + int count; +} FamilyTableEntry; + + +typedef struct +{ + FcHashTable *family_blank_hash; + FcHashTable *family_hash; +} FamilyTable; + +static FcBool +FamilyTableLookup (FamilyTable *table, + FcOp _op, + const FcChar8 *s) +{ + FamilyTableEntry *fe; + int flags = FC_OP_GET_FLAGS (_op); + FcHashTable *hash; + + if (flags & FcOpFlagIgnoreBlanks) + hash = table->family_blank_hash; + else + hash = table->family_hash; + + return FcHashTableFind (hash, (const void *)s, (void **)&fe); +} + +static void +FamilyTableAdd (FamilyTable *table, + FcValueListPtr values) +{ + FcValueListPtr ll; + for (ll = values; ll; ll = FcValueListNext (ll)) + { + const FcChar8 *s = FcValueString (&ll->value); + FamilyTableEntry *fe; + + if (!FcHashTableFind (table->family_hash, (const void *)s, (void **)&fe)) + { + fe = malloc (sizeof (FamilyTableEntry)); + fe->count = 0; + FcHashTableAdd (table->family_hash, (void *)s, fe); + } + fe->count++; + + if (!FcHashTableFind (table->family_blank_hash, (const void *)s, (void **)&fe)) + { + fe = malloc (sizeof (FamilyTableEntry)); + fe->count = 0; + FcHashTableAdd (table->family_blank_hash, (void *)s, fe); + } + fe->count++; + } +} + +static void +FamilyTableDel (FamilyTable *table, + const FcChar8 *s) +{ + FamilyTableEntry *fe; + + if (FcHashTableFind (table->family_hash, (void *)s, (void **)&fe)) + { + fe->count--; + if (fe->count == 0) + FcHashTableRemove (table->family_hash, (void *)s); + } + + if (FcHashTableFind (table->family_blank_hash, (void *)s, (void **)&fe)) + { + fe->count--; + if (fe->count == 0) + FcHashTableRemove (table->family_blank_hash, (void *)s); + } +} + +static void +FamilyTableInit (FamilyTable *table, + FcPattern *p) +{ + FcPatternElt *e; + + table->family_blank_hash = FcHashTableCreate ((FcHashFunc)FcStrHashIgnoreBlanksAndCase, + (FcCompareFunc)FcStrCmpIgnoreBlanksAndCase, + NULL, + NULL, + NULL, + free); + table->family_hash = FcHashTableCreate ((FcHashFunc)FcStrHashIgnoreCase, + (FcCompareFunc)FcStrCmpIgnoreCase, + NULL, + NULL, + NULL, + free); + e = FcPatternObjectFindElt (p, FC_FAMILY_OBJECT); + if (e) + FamilyTableAdd (table, FcPatternEltValues (e)); +} + +static void +FamilyTableClear (FamilyTable *table) +{ + if (table->family_blank_hash) + FcHashTableDestroy (table->family_blank_hash); + if (table->family_hash) + FcHashTableDestroy (table->family_hash); +} + static FcValueList * FcConfigMatchValueList (FcPattern *p, FcPattern *p_pat, FcMatchKind kind, FcTest *t, - FcValueList *values) + FcValueList *values, + FamilyTable *table) { FcValueList *ret = 0; FcExpr *e = t->expr; @@ -1605,6 +1721,14 @@ FcConfigMatchValueList (FcPattern *p, e = 0; } + if (t->object == FC_FAMILY_OBJECT && table) + { + if (!FamilyTableLookup (table, t->op, FcValueString (&value))) + { + ret = 0; + goto done; + } + } for (v = values; v; v = FcValueListNext(v)) { /* Compare the pattern value to the match expression value */ @@ -1624,6 +1748,7 @@ FcConfigMatchValueList (FcPattern *p, } } } +done: FcValueDestroy (value); } return ret; @@ -1666,7 +1791,8 @@ FcConfigAdd (FcValueListPtr *head, FcValueList *position, FcBool append, FcValueList *new, - FcObject object) + FcObject object, + FamilyTable *table) { FcValueListPtr *prev, l, last, v; FcValueBinding sameBinding; @@ -1692,6 +1818,11 @@ FcConfigAdd (FcValueListPtr *head, } } + if (object == FC_FAMILY_OBJECT && table) + { + FamilyTableAdd (table, new); + } + if (position) sameBinding = position->binding; else @@ -1758,10 +1889,17 @@ FcConfigAdd (FcValueListPtr *head, static void FcConfigDel (FcValueListPtr *head, - FcValueList *position) + FcValueList *position, + FcObject object, + FamilyTable *table) { FcValueListPtr *prev; + if (object == FC_FAMILY_OBJECT && table) + { + FamilyTableDel (table, FcValueString (&position->value)); + } + for (prev = head; *prev != NULL; prev = &(*prev)->next) { if (*prev == position) @@ -1776,9 +1914,10 @@ FcConfigDel (FcValueListPtr *head, static void FcConfigPatternAdd (FcPattern *p, - FcObject object, + FcObject object, FcValueList *list, - FcBool append) + FcBool append, + FamilyTable *table) { if (list) { @@ -1786,7 +1925,7 @@ FcConfigPatternAdd (FcPattern *p, if (!e) return; - FcConfigAdd (&e->values, 0, append, list, object); + FcConfigAdd (&e->values, 0, append, list, object, table); } } @@ -1795,13 +1934,14 @@ FcConfigPatternAdd (FcPattern *p, */ static void FcConfigPatternDel (FcPattern *p, - FcObject object) + FcObject object, + FamilyTable *table) { FcPatternElt *e = FcPatternObjectFindElt (p, object); if (!e) return; while (e->values != NULL) - FcConfigDel (&e->values, e->values); + FcConfigDel (&e->values, e->values, object, table); } static void @@ -1834,6 +1974,8 @@ FcConfigSubstituteWithPat (FcConfig *config, int i, nobjs; FcBool retval = FcTrue; FcTest **tst = NULL; + FamilyTable data; + FamilyTable *table = &data; if (kind < FcMatchKindBegin || kind >= FcMatchKindEnd) return FcFalse; @@ -1931,6 +2073,9 @@ FcConfigSubstituteWithPat (FcConfig *config, printf ("FcConfigSubstitute "); FcPatternPrint (p); } + + FamilyTableInit (&data, p); + FcPtrListIterInit (s, &iter); for (; FcPtrListIterIsValid (s, &iter); FcPtrListIterNext (s, &iter)) { @@ -1967,9 +2112,15 @@ FcConfigSubstituteWithPat (FcConfig *config, FcTestPrint (r->u.test); } if (kind == FcMatchFont && r->u.test->kind == FcMatchPattern) + { m = p_pat; + table = NULL; + } else + { m = p; + table = &data; + } if (m) e = FcPatternObjectFindElt (m, r->u.test->object); else @@ -2002,7 +2153,7 @@ FcConfigSubstituteWithPat (FcConfig *config, * Check to see if there is a match, mark the location * to apply match-relative edits */ - vl = FcConfigMatchValueList (m, p_pat, kind, r->u.test, e->values); + vl = FcConfigMatchValueList (m, p_pat, kind, r->u.test, e->values, table); /* different 'kind' won't be the target of edit */ if (!value[object] && kind == r->u.test->kind) value[object] = vl; @@ -2026,7 +2177,7 @@ FcConfigSubstituteWithPat (FcConfig *config, /* * Evaluate the list of expressions */ - l = FcConfigValues (p, p_pat,kind, r->u.edit->expr, r->u.edit->binding); + l = FcConfigValues (p, p_pat, kind, r->u.edit->expr, r->u.edit->binding); if (tst[object] && (tst[object]->kind == FcMatchFont || kind == FcMatchPattern)) elt[object] = FcPatternObjectFindElt (p, tst[object]->object); @@ -2044,12 +2195,12 @@ FcConfigSubstituteWithPat (FcConfig *config, /* * Append the new list of values after the current value */ - FcConfigAdd (&elt[object]->values, thisValue, FcTrue, l, r->u.edit->object); + FcConfigAdd (&elt[object]->values, thisValue, FcTrue, l, r->u.edit->object, table); /* * Delete the marked value */ if (thisValue) - FcConfigDel (&elt[object]->values, thisValue); + FcConfigDel (&elt[object]->values, thisValue, object, table); /* * Adjust a pointer into the value list to ensure * future edits occur at the same place @@ -2063,8 +2214,8 @@ FcConfigSubstituteWithPat (FcConfig *config, * Delete all of the values and insert * the new set */ - FcConfigPatternDel (p, r->u.edit->object); - FcConfigPatternAdd (p, r->u.edit->object, l, FcTrue); + FcConfigPatternDel (p, r->u.edit->object, table); + FcConfigPatternAdd (p, r->u.edit->object, l, FcTrue, table); /* * Adjust a pointer into the value list as they no * longer point to anything valid @@ -2074,33 +2225,33 @@ FcConfigSubstituteWithPat (FcConfig *config, case FcOpPrepend: if (value[object]) { - FcConfigAdd (&elt[object]->values, value[object], FcFalse, l, r->u.edit->object); + FcConfigAdd (&elt[object]->values, value[object], FcFalse, l, r->u.edit->object, table); break; } /* fall through ... */ case FcOpPrependFirst: - FcConfigPatternAdd (p, r->u.edit->object, l, FcFalse); + FcConfigPatternAdd (p, r->u.edit->object, l, FcFalse, table); break; case FcOpAppend: if (value[object]) { - FcConfigAdd (&elt[object]->values, value[object], FcTrue, l, r->u.edit->object); + FcConfigAdd (&elt[object]->values, value[object], FcTrue, l, r->u.edit->object, table); break; } /* fall through ... */ case FcOpAppendLast: - FcConfigPatternAdd (p, r->u.edit->object, l, FcTrue); + FcConfigPatternAdd (p, r->u.edit->object, l, FcTrue, table); break; case FcOpDelete: if (value[object]) { - FcConfigDel (&elt[object]->values, value[object]); + FcConfigDel (&elt[object]->values, value[object], object, table); FcValueListDestroy (l); break; } /* fall through ... */ case FcOpDeleteAll: - FcConfigPatternDel (p, r->u.edit->object); + FcConfigPatternDel (p, r->u.edit->object, table); FcValueListDestroy (l); break; default: @@ -2130,6 +2281,7 @@ FcConfigSubstituteWithPat (FcConfig *config, FcPatternPrint (p); } bail1: + FamilyTableClear (&data); if (elt) free (elt); if (value) commit 7deb07e38e31da787875cb65db2ecb0dc0d5807f Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Fri Aug 21 09:12:35 2020 -0400 Speed up FcCompareLang and FcCompareBool Avoid FcCanonicalize here too. diff --git a/src/fcmatch.c b/src/fcmatch.c index 289204e..16d03de 100644 --- a/src/fcmatch.c +++ b/src/fcmatch.c @@ -104,30 +104,27 @@ static double FcCompareLang (const FcValue *v1, const FcValue *v2, FcValue *bestValue) { FcLangResult result; - FcValue value1 = FcValueCanonicalize(v1), value2 = FcValueCanonicalize(v2); - switch ((int) value1.type) { + switch ((int) v1->type) { case FcTypeLangSet: - switch ((int) value2.type) { + switch ((int) v2->type) { case FcTypeLangSet: - result = FcLangSetCompare (value1.u.l, value2.u.l); + result = FcLangSetCompare (FcValueLangSet (v1), FcValueLangSet (v2)); break; case FcTypeString: - result = FcLangSetHasLang (value1.u.l, - value2.u.s); + result = FcLangSetHasLang (FcValueLangSet (v1), FcValueString (v2)); break; default: return -1.0; } break; case FcTypeString: - switch ((int) value2.type) { + switch ((int) v2->type) { case FcTypeLangSet: - result = FcLangSetHasLang (value2.u.l, value1.u.s); + result = FcLangSetHasLang (FcValueLangSet (v2), FcValueString (v1)); break; case FcTypeString: - result = FcLangCompare (value1.u.s, - value2.u.s); + result = FcLangCompare (FcValueString (v1), FcValueString (v2)); break; default: return -1.0; @@ -154,10 +151,11 @@ FcCompareBool (const FcValue *v1, const FcValue *v2, FcValue *bestValue) if (v2->type != FcTypeBool || v1->type != FcTypeBool) return -1.0; + bestValue->type = FcTypeBool; if (v2->u.b != FcDontCare) - *bestValue = FcValueCanonicalize (v2); + bestValue->u.b = v2->u.b; else - *bestValue = FcValueCanonicalize (v1); + bestValue->u.b = v1->u.b; return (double) ((v2->u.b ^ v1->u.b) == 1); } commit 09729c9032e66da30c24d8268e444b2e6dd0e0d7 Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Fri Aug 21 00:49:04 2020 -0400 Speed up FcConfigCompareValue Avoid FcValueCanonicalize when we can, to avoid unnecessary copying of FcValue structs. diff --git a/src/fccfg.c b/src/fccfg.c index 8e7a682..803cd68 100644 --- a/src/fccfg.c +++ b/src/fccfg.c @@ -982,192 +982,215 @@ FcConfigCompareValue (const FcValue *left_o, unsigned int op_, const FcValue *right_o) { - FcValue left = FcValueCanonicalize(left_o); - FcValue right = FcValueCanonicalize(right_o); + FcValue left; + FcValue right; FcBool ret = FcFalse; FcOp op = FC_OP_GET_OP (op_); int flags = FC_OP_GET_FLAGS (op_); - if (left.type != right.type) + if (left_o->type != right_o->type) { + left = FcValueCanonicalize(left_o); + right = FcValueCanonicalize(right_o); FcValuePromotionBuffer buf1, buf2; left = FcConfigPromote (left, right, &buf1); right = FcConfigPromote (right, left, &buf2); - if (left.type != right.type) + left_o = &left; + right_o = &right; + if (left_o->type != right_o->type) { if (op == FcOpNotEqual || op == FcOpNotContains) ret = FcTrue; return ret; } } - switch (left.type) { + switch (left_o->type) { case FcTypeUnknown: break; /* No way to guess how to compare for this object */ - case FcTypeInteger: + case FcTypeInteger: { + int l = left_o->u.i; + int r = right_o->u.i; switch ((int) op) { case FcOpEqual: case FcOpContains: case FcOpListing: - ret = left.u.i == right.u.i; + ret = l == r; break; case FcOpNotEqual: case FcOpNotContains: - ret = left.u.i != right.u.i; + ret = l != r; break; case FcOpLess: - ret = left.u.i < right.u.i; + ret = l < r; break; case FcOpLessEqual: - ret = left.u.i <= right.u.i; + ret = l <= r; break; case FcOpMore: - ret = left.u.i > right.u.i; + ret = l > r; break; case FcOpMoreEqual: - ret = left.u.i >= right.u.i; + ret = l >= r; break; default: break; } break; - case FcTypeDouble: + } + case FcTypeDouble: { + double l = left_o->u.d; + double r = right_o->u.d; switch ((int) op) { case FcOpEqual: case FcOpContains: case FcOpListing: - ret = left.u.d == right.u.d; + ret = l == r; break; case FcOpNotEqual: case FcOpNotContains: - ret = left.u.d != right.u.d; + ret = l != r; break; case FcOpLess: - ret = left.u.d < right.u.d; + ret = l < r; break; case FcOpLessEqual: - ret = left.u.d <= right.u.d; + ret = l <= r; break; case FcOpMore: - ret = left.u.d > right.u.d; + ret = l > r; break; case FcOpMoreEqual: - ret = left.u.d >= right.u.d; + ret = l >= r; break; default: break; } break; - case FcTypeBool: + } + case FcTypeBool: { + FcBool l = left_o->u.b; + FcBool r = right_o->u.b; switch ((int) op) { case FcOpEqual: - ret = left.u.b == right.u.b; + ret = l == r; break; case FcOpContains: case FcOpListing: - ret = left.u.b == right.u.b || left.u.b >= FcDontCare; + ret = l == r || l >= FcDontCare; break; case FcOpNotEqual: - ret = left.u.b != right.u.b; + ret = l != r; break; case FcOpNotContains: - ret = !(left.u.b == right.u.b || left.u.b >= FcDontCare); + ret = !(l == r || l >= FcDontCare); break; case FcOpLess: - ret = left.u.b != right.u.b && right.u.b >= FcDontCare; + ret = l != r && r >= FcDontCare; break; case FcOpLessEqual: - ret = left.u.b == right.u.b || right.u.b >= FcDontCare; + ret = l == r || r >= FcDontCare; break; case FcOpMore: - ret = left.u.b != right.u.b && left.u.b >= FcDontCare; + ret = l != r && l >= FcDontCare; break; case FcOpMoreEqual: - ret = left.u.b == right.u.b || left.u.b >= FcDontCare; + ret = l == r || l >= FcDontCare; break; default: break; } break; - case FcTypeString: + } + case FcTypeString: { + const FcChar8 *l = FcValueString (left_o); + const FcChar8 *r = FcValueString (right_o); switch ((int) op) { case FcOpEqual: case FcOpListing: if (flags & FcOpFlagIgnoreBlanks) - ret = FcStrCmpIgnoreBlanksAndCase (left.u.s, right.u.s) == 0; + ret = FcStrCmpIgnoreBlanksAndCase (l, r) == 0; else - ret = FcStrCmpIgnoreCase (left.u.s, right.u.s) == 0; + ret = FcStrCmpIgnoreCase (l, r) == 0; break; case FcOpContains: - ret = FcStrStrIgnoreCase (left.u.s, right.u.s) != 0; + ret = FcStrStrIgnoreCase (l, r) != 0; break; case FcOpNotEqual: if (flags & FcOpFlagIgnoreBlanks) - ret = FcStrCmpIgnoreBlanksAndCase (left.u.s, right.u.s) != 0; + ret = FcStrCmpIgnoreBlanksAndCase (l, r) != 0; else - ret = FcStrCmpIgnoreCase (left.u.s, right.u.s) != 0; + ret = FcStrCmpIgnoreCase (l, r) != 0; break; case FcOpNotContains: - ret = FcStrStrIgnoreCase (left.u.s, right.u.s) == 0; + ret = FcStrStrIgnoreCase (l, r) == 0; break; default: break; } break; - case FcTypeMatrix: + } + case FcTypeMatrix: { switch ((int) op) { case FcOpEqual: case FcOpContains: case FcOpListing: - ret = FcMatrixEqual (left.u.m, right.u.m); + ret = FcMatrixEqual (left_o->u.m, right_o->u.m); break; case FcOpNotEqual: case FcOpNotContains: - ret = !FcMatrixEqual (left.u.m, right.u.m); + ret = !FcMatrixEqual (left_o->u.m, right_o->u.m); break; default: break; } break; - case FcTypeCharSet: + } + case FcTypeCharSet: { + const FcCharSet *l = FcValueCharSet (left_o); + const FcCharSet *r = FcValueCharSet (right_o); switch ((int) op) { case FcOpContains: case FcOpListing: /* left contains right if right is a subset of left */ - ret = FcCharSetIsSubset (right.u.c, left.u.c); + ret = FcCharSetIsSubset (r, l); break; case FcOpNotContains: /* left contains right if right is a subset of left */ - ret = !FcCharSetIsSubset (right.u.c, left.u.c); + ret = !FcCharSetIsSubset (r, l); break; case FcOpEqual: - ret = FcCharSetEqual (left.u.c, right.u.c); + ret = FcCharSetEqual (l, r); break; case FcOpNotEqual: - ret = !FcCharSetEqual (left.u.c, right.u.c); + ret = !FcCharSetEqual (l, r); break; default: break; } break; - case FcTypeLangSet: + } + case FcTypeLangSet: { + const FcLangSet *l = FcValueLangSet (left_o); + const FcLangSet *r = FcValueLangSet (right_o); switch ((int) op) { case FcOpContains: case FcOpListing: - ret = FcLangSetContains (left.u.l, right.u.l); + ret = FcLangSetContains (l, r); break; case FcOpNotContains: - ret = !FcLangSetContains (left.u.l, right.u.l); + ret = !FcLangSetContains (l, r); break; case FcOpEqual: - ret = FcLangSetEqual (left.u.l, right.u.l); + ret = FcLangSetEqual (l, r); break; case FcOpNotEqual: - ret = !FcLangSetEqual (left.u.l, right.u.l); + ret = !FcLangSetEqual (l, r); break; default: break; } break; + } case FcTypeVoid: switch ((int) op) { case FcOpEqual: @@ -1184,20 +1207,23 @@ FcConfigCompareValue (const FcValue *left_o, case FcOpEqual: case FcOpContains: case FcOpListing: - ret = left.u.f == right.u.f; + ret = left_o->u.f == right_o->u.f; break; case FcOpNotEqual: case FcOpNotContains: - ret = left.u.f != right.u.f; + ret = left_o->u.f != right_o->u.f; break; default: break; } break; - case FcTypeRange: - ret = FcRangeCompare (op, left.u.r, right.u.r); + case FcTypeRange: { + const FcRange *l = FcValueRange (left_o); + const FcRange *r = FcValueRange (right_o); + ret = FcRangeCompare (op, l, r); break; } + } return ret; } commit 9d4e5d0f25dd592316d1728f95f40fe5967b120c Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Thu Aug 20 23:45:19 2020 -0400 Speed up FcConfigCompareValue Make FcConfigPromote use a switch instead of an if-else cascade, and avoid calling it when we can. Note that we need to add a case for integers in FcConfigCompareValue, since we are no longer promoting integers to doubles unconditionally. diff --git a/src/fccfg.c b/src/fccfg.c index cb4d3e0..8e7a682 100644 --- a/src/fccfg.c +++ b/src/fccfg.c @@ -934,35 +934,45 @@ FcConfigAddRule (FcConfig *config, static FcValue FcConfigPromote (FcValue v, FcValue u, FcValuePromotionBuffer *buf) { - if (v.type == FcTypeInteger) - { - v.type = FcTypeDouble; - v.u.d = (double) v.u.i; - } - else if (v.type == FcTypeVoid && u.type == FcTypeMatrix) - { - v.u.m = &FcIdentityMatrix; - v.type = FcTypeMatrix; - } - else if (buf && v.type == FcTypeString && u.type == FcTypeLangSet) - { - v.u.l = FcLangSetPromote (v.u.s, buf); - v.type = FcTypeLangSet; - } - else if (buf && v.type == FcTypeVoid && u.type == FcTypeLangSet) - { - v.u.l = FcLangSetPromote (NULL, buf); - v.type = FcTypeLangSet; - } - else if (buf && v.type == FcTypeVoid && u.type == FcTypeCharSet) - { - v.u.c = FcCharSetPromote (buf); - v.type = FcTypeCharSet; - } - if (buf && v.type == FcTypeDouble && u.type == FcTypeRange) - { - v.u.r = FcRangePromote (v.u.d, buf); - v.type = FcTypeRange; + switch (v.type) + { + case FcTypeInteger: + v.type = FcTypeDouble; + v.u.d = (double) v.u.i; + break; + case FcTypeVoid: + if (u.type == FcTypeMatrix) + { + v.u.m = &FcIdentityMatrix; + v.type = FcTypeMatrix; + } + else if (u.type == FcTypeLangSet && buf) + { + v.u.l = FcLangSetPromote (NULL, buf); + v.type = FcTypeLangSet; + } + else if (u.type == FcTypeCharSet && buf) + { + v.u.c = FcCharSetPromote (buf); + v.type = FcTypeCharSet; + } + break; + case FcTypeString: + if (u.type == FcTypeLangSet && buf) + { + v.u.l = FcLangSetPromote (v.u.s, buf); + v.type = FcTypeLangSet; + } + break; + case FcTypeDouble: + if (u.type == FcTypeRange && buf) + { + v.u.r = FcRangePromote (v.u.d, buf); + v.type = FcTypeRange; + } + break; + default: + break; } return v; } @@ -972,195 +982,221 @@ FcConfigCompareValue (const FcValue *left_o, unsigned int op_, const FcValue *right_o) { - FcValue left = FcValueCanonicalize(left_o); - FcValue right = FcValueCanonicalize(right_o); + FcValue left = FcValueCanonicalize(left_o); + FcValue right = FcValueCanonicalize(right_o); FcBool ret = FcFalse; FcOp op = FC_OP_GET_OP (op_); int flags = FC_OP_GET_FLAGS (op_); - FcValuePromotionBuffer buf1, buf2; - left = FcConfigPromote (left, right, &buf1); - right = FcConfigPromote (right, left, &buf2); - if (left.type == right.type) - { - switch (left.type) { - case FcTypeUnknown: - break; /* No way to guess how to compare for this object */ - case FcTypeInteger: - break; /* FcConfigPromote prevents this from happening */ - case FcTypeDouble: - switch ((int) op) { - case FcOpEqual: - case FcOpContains: - case FcOpListing: - ret = left.u.d == right.u.d; - break; - case FcOpNotEqual: - case FcOpNotContains: - ret = left.u.d != right.u.d; - break; - case FcOpLess: - ret = left.u.d < right.u.d; - break; - case FcOpLessEqual: - ret = left.u.d <= right.u.d; - break; - case FcOpMore: - ret = left.u.d > right.u.d; - break; - case FcOpMoreEqual: - ret = left.u.d >= right.u.d; - break; - default: - break; - } - break; - case FcTypeBool: - switch ((int) op) { - case FcOpEqual: - ret = left.u.b == right.u.b; - break; - case FcOpContains: - case FcOpListing: - ret = left.u.b == right.u.b || left.u.b >= FcDontCare; - break; - case FcOpNotEqual: - ret = left.u.b != right.u.b; - break; - case FcOpNotContains: - ret = !(left.u.b == right.u.b || left.u.b >= FcDontCare); - break; - case FcOpLess: - ret = left.u.b != right.u.b && right.u.b >= FcDontCare; - break; - case FcOpLessEqual: - ret = left.u.b == right.u.b || right.u.b >= FcDontCare; - break; - case FcOpMore: - ret = left.u.b != right.u.b && left.u.b >= FcDontCare; - break; - case FcOpMoreEqual: - ret = left.u.b == right.u.b || left.u.b >= FcDontCare; - break; - default: - break; - } - break; - case FcTypeString: - switch ((int) op) { - case FcOpEqual: - case FcOpListing: - if (flags & FcOpFlagIgnoreBlanks) - ret = FcStrCmpIgnoreBlanksAndCase (left.u.s, right.u.s) == 0; - else - ret = FcStrCmpIgnoreCase (left.u.s, right.u.s) == 0; - break; - case FcOpContains: - ret = FcStrStrIgnoreCase (left.u.s, right.u.s) != 0; - break; - case FcOpNotEqual: - if (flags & FcOpFlagIgnoreBlanks) - ret = FcStrCmpIgnoreBlanksAndCase (left.u.s, right.u.s) != 0; - else - ret = FcStrCmpIgnoreCase (left.u.s, right.u.s) != 0; - break; - case FcOpNotContains: - ret = FcStrStrIgnoreCase (left.u.s, right.u.s) == 0; - break; - default: - break; - } - break; - case FcTypeMatrix: - switch ((int) op) { - case FcOpEqual: - case FcOpContains: - case FcOpListing: - ret = FcMatrixEqual (left.u.m, right.u.m); - break; - case FcOpNotEqual: - case FcOpNotContains: - ret = !FcMatrixEqual (left.u.m, right.u.m); - break; - default: - break; - } - break; - case FcTypeCharSet: - switch ((int) op) { - case FcOpContains: - case FcOpListing: - /* left contains right if right is a subset of left */ - ret = FcCharSetIsSubset (right.u.c, left.u.c); - break; - case FcOpNotContains: - /* left contains right if right is a subset of left */ - ret = !FcCharSetIsSubset (right.u.c, left.u.c); - break; - case FcOpEqual: - ret = FcCharSetEqual (left.u.c, right.u.c); - break; - case FcOpNotEqual: - ret = !FcCharSetEqual (left.u.c, right.u.c); - break; - default: - break; - } - break; - case FcTypeLangSet: - switch ((int) op) { - case FcOpContains: - case FcOpListing: - ret = FcLangSetContains (left.u.l, right.u.l); - break; - case FcOpNotContains: - ret = !FcLangSetContains (left.u.l, right.u.l); - break; - case FcOpEqual: - ret = FcLangSetEqual (left.u.l, right.u.l); - break; - case FcOpNotEqual: - ret = !FcLangSetEqual (left.u.l, right.u.l); - break; - default: - break; - } - break; - case FcTypeVoid: - switch ((int) op) { - case FcOpEqual: - case FcOpContains: - case FcOpListing: - ret = FcTrue; - break; - default: - break; - } - break; - case FcTypeFTFace: - switch ((int) op) { - case FcOpEqual: - case FcOpContains: - case FcOpListing: - ret = left.u.f == right.u.f; - break; - case FcOpNotEqual: - case FcOpNotContains: - ret = left.u.f != right.u.f; - break; - default: - break; - } - break; - case FcTypeRange: - ret = FcRangeCompare (op, left.u.r, right.u.r); - break; - } - } - else - { - if (op == FcOpNotEqual || op == FcOpNotContains) - ret = FcTrue; + if (left.type != right.type) + { + FcValuePromotionBuffer buf1, buf2; + left = FcConfigPromote (left, right, &buf1); + right = FcConfigPromote (right, left, &buf2); + if (left.type != right.type) + { + if (op == FcOpNotEqual || op == FcOpNotContains) + ret = FcTrue; + return ret; + } + } + switch (left.type) { + case FcTypeUnknown: + break; /* No way to guess how to compare for this object */ + case FcTypeInteger: + switch ((int) op) { + case FcOpEqual: + case FcOpContains: + case FcOpListing: + ret = left.u.i == right.u.i; + break; + case FcOpNotEqual: + case FcOpNotContains: + ret = left.u.i != right.u.i; + break; + case FcOpLess: + ret = left.u.i < right.u.i; + break; + case FcOpLessEqual: + ret = left.u.i <= right.u.i; + break; + case FcOpMore: + ret = left.u.i > right.u.i; + break; + case FcOpMoreEqual: + ret = left.u.i >= right.u.i; + break; + default: + break; + } + break; + case FcTypeDouble: + switch ((int) op) { + case FcOpEqual: + case FcOpContains: + case FcOpListing: + ret = left.u.d == right.u.d; + break; + case FcOpNotEqual: + case FcOpNotContains: + ret = left.u.d != right.u.d; + break; + case FcOpLess: + ret = left.u.d < right.u.d; + break; + case FcOpLessEqual: + ret = left.u.d <= right.u.d; + break; + case FcOpMore: + ret = left.u.d > right.u.d; + break; + case FcOpMoreEqual: + ret = left.u.d >= right.u.d; + break; + default: + break; + } + break; + case FcTypeBool: + switch ((int) op) { + case FcOpEqual: + ret = left.u.b == right.u.b; + break; + case FcOpContains: + case FcOpListing: + ret = left.u.b == right.u.b || left.u.b >= FcDontCare; + break; + case FcOpNotEqual: + ret = left.u.b != right.u.b; + break; + case FcOpNotContains: + ret = !(left.u.b == right.u.b || left.u.b >= FcDontCare); + break; + case FcOpLess: + ret = left.u.b != right.u.b && right.u.b >= FcDontCare; + break; + case FcOpLessEqual: + ret = left.u.b == right.u.b || right.u.b >= FcDontCare; + break; + case FcOpMore: + ret = left.u.b != right.u.b && left.u.b >= FcDontCare; + break; + case FcOpMoreEqual: + ret = left.u.b == right.u.b || left.u.b >= FcDontCare; + break; + default: + break; + } + break; + case FcTypeString: + switch ((int) op) { + case FcOpEqual: + case FcOpListing: + if (flags & FcOpFlagIgnoreBlanks) + ret = FcStrCmpIgnoreBlanksAndCase (left.u.s, right.u.s) == 0; + else + ret = FcStrCmpIgnoreCase (left.u.s, right.u.s) == 0; + break; + case FcOpContains: + ret = FcStrStrIgnoreCase (left.u.s, right.u.s) != 0; + break; + case FcOpNotEqual: + if (flags & FcOpFlagIgnoreBlanks) + ret = FcStrCmpIgnoreBlanksAndCase (left.u.s, right.u.s) != 0; + else + ret = FcStrCmpIgnoreCase (left.u.s, right.u.s) != 0; + break; + case FcOpNotContains: + ret = FcStrStrIgnoreCase (left.u.s, right.u.s) == 0; + break; + default: + break; + } + break; + case FcTypeMatrix: + switch ((int) op) { + case FcOpEqual: + case FcOpContains: + case FcOpListing: + ret = FcMatrixEqual (left.u.m, right.u.m); + break; + case FcOpNotEqual: + case FcOpNotContains: + ret = !FcMatrixEqual (left.u.m, right.u.m); + break; + default: + break; + } + break; + case FcTypeCharSet: + switch ((int) op) { + case FcOpContains: + case FcOpListing: + /* left contains right if right is a subset of left */ + ret = FcCharSetIsSubset (right.u.c, left.u.c); + break; + case FcOpNotContains: + /* left contains right if right is a subset of left */ + ret = !FcCharSetIsSubset (right.u.c, left.u.c); + break; + case FcOpEqual: + ret = FcCharSetEqual (left.u.c, right.u.c); + break; + case FcOpNotEqual: + ret = !FcCharSetEqual (left.u.c, right.u.c); + break; + default: + break; + } + break; + case FcTypeLangSet: + switch ((int) op) { + case FcOpContains: + case FcOpListing: + ret = FcLangSetContains (left.u.l, right.u.l); + break; + case FcOpNotContains: + ret = !FcLangSetContains (left.u.l, right.u.l); + break; + case FcOpEqual: + ret = FcLangSetEqual (left.u.l, right.u.l); + break; + case FcOpNotEqual: + ret = !FcLangSetEqual (left.u.l, right.u.l); + break; + default: + break; + } + break; + case FcTypeVoid: + switch ((int) op) { + case FcOpEqual: + case FcOpContains: + case FcOpListing: + ret = FcTrue; + break; + default: + break; + } + break; + case FcTypeFTFace: + switch ((int) op) { + case FcOpEqual: + case FcOpContains: + case FcOpListing: + ret = left.u.f == right.u.f; + break; + case FcOpNotEqual: + case FcOpNotContains: + ret = left.u.f != right.u.f; + break; + default: + break; + } + break; + case FcTypeRange: + ret = FcRangeCompare (op, left.u.r, right.u.r); + break; } return ret; } commit 922168afe8bf48e872dc0a808fadefaa798a42ce Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Fri Aug 21 08:19:13 2020 -0400 Speed up fonthashint matching When we don't need to differentiate between weak and strong, we can exit the loop in FcCompareValueList once we found a best match. This change helps reducing the amount of list walking we do for fonthashint, where careless config files end up creating lists with ~100 booleans :( We don't want to walk all those to the end, over and over again. We are already special-casing family, and the only other case where weak != strong, PostScript names, doesn't have long lists of values, so the limitation to weak == strong doesn't matter much in practice. diff --git a/src/fcmatch.c b/src/fcmatch.c index 969b855..289204e 100644 --- a/src/fcmatch.c +++ b/src/fcmatch.c @@ -408,6 +408,7 @@ FcCompareValueList (FcObject object, FcValueListPtr v1, v2; double v, best, bestStrong, bestWeak; int j, k, pos = 0; + int weak, strong; if (!match) { @@ -418,11 +419,13 @@ FcCompareValueList (FcObject object, return FcTrue; } + weak = match->weak; + strong = match->strong; + best = 1e99; bestStrong = 1e99; bestWeak = 1e99; - j = 0; - for (v1 = v1orig; v1; v1 = FcValueListNext(v1)) + for (v1 = v1orig, j = 0; v1; v1 = FcValueListNext(v1), j++) { for (v2 = v2orig, k = 0; v2; v2 = FcValueListNext(v2), k++) { @@ -441,7 +444,13 @@ FcCompareValueList (FcObject object, best = v; pos = k; } - if (v1->binding == FcValueBindingStrong) + if (weak == strong) + { + /* found the best possible match */ + if (best < 1000) + goto done; + } + else if (v1->binding == FcValueBindingStrong) { if (v < bestStrong) bestStrong = v; @@ -452,8 +461,8 @@ FcCompareValueList (FcObject object, bestWeak = v; } } - j++; } +done: if (FcDebug () & FC_DBG_MATCHV) { printf (" %s: %g ", FcObjectName (object), best); @@ -464,8 +473,6 @@ FcCompareValueList (FcObject object, } if (value) { - int weak = match->weak; - int strong = match->strong; if (weak == strong) value[strong] += best; else commit 1b0cb23adcdf0c2b136b41d805b157f0c6d75ad4 Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Tue Aug 18 15:02:21 2020 -0400 Add a shortcut for FcQualAny matching When checking whether a test matches a pattern, we cut the loop short for FcQualAll when we see the first non-matching value, but for FcQualAny we were always walking the full list. This patch cuts the loop short for FcQualAny when we see the first matching value. diff --git a/src/fccfg.c b/src/fccfg.c index 74e8746..cb4d3e0 100644 --- a/src/fccfg.c +++ b/src/fccfg.c @@ -1550,6 +1550,8 @@ FcConfigMatchValueList (FcPattern *p, { if (!ret) ret = v; + if (t->qual != FcQualAll) + break; } else { commit 8022ab4aff469a8f095ce3168d879d3e0b3605ef Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Tue Aug 18 10:54:43 2020 -0400 Use a hash table for family matching With the way typical font configurations look, matching the lists of families is the bottleneck for both FcFontMatch and FcFontSort. After installing the Noto fonts on my system, an innocent match pattern like "Cantarell 14" turns into a monster with a list of 300 family names after calling FcConfigSubstitute(). With this setup, every FcFontSort call takes 80-100 ms, which is entirely incompatible with using FcFontSort for anything interactive. And many font choosers render every font in itself, causing on average one FcFontSort call per font. On my system, it takes more than 20 seconds to open the GTK font chooser dialog, with frequent stalls when scrolling. This patch special-cases font families and replaces the list walking for comparison with a hash table lookup. With this patch, the FcFontSort time goes to ~10ms per call. Which is still not good enough for calling it dozens of times when scrolling, but a significant improvement. diff --git a/src/fcmatch.c b/src/fcmatch.c index e370e8b..969b855 100644 --- a/src/fcmatch.c +++ b/src/fcmatch.c @@ -480,6 +480,109 @@ FcCompareValueList (FcObject object, return FcTrue; } +/* The bulk of the time in FcFontMatch and FcFontSort goes to + * walking long lists of family names. We speed this up with a + * hash table. + */ +typedef struct +{ + double strong_value; + double weak_value; +} FamilyEntry; + +typedef struct +{ + FcHashTable *family_hash; +} FcCompareData; + +static void +FcCompareDataClear (FcCompareData *data) +{ + FcHashTableDestroy (data->family_hash); +} + +static void +FcCompareDataInit (FcPattern *pat, + FcCompareData *data) +{ + FcHashTable *table; + FcPatternElt *elt; + FcValueListPtr l; + int i; + const void *key; + FamilyEntry *e; + + table = FcHashTableCreate ((FcHashFunc)FcStrHashIgnoreBlanksAndCase, + (FcCompareFunc)FcStrCmpIgnoreBlanksAndCase, + NULL, + NULL, + NULL, + free); + + elt = FcPatternObjectFindElt (pat, FC_FAMILY_OBJECT); + for (l = FcPatternEltValues(elt), i = 0; l; l = FcValueListNext(l), i++) + { + key = FcValueString (&l->value); + if (!FcHashTableFind (table, key, (void **)&e)) + { + e = malloc (sizeof (FamilyEntry)); + e->strong_value = 1e99; + e->weak_value = 1e99; + FcHashTableAdd (table, (void *)key, e); + } + if (l->binding == FcValueBindingWeak) + { + if (i < e->weak_value) + e->weak_value = i; + } + else + { + if (i < e->strong_value) + e->strong_value = i; + } + } + + data->family_hash = table; +} + +static FcBool +FcCompareFamilies (FcPattern *pat, + FcValueListPtr v1orig, + FcPattern *fnt, + FcValueListPtr v2orig, + double *value, + FcResult *result, + FcHashTable *table) +{ + FcValueListPtr v2; + double strong_value; + double weak_value; + const void *key; + FamilyEntry *e; + + assert (table != NULL); + + strong_value = 1e99; + weak_value = 1e99; + + for (v2 = v2orig; v2; v2 = FcValueListNext(v2)) + { + key = FcValueString (&v2->value); + if (FcHashTableFind (table, key, (void **)&e)) + { + if (e->strong_value < strong_value) + strong_value = e->strong_value; + if (e->weak_value < weak_value) + weak_value = e->weak_value; + } + } + + value[PRI_FAMILY_STRONG] = strong_value; + value[PRI_FAMILY_WEAK] = weak_value; + + return FcTrue; +} + /* * Return a value indicating the distance between the two lists of * values @@ -489,7 +592,8 @@ static FcBool FcCompare (FcPattern *pat, FcPattern *fnt, double *value, - FcResult *result) + FcResult *result, + FcCompareData *data) { int i, i1, i2; @@ -508,8 +612,18 @@ FcCompare (FcPattern *pat, i2++; else if (i < 0) i1++; - else - { + else if (elt_i1->object == FC_FAMILY_OBJECT && data->family_hash) + { + if (!FcCompareFamilies (pat, FcPatternEltValues(elt_i1), + fnt, FcPatternEltValues(elt_i2), + value, result, + data->family_hash)) + return FcFalse; + i1++; + i2++; + } + else + { const FcMatcher *match = FcObjectToMatcher (elt_i1->object, FcFalse); if (!FcCompareValueList (elt_i1->object, match, FcPatternEltValues(elt_i1), @@ -734,6 +848,7 @@ FcFontSetMatchInternal (FcFontSet **sets, FcPattern *best; int i; int set; + FcCompareData data; for (i = 0; i < PRI_END; i++) bestscore[i] = 0; @@ -743,6 +858,9 @@ FcFontSetMatchInternal (FcFontSet **sets, printf ("Match "); FcPatternPrint (p); } + + FcCompareDataInit (p, &data); + for (set = 0; set < nsets; set++) { s = sets[set]; @@ -755,8 +873,11 @@ FcFontSetMatchInternal (FcFontSet **sets, printf ("Font %d ", f); FcPatternPrint (s->fonts[f]); } - if (!FcCompare (p, s->fonts[f], score, result)) + if (!FcCompare (p, s->fonts[f], score, result, &data)) + { + FcCompareDataClear (&data); return 0; + } if (FcDebug () & FC_DBG_MATCHV) { printf ("Score"); @@ -780,6 +901,9 @@ FcFontSetMatchInternal (FcFontSet **sets, } } } + + FcCompareDataClear (&data); + if (FcDebug () & FC_DBG_MATCH) { printf ("Best score"); @@ -1015,6 +1139,7 @@ FcFontSetSort (FcConfig *config FC_UNUSED, int nPatternLang; FcBool *patternLangSat; FcValue patternLang; + FcCompareData data; assert (sets != NULL); assert (p != NULL); @@ -1059,6 +1184,8 @@ FcFontSetSort (FcConfig *config FC_UNUSED, nodeps = (FcSortNode **) (nodes + nnodes); patternLangSat = (FcBool *) (nodeps + nnodes); + FcCompareDataInit (p, &data); + new = nodes; nodep = nodeps; for (set = 0; set < nsets; set++) @@ -1074,7 +1201,7 @@ FcFontSetSort (FcConfig *config FC_UNUSED, FcPatternPrint (s->fonts[f]); } new->pattern = s->fonts[f]; - if (!FcCompare (p, new->pattern, new->score, result)) + if (!FcCompare (p, new->pattern, new->score, result, &data)) goto bail1; if (FcDebug () & FC_DBG_MATCHV) { @@ -1091,6 +1218,8 @@ FcFontSetSort (FcConfig *config FC_UNUSED, } } + FcCompareDataClear (&data); + nnodes = new - nodes; qsort (nodeps, nnodes, sizeof (FcSortNode *), commit 055843631b995f8acc778c94032c949844e8812b Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Fri Aug 21 08:05:18 2020 -0400 Add a hash function for families Add a hash function that behaves like family comparison: ignoring case and blanks. This will be used to replace the list walking for finding family matches with a hash table. diff --git a/src/fcint.h b/src/fcint.h index 88e0701..08a0cd6 100644 --- a/src/fcint.h +++ b/src/fcint.h @@ -1328,6 +1328,9 @@ FcStrLastSlash (const FcChar8 *path); FcPrivate FcChar32 FcStrHashIgnoreCase (const FcChar8 *s); +FcPrivate FcChar32 +FcStrHashIgnoreBlanksAndCase (const FcChar8 *s); + FcPrivate FcChar8 * FcStrCanonFilename (const FcChar8 *s); diff --git a/src/fcstr.c b/src/fcstr.c index 39ecbbb..dc9940a 100644 --- a/src/fcstr.c +++ b/src/fcstr.c @@ -320,6 +320,19 @@ FcStrHashIgnoreCase (const FcChar8 *s) return h; } +FcChar32 +FcStrHashIgnoreBlanksAndCase (const FcChar8 *s) +{ + FcChar32 h = 0; + FcCaseWalker w; + FcChar8 c; + + FcStrCaseWalkerInit (s, &w); + while ((c = FcStrCaseWalkerNextNonBlank (&w))) + h = ((h << 3) ^ (h >> 3)) ^ c; + return h; +} + /* * Is the head of s1 equal to s2? */ commit 46d818df26f3585f9261c735129a606837406875 Author: Matthias Clasen <mclasen@xxxxxxxxxx> Date: Fri Aug 21 00:47:09 2020 -0400 Special-case some of the string walking code Make variants of FcStrCaseWalkerNext for the two common cases, delim == NULL and delim == " ", to speed things up. These are inner loops, and having the conditions as simple as possible helps. diff --git a/src/fcstr.c b/src/fcstr.c index 864d4aa..39ecbbb 100644 --- a/src/fcstr.c +++ b/src/fcstr.c @@ -160,7 +160,7 @@ FcStrCaseWalkerLong (FcCaseWalker *w, FcChar8 r) } static FcChar8 -FcStrCaseWalkerNext (FcCaseWalker *w, const char *delims) +FcStrCaseWalkerNextNonDelim (FcCaseWalker *w, const char *delims) { FcChar8 r; @@ -182,6 +182,50 @@ FcStrCaseWalkerNext (FcCaseWalker *w, const char *delims) return r; } +static FcChar8 +FcStrCaseWalkerNextNonBlank (FcCaseWalker *w) +{ + FcChar8 r; + + if (w->read) + { + if ((r = *w->read++)) + return r; + w->read = 0; + } + do + { + r = *w->src++; + } while (r == ' '); + + if ((r & 0xc0) == 0xc0) + return FcStrCaseWalkerLong (w, r); + if ('A' <= r && r <= 'Z') + r = r - 'A' + 'a'; + return r; +} + +static FcChar8 +FcStrCaseWalkerNext (FcCaseWalker *w) +{ + FcChar8 r; + + if (w->read) + { + if ((r = *w->read++)) + return r; + w->read = 0; + } + + r = *w->src++; + + if ((r & 0xc0) == 0xc0) + return FcStrCaseWalkerLong (w, r); + if ('A' <= r && r <= 'Z') + r = r - 'A' + 'a'; + return r; +} + FcChar8 * FcStrDowncase (const FcChar8 *s) { @@ -190,13 +234,13 @@ FcStrDowncase (const FcChar8 *s) FcChar8 *dst, *d; FcStrCaseWalkerInit (s, &w); - while (FcStrCaseWalkerNext (&w, NULL)) + while (FcStrCaseWalkerNext (&w)) len++; d = dst = malloc (len + 1); if (!d) return 0; FcStrCaseWalkerInit (s, &w); - while ((*d++ = FcStrCaseWalkerNext (&w, NULL))); + while ((*d++ = FcStrCaseWalkerNext (&w))); return dst; } @@ -213,8 +257,8 @@ FcStrCmpIgnoreCase (const FcChar8 *s1, const FcChar8 *s2) for (;;) { - c1 = FcStrCaseWalkerNext (&w1, NULL); - c2 = FcStrCaseWalkerNext (&w2, NULL); + c1 = FcStrCaseWalkerNext (&w1); + c2 = FcStrCaseWalkerNext (&w2); if (!c1 || (c1 != c2)) break; } @@ -223,12 +267,6 @@ FcStrCmpIgnoreCase (const FcChar8 *s1, const FcChar8 *s2) int FcStrCmpIgnoreBlanksAndCase (const FcChar8 *s1, const FcChar8 *s2) -{ - return FcStrCmpIgnoreCaseAndDelims (s1, s2, (const FcChar8 *)" "); -} - -int -FcStrCmpIgnoreCaseAndDelims (const FcChar8 *s1, const FcChar8 *s2, const FcChar8 *delims) { FcCaseWalker w1, w2; FcChar8 c1, c2; @@ -240,8 +278,8 @@ FcStrCmpIgnoreCaseAndDelims (const FcChar8 *s1, const FcChar8 *s2, const FcChar8 for (;;) { - c1 = FcStrCaseWalkerNext (&w1, (const char *)delims); - c2 = FcStrCaseWalkerNext (&w2, (const char *)delims); + c1 = FcStrCaseWalkerNextNonBlank (&w1); + c2 = FcStrCaseWalkerNextNonBlank (&w2); if (!c1 || (c1 != c2)) break; } @@ -277,7 +315,7 @@ FcStrHashIgnoreCase (const FcChar8 *s) FcChar8 c; FcStrCaseWalkerInit (s, &w); - while ((c = FcStrCaseWalkerNext (&w, NULL))) + while ((c = FcStrCaseWalkerNext (&w))) h = ((h << 3) ^ (h >> 3)) ^ c; return h; } @@ -297,8 +335,8 @@ FcStrIsAtIgnoreBlanksAndCase (const FcChar8 *s1, const FcChar8 *s2) for (;;) { - c1 = FcStrCaseWalkerNext (&w1, " "); - c2 = FcStrCaseWalkerNext (&w2, " "); + c1 = FcStrCaseWalkerNextNonBlank (&w1); + c2 = FcStrCaseWalkerNextNonBlank (&w2); if (!c1 || (c1 != c2)) break; } @@ -356,8 +394,8 @@ FcStrIsAtIgnoreCase (const FcChar8 *s1, const FcChar8 *s2) for (;;) { - c1 = FcStrCaseWalkerNext (&w1, NULL); - c2 = FcStrCaseWalkerNext (&w2, NULL); + c1 = FcStrCaseWalkerNext (&w1); + c2 = FcStrCaseWalkerNext (&w2); if (!c1 || (c1 != c2)) break; } @@ -425,8 +463,8 @@ FcStrMatchIgnoreCaseAndDelims (const FcChar8 *s1, const FcChar8 *s2, const FcCha for (;;) { - c1 = FcStrCaseWalkerNext (&w1, (const char *)delims); - c2 = FcStrCaseWalkerNext (&w2, (const char *)delims); + c1 = FcStrCaseWalkerNextNonDelim (&w1, (const char *)delims); + c2 = FcStrCaseWalkerNextNonDelim (&w2, (const char *)delims); if (!c1 || (c1 != c2)) break; } @@ -493,12 +531,12 @@ FcStrStrIgnoreCase (const FcChar8 *s1, const FcChar8 *s2) FcStrCaseWalkerInit (s1, &w1); FcStrCaseWalkerInit (s2, &w2); - c2 = FcStrCaseWalkerNext (&w2, NULL); + c2 = FcStrCaseWalkerNext (&w2); for (;;) { cur = w1.src; - c1 = FcStrCaseWalkerNext (&w1, NULL); + c1 = FcStrCaseWalkerNext (&w1); if (!c1) break; if (c1 == c2) @@ -509,8 +547,8 @@ FcStrStrIgnoreCase (const FcChar8 *s1, const FcChar8 *s2) for (;;) { - c1t = FcStrCaseWalkerNext (&w1t, NULL); - c2t = FcStrCaseWalkerNext (&w2t, NULL); + c1t = FcStrCaseWalkerNext (&w1t); + c2t = FcStrCaseWalkerNext (&w2t); if (!c2t) return cur; _______________________________________________ Fontconfig mailing list Fontconfig@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/fontconfig