fclist.c revision c9710b42
1/*
2 * fontconfig/src/fclist.c
3 *
4 * Copyright © 2000 Keith Packard
5 *
6 * Permission to use, copy, modify, distribute, and sell this software and its
7 * documentation for any purpose is hereby granted without fee, provided that
8 * the above copyright notice appear in all copies and that both that
9 * copyright notice and this permission notice appear in supporting
10 * documentation, and that the name of the author(s) not be used in
11 * advertising or publicity pertaining to distribution of the software without
12 * specific, written prior permission.  The authors make no
13 * representations about the suitability of this software for any purpose.  It
14 * is provided "as is" without express or implied warranty.
15 *
16 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
18 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
19 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
20 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
21 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
22 * PERFORMANCE OF THIS SOFTWARE.
23 */
24
25#include "fcint.h"
26#include <stdlib.h>
27
28FcObjectSet *
29FcObjectSetCreate (void)
30{
31    FcObjectSet    *os;
32
33    os = (FcObjectSet *) malloc (sizeof (FcObjectSet));
34    if (!os)
35	return 0;
36    os->nobject = 0;
37    os->sobject = 0;
38    os->objects = 0;
39    return os;
40}
41
42FcBool
43FcObjectSetAdd (FcObjectSet *os, const char *object)
44{
45    int		s;
46    const char	**objects;
47    int		high, low, mid, c;
48
49    if (os->nobject == os->sobject)
50    {
51	s = os->sobject + 4;
52	if (os->objects)
53	    objects = (const char **) realloc ((void *) os->objects,
54					       s * sizeof (const char *));
55	else
56	    objects = (const char **) malloc (s * sizeof (const char *));
57	if (!objects)
58	    return FcFalse;
59	os->objects = objects;
60	os->sobject = s;
61    }
62    high = os->nobject - 1;
63    low = 0;
64    mid = 0;
65    c = 1;
66    object = strdup (object);
67    while (low <= high)
68    {
69	mid = (low + high) >> 1;
70	c = os->objects[mid] - object;
71	if (c == 0)
72	{
73	    FcFree (object);
74	    return FcTrue;
75	}
76	if (c < 0)
77	    low = mid + 1;
78	else
79	    high = mid - 1;
80    }
81    if (c < 0)
82	mid++;
83    memmove (os->objects + mid + 1, os->objects + mid,
84	     (os->nobject - mid) * sizeof (const char *));
85    os->objects[mid] = object;
86    os->nobject++;
87    return FcTrue;
88}
89
90void
91FcObjectSetDestroy (FcObjectSet *os)
92{
93    int i;
94
95    if (os->objects)
96    {
97	for (i = 0; i < os->nobject; i++)
98	    FcFree (os->objects[i]);
99
100	free ((void *) os->objects);
101    }
102    free (os);
103}
104
105FcObjectSet *
106FcObjectSetVaBuild (const char *first, va_list va)
107{
108    FcObjectSet    *ret;
109
110    FcObjectSetVapBuild (ret, first, va);
111    return ret;
112}
113
114FcObjectSet *
115FcObjectSetBuild (const char *first, ...)
116{
117    va_list	    va;
118    FcObjectSet    *os;
119
120    va_start (va, first);
121    FcObjectSetVapBuild (os, first, va);
122    va_end (va);
123    return os;
124}
125
126/*
127 * Font must have a containing value for every value in the pattern
128 */
129static FcBool
130FcListValueListMatchAny (FcValueListPtr patOrig,	    /* pattern */
131			 FcValueListPtr fntOrig)	    /* font */
132{
133    FcValueListPtr	 pat, fnt;
134
135    for (pat = patOrig; pat != NULL; pat = FcValueListNext(pat))
136    {
137	for (fnt = fntOrig; fnt != NULL; fnt = FcValueListNext(fnt))
138	{
139	    /*
140	     * make sure the font 'contains' the pattern.
141	     * (OpListing is OpContains except for strings
142	     *  where it requires an exact match)
143	     */
144	    if (FcConfigCompareValue (&fnt->value,
145				      FC_OP (FcOpListing, FcOpFlagIgnoreBlanks),
146				      &pat->value))
147		break;
148	}
149	if (fnt == NULL)
150	    return FcFalse;
151    }
152    return FcTrue;
153}
154
155static FcBool
156FcListValueListEqual (FcValueListPtr v1orig,
157		      FcValueListPtr v2orig)
158{
159    FcValueListPtr	    v1, v2;
160
161    for (v1 = v1orig; v1 != NULL; v1 = FcValueListNext(v1))
162    {
163	for (v2 = v2orig; v2 != NULL; v2 = FcValueListNext(v2))
164	    if (FcValueEqual (FcValueCanonicalize(&(v1)->value),
165			      FcValueCanonicalize(&(v2)->value)))
166		break;
167	if (v2 == NULL)
168	    return FcFalse;
169    }
170    for (v2 = v2orig; v2 != NULL; v2 = FcValueListNext(v2))
171    {
172	for (v1 = v1orig; v1 != NULL; v1 = FcValueListNext(v1))
173	    if (FcValueEqual (FcValueCanonicalize(&v1->value),
174			      FcValueCanonicalize(&v2->value)))
175		break;
176	if (v1 == NULL)
177	    return FcFalse;
178    }
179    return FcTrue;
180}
181
182static FcBool
183FcListPatternEqual (FcPattern	*p1,
184		    FcPattern	*p2,
185		    FcObjectSet	*os)
186{
187    int		    i;
188    FcPatternElt    *e1, *e2;
189
190    for (i = 0; i < os->nobject; i++)
191    {
192	e1 = FcPatternObjectFindElt (p1, FcObjectFromName (os->objects[i]));
193	e2 = FcPatternObjectFindElt (p2, FcObjectFromName (os->objects[i]));
194	if (!e1 && !e2)
195	    continue;
196	if (!e1 || !e2)
197	    return FcFalse;
198	if (!FcListValueListEqual (FcPatternEltValues(e1),
199				   FcPatternEltValues(e2)))
200	    return FcFalse;
201    }
202    return FcTrue;
203}
204
205/*
206 * FcTrue iff all objects in "p" match "font"
207 */
208
209FcBool
210FcListPatternMatchAny (const FcPattern *p,
211		       const FcPattern *font)
212{
213    int		    i;
214
215    for (i = 0; i < p->num; i++)
216    {
217	FcPatternElt	*pe = &FcPatternElts(p)[i];
218	FcPatternElt	*fe;
219
220	if (pe->object == FC_NAMELANG_OBJECT)
221	{
222	    /* "namelang" object is the alias object to change "familylang",
223	     * "stylelang" and "fullnamelang" object alltogether. it won't be
224	     * available on the font pattern. so checking its availability
225	     * causes no results. we should ignore it here.
226	     */
227	    continue;
228	}
229	fe = FcPatternObjectFindElt (font, pe->object);
230	if (!fe)
231	    return FcFalse;
232	if (!FcListValueListMatchAny (FcPatternEltValues(pe),    /* pat elts */
233				      FcPatternEltValues(fe)))   /* font elts */
234	    return FcFalse;
235    }
236    return FcTrue;
237}
238
239static FcChar32
240FcListMatrixHash (const FcMatrix *m)
241{
242    int	    xx = (int) (m->xx * 100),
243	    xy = (int) (m->xy * 100),
244	    yx = (int) (m->yx * 100),
245	    yy = (int) (m->yy * 100);
246
247    return ((FcChar32) xx) ^ ((FcChar32) xy) ^ ((FcChar32) yx) ^ ((FcChar32) yy);
248}
249
250static FcChar32
251FcListValueHash (FcValue    *value)
252{
253    FcValue v = FcValueCanonicalize(value);
254    switch (v.type) {
255    case FcTypeVoid:
256	return 0;
257    case FcTypeInteger:
258	return (FcChar32) v.u.i;
259    case FcTypeDouble:
260	return (FcChar32) (int) v.u.d;
261    case FcTypeString:
262	return FcStrHashIgnoreCase (v.u.s);
263    case FcTypeBool:
264	return (FcChar32) v.u.b;
265    case FcTypeMatrix:
266	return FcListMatrixHash (v.u.m);
267    case FcTypeCharSet:
268	return FcCharSetCount (v.u.c);
269    case FcTypeFTFace:
270	return (long) v.u.f;
271    case FcTypeLangSet:
272	return FcLangSetHash (v.u.l);
273    }
274    return 0;
275}
276
277static FcChar32
278FcListValueListHash (FcValueListPtr list)
279{
280    FcChar32	h = 0;
281
282    while (list != NULL)
283    {
284	h = h ^ FcListValueHash (&list->value);
285	list = FcValueListNext(list);
286    }
287    return h;
288}
289
290static FcChar32
291FcListPatternHash (FcPattern	*font,
292		   FcObjectSet	*os)
293{
294    int		    n;
295    FcPatternElt    *e;
296    FcChar32	    h = 0;
297
298    for (n = 0; n < os->nobject; n++)
299    {
300	e = FcPatternObjectFindElt (font, FcObjectFromName (os->objects[n]));
301	if (e)
302	    h = h ^ FcListValueListHash (FcPatternEltValues(e));
303    }
304    return h;
305}
306
307typedef struct _FcListBucket {
308    struct _FcListBucket    *next;
309    FcChar32		    hash;
310    FcPattern		    *pattern;
311} FcListBucket;
312
313#define FC_LIST_HASH_SIZE   4099
314
315typedef struct _FcListHashTable {
316    int		    entries;
317    FcListBucket    *buckets[FC_LIST_HASH_SIZE];
318} FcListHashTable;
319
320static void
321FcListHashTableInit (FcListHashTable *table)
322{
323    table->entries = 0;
324    memset (table->buckets, '\0', sizeof (table->buckets));
325}
326
327static void
328FcListHashTableCleanup (FcListHashTable *table)
329{
330    int	i;
331    FcListBucket    *bucket, *next;
332
333    for (i = 0; i < FC_LIST_HASH_SIZE; i++)
334    {
335	for (bucket = table->buckets[i]; bucket; bucket = next)
336	{
337	    next = bucket->next;
338	    FcPatternDestroy (bucket->pattern);
339	    free (bucket);
340	}
341	table->buckets[i] = 0;
342    }
343    table->entries = 0;
344}
345
346static int
347FcGetDefaultObjectLangIndex (FcPattern *font, FcObject object, const FcChar8 *lang)
348{
349    FcPatternElt   *e = FcPatternObjectFindElt (font, object);
350    FcValueListPtr  v;
351    FcValue         value;
352    int             idx = -1;
353    int             defidx = -1;
354    int             i;
355
356    if (e)
357    {
358	for (v = FcPatternEltValues(e), i = 0; v; v = FcValueListNext(v), ++i)
359	{
360	    value = FcValueCanonicalize (&v->value);
361
362	    if (value.type == FcTypeString)
363	    {
364		FcLangResult res = FcLangCompare (value.u.s, lang);
365		if (res == FcLangEqual)
366		    return i;
367
368		if (res == FcLangDifferentCountry && idx < 0)
369		    idx = i;
370		if (defidx < 0)
371		{
372		    /* workaround for fonts that has non-English value
373		     * at the head of values.
374		     */
375		    res = FcLangCompare (value.u.s, (FcChar8 *)"en");
376		    if (res == FcLangEqual)
377			defidx = i;
378		}
379	    }
380	}
381    }
382
383    return (idx > 0) ? idx : (defidx > 0) ? defidx : 0;
384}
385
386static FcBool
387FcListAppend (FcListHashTable	*table,
388	      FcPattern		*font,
389	      FcObjectSet	*os,
390	      const FcChar8	*lang)
391{
392    int		    o;
393    FcPatternElt    *e;
394    FcValueListPtr  v;
395    FcChar32	    hash;
396    FcListBucket    **prev, *bucket;
397    int             familyidx = -1;
398    int             fullnameidx = -1;
399    int             styleidx = -1;
400    int             defidx = 0;
401    int             idx;
402
403    hash = FcListPatternHash (font, os);
404    for (prev = &table->buckets[hash % FC_LIST_HASH_SIZE];
405	 (bucket = *prev); prev = &(bucket->next))
406    {
407	if (bucket->hash == hash &&
408	    FcListPatternEqual (bucket->pattern, font, os))
409	    return FcTrue;
410    }
411    bucket = (FcListBucket *) malloc (sizeof (FcListBucket));
412    if (!bucket)
413	goto bail0;
414    bucket->next = 0;
415    bucket->hash = hash;
416    bucket->pattern = FcPatternCreate ();
417    if (!bucket->pattern)
418	goto bail1;
419
420    for (o = 0; o < os->nobject; o++)
421    {
422	if (!strcmp (os->objects[o], FC_FAMILY) || !strcmp (os->objects[o], FC_FAMILYLANG))
423	{
424	    if (familyidx < 0)
425		familyidx = FcGetDefaultObjectLangIndex (font, FC_FAMILYLANG_OBJECT, lang);
426	    defidx = familyidx;
427	}
428	else if (!strcmp (os->objects[o], FC_FULLNAME) || !strcmp (os->objects[o], FC_FULLNAMELANG))
429	{
430	    if (fullnameidx < 0)
431		fullnameidx = FcGetDefaultObjectLangIndex (font, FC_FULLNAMELANG_OBJECT, lang);
432	    defidx = fullnameidx;
433	}
434	else if (!strcmp (os->objects[o], FC_STYLE) || !strcmp (os->objects[o], FC_STYLELANG))
435	{
436	    if (styleidx < 0)
437		styleidx = FcGetDefaultObjectLangIndex (font, FC_STYLELANG_OBJECT, lang);
438	    defidx = styleidx;
439	}
440	else
441	    defidx = 0;
442
443	e = FcPatternObjectFindElt (font, FcObjectFromName (os->objects[o]));
444	if (e)
445	{
446	    for (v = FcPatternEltValues(e), idx = 0; v;
447		 v = FcValueListNext(v), ++idx)
448	    {
449		if (!FcPatternAdd (bucket->pattern,
450				   os->objects[o],
451				   FcValueCanonicalize(&v->value), defidx != idx))
452		    goto bail2;
453	    }
454	}
455    }
456    *prev = bucket;
457    ++table->entries;
458
459    return FcTrue;
460
461bail2:
462    FcPatternDestroy (bucket->pattern);
463bail1:
464    free (bucket);
465bail0:
466    return FcFalse;
467}
468
469FcFontSet *
470FcFontSetList (FcConfig	    *config,
471	       FcFontSet    **sets,
472	       int	    nsets,
473	       FcPattern    *p,
474	       FcObjectSet  *os)
475{
476    FcFontSet	    *ret;
477    FcFontSet	    *s;
478    int		    f;
479    int		    set;
480    FcListHashTable table;
481    int		    i;
482    FcListBucket    *bucket;
483    int             destroy_os = 0;
484
485    if (!config)
486    {
487	if (!FcInitBringUptoDate ())
488	    goto bail0;
489
490	config = FcConfigGetCurrent ();
491	if (!config)
492	    goto bail0;
493    }
494    FcListHashTableInit (&table);
495
496    if (!os)
497    {
498	os = FcObjectGetSet ();
499	destroy_os = 1;
500    }
501
502    /*
503     * Walk all available fonts adding those that
504     * match to the hash table
505     */
506    for (set = 0; set < nsets; set++)
507    {
508	s = sets[set];
509	if (!s)
510	    continue;
511	for (f = 0; f < s->nfont; f++)
512	    if (FcListPatternMatchAny (p,		/* pattern */
513				       s->fonts[f]))	/* font */
514	    {
515		FcChar8 *lang;
516
517		if (FcPatternObjectGetString (p, FC_NAMELANG_OBJECT, 0, &lang) != FcResultMatch)
518		{
519			lang = FcGetDefaultLang ();
520		}
521		if (!FcListAppend (&table, s->fonts[f], os, lang))
522		    goto bail1;
523	    }
524    }
525#if 0
526    {
527	int	max = 0;
528	int	full = 0;
529	int	ents = 0;
530	int	len;
531	for (i = 0; i < FC_LIST_HASH_SIZE; i++)
532	{
533	    if ((bucket = table.buckets[i]))
534	    {
535		len = 0;
536		for (; bucket; bucket = bucket->next)
537		{
538		    ents++;
539		    len++;
540		}
541		if (len > max)
542		    max = len;
543		full++;
544	    }
545	}
546	printf ("used: %d max: %d avg: %g\n", full, max,
547		(double) ents / FC_LIST_HASH_SIZE);
548    }
549#endif
550    /*
551     * Walk the hash table and build
552     * a font set
553     */
554    ret = FcFontSetCreate ();
555    if (!ret)
556	goto bail0;
557    for (i = 0; i < FC_LIST_HASH_SIZE; i++)
558	while ((bucket = table.buckets[i]))
559	{
560	    if (!FcFontSetAdd (ret, bucket->pattern))
561		goto bail2;
562	    table.buckets[i] = bucket->next;
563	    free (bucket);
564	}
565
566    return ret;
567
568bail2:
569    FcFontSetDestroy (ret);
570bail1:
571    FcListHashTableCleanup (&table);
572bail0:
573    if (destroy_os)
574	FcObjectSetDestroy (os);
575    return 0;
576}
577
578FcFontSet *
579FcFontList (FcConfig	*config,
580	    FcPattern	*p,
581	    FcObjectSet *os)
582{
583    FcFontSet	*sets[2];
584    int		nsets;
585
586    if (!config)
587    {
588	if (!FcInitBringUptoDate ())
589	    return 0;
590
591	config = FcConfigGetCurrent ();
592	if (!config)
593	    return 0;
594    }
595    nsets = 0;
596    if (config->fonts[FcSetSystem])
597	sets[nsets++] = config->fonts[FcSetSystem];
598    if (config->fonts[FcSetApplication])
599	sets[nsets++] = config->fonts[FcSetApplication];
600    return FcFontSetList (config, sets, nsets, p, os);
601}
602#define __fclist__
603#include "fcaliastail.h"
604#undef __fclist__
605