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.
This commit is contained in:
parent
055843631b
commit
8022ab4aff
135
src/fcmatch.c
135
src/fcmatch.c
|
@ -480,6 +480,109 @@ FcCompareValueList (FcObject object,
|
||||||
return FcTrue;
|
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
|
* Return a value indicating the distance between the two lists of
|
||||||
* values
|
* values
|
||||||
|
@ -489,7 +592,8 @@ static FcBool
|
||||||
FcCompare (FcPattern *pat,
|
FcCompare (FcPattern *pat,
|
||||||
FcPattern *fnt,
|
FcPattern *fnt,
|
||||||
double *value,
|
double *value,
|
||||||
FcResult *result)
|
FcResult *result,
|
||||||
|
FcCompareData *data)
|
||||||
{
|
{
|
||||||
int i, i1, i2;
|
int i, i1, i2;
|
||||||
|
|
||||||
|
@ -508,6 +612,16 @@ FcCompare (FcPattern *pat,
|
||||||
i2++;
|
i2++;
|
||||||
else if (i < 0)
|
else if (i < 0)
|
||||||
i1++;
|
i1++;
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
const FcMatcher *match = FcObjectToMatcher (elt_i1->object, FcFalse);
|
const FcMatcher *match = FcObjectToMatcher (elt_i1->object, FcFalse);
|
||||||
|
@ -734,6 +848,7 @@ FcFontSetMatchInternal (FcFontSet **sets,
|
||||||
FcPattern *best;
|
FcPattern *best;
|
||||||
int i;
|
int i;
|
||||||
int set;
|
int set;
|
||||||
|
FcCompareData data;
|
||||||
|
|
||||||
for (i = 0; i < PRI_END; i++)
|
for (i = 0; i < PRI_END; i++)
|
||||||
bestscore[i] = 0;
|
bestscore[i] = 0;
|
||||||
|
@ -743,6 +858,9 @@ FcFontSetMatchInternal (FcFontSet **sets,
|
||||||
printf ("Match ");
|
printf ("Match ");
|
||||||
FcPatternPrint (p);
|
FcPatternPrint (p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FcCompareDataInit (p, &data);
|
||||||
|
|
||||||
for (set = 0; set < nsets; set++)
|
for (set = 0; set < nsets; set++)
|
||||||
{
|
{
|
||||||
s = sets[set];
|
s = sets[set];
|
||||||
|
@ -755,8 +873,11 @@ FcFontSetMatchInternal (FcFontSet **sets,
|
||||||
printf ("Font %d ", f);
|
printf ("Font %d ", f);
|
||||||
FcPatternPrint (s->fonts[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;
|
return 0;
|
||||||
|
}
|
||||||
if (FcDebug () & FC_DBG_MATCHV)
|
if (FcDebug () & FC_DBG_MATCHV)
|
||||||
{
|
{
|
||||||
printf ("Score");
|
printf ("Score");
|
||||||
|
@ -780,6 +901,9 @@ FcFontSetMatchInternal (FcFontSet **sets,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FcCompareDataClear (&data);
|
||||||
|
|
||||||
if (FcDebug () & FC_DBG_MATCH)
|
if (FcDebug () & FC_DBG_MATCH)
|
||||||
{
|
{
|
||||||
printf ("Best score");
|
printf ("Best score");
|
||||||
|
@ -1015,6 +1139,7 @@ FcFontSetSort (FcConfig *config FC_UNUSED,
|
||||||
int nPatternLang;
|
int nPatternLang;
|
||||||
FcBool *patternLangSat;
|
FcBool *patternLangSat;
|
||||||
FcValue patternLang;
|
FcValue patternLang;
|
||||||
|
FcCompareData data;
|
||||||
|
|
||||||
assert (sets != NULL);
|
assert (sets != NULL);
|
||||||
assert (p != NULL);
|
assert (p != NULL);
|
||||||
|
@ -1059,6 +1184,8 @@ FcFontSetSort (FcConfig *config FC_UNUSED,
|
||||||
nodeps = (FcSortNode **) (nodes + nnodes);
|
nodeps = (FcSortNode **) (nodes + nnodes);
|
||||||
patternLangSat = (FcBool *) (nodeps + nnodes);
|
patternLangSat = (FcBool *) (nodeps + nnodes);
|
||||||
|
|
||||||
|
FcCompareDataInit (p, &data);
|
||||||
|
|
||||||
new = nodes;
|
new = nodes;
|
||||||
nodep = nodeps;
|
nodep = nodeps;
|
||||||
for (set = 0; set < nsets; set++)
|
for (set = 0; set < nsets; set++)
|
||||||
|
@ -1074,7 +1201,7 @@ FcFontSetSort (FcConfig *config FC_UNUSED,
|
||||||
FcPatternPrint (s->fonts[f]);
|
FcPatternPrint (s->fonts[f]);
|
||||||
}
|
}
|
||||||
new->pattern = 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;
|
goto bail1;
|
||||||
if (FcDebug () & FC_DBG_MATCHV)
|
if (FcDebug () & FC_DBG_MATCHV)
|
||||||
{
|
{
|
||||||
|
@ -1091,6 +1218,8 @@ FcFontSetSort (FcConfig *config FC_UNUSED,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FcCompareDataClear (&data);
|
||||||
|
|
||||||
nnodes = new - nodes;
|
nnodes = new - nodes;
|
||||||
|
|
||||||
qsort (nodeps, nnodes, sizeof (FcSortNode *),
|
qsort (nodeps, nnodes, sizeof (FcSortNode *),
|
||||||
|
|
Loading…
Reference in New Issue