fclist.c revision 6fc018e4
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    if (!p)
216	return FcFalse;
217    for (i = 0; i < p->num; i++)
218    {
219	FcPatternElt	*pe = &FcPatternElts(p)[i];
220	FcPatternElt	*fe;
221
222	if (pe->object == FC_NAMELANG_OBJECT)
223	{
224	    /* "namelang" object is the alias object to change "familylang",
225	     * "stylelang" and "fullnamelang" object alltogether. it won't be
226	     * available on the font pattern. so checking its availability
227	     * causes no results. we should ignore it here.
228	     */
229	    continue;
230	}
231	fe = FcPatternObjectFindElt (font, pe->object);
232	if (!fe)
233	    return FcFalse;
234	if (!FcListValueListMatchAny (FcPatternEltValues(pe),    /* pat elts */
235				      FcPatternEltValues(fe)))   /* font elts */
236	    return FcFalse;
237    }
238    return FcTrue;
239}
240
241static FcChar32
242FcListMatrixHash (const FcMatrix *m)
243{
244    int	    xx = (int) (m->xx * 100),
245	    xy = (int) (m->xy * 100),
246	    yx = (int) (m->yx * 100),
247	    yy = (int) (m->yy * 100);
248
249    return ((FcChar32) xx) ^ ((FcChar32) xy) ^ ((FcChar32) yx) ^ ((FcChar32) yy);
250}
251
252static FcChar32
253FcListValueHash (FcValue    *value)
254{
255    FcValue v = FcValueCanonicalize(value);
256    switch (v.type) {
257    case FcTypeUnknown:
258    case FcTypeVoid:
259	return 0;
260    case FcTypeInteger:
261	return (FcChar32) v.u.i;
262    case FcTypeDouble:
263	return (FcChar32) (int) v.u.d;
264    case FcTypeString:
265	return FcStrHashIgnoreCase (v.u.s);
266    case FcTypeBool:
267	return (FcChar32) v.u.b;
268    case FcTypeMatrix:
269	return FcListMatrixHash (v.u.m);
270    case FcTypeCharSet:
271	return FcCharSetCount (v.u.c);
272    case FcTypeFTFace:
273	return (long) v.u.f;
274    case FcTypeLangSet:
275	return FcLangSetHash (v.u.l);
276    }
277    return 0;
278}
279
280static FcChar32
281FcListValueListHash (FcValueListPtr list)
282{
283    FcChar32	h = 0;
284
285    while (list != NULL)
286    {
287	h = h ^ FcListValueHash (&list->value);
288	list = FcValueListNext(list);
289    }
290    return h;
291}
292
293static FcChar32
294FcListPatternHash (FcPattern	*font,
295		   FcObjectSet	*os)
296{
297    int		    n;
298    FcPatternElt    *e;
299    FcChar32	    h = 0;
300
301    for (n = 0; n < os->nobject; n++)
302    {
303	e = FcPatternObjectFindElt (font, FcObjectFromName (os->objects[n]));
304	if (e)
305	    h = h ^ FcListValueListHash (FcPatternEltValues(e));
306    }
307    return h;
308}
309
310typedef struct _FcListBucket {
311    struct _FcListBucket    *next;
312    FcChar32		    hash;
313    FcPattern		    *pattern;
314} FcListBucket;
315
316#define FC_LIST_HASH_SIZE   4099
317
318typedef struct _FcListHashTable {
319    int		    entries;
320    FcListBucket    *buckets[FC_LIST_HASH_SIZE];
321} FcListHashTable;
322
323static void
324FcListHashTableInit (FcListHashTable *table)
325{
326    table->entries = 0;
327    memset (table->buckets, '\0', sizeof (table->buckets));
328}
329
330static void
331FcListHashTableCleanup (FcListHashTable *table)
332{
333    int	i;
334    FcListBucket    *bucket, *next;
335
336    for (i = 0; i < FC_LIST_HASH_SIZE; i++)
337    {
338	for (bucket = table->buckets[i]; bucket; bucket = next)
339	{
340	    next = bucket->next;
341	    FcPatternDestroy (bucket->pattern);
342	    free (bucket);
343	}
344	table->buckets[i] = 0;
345    }
346    table->entries = 0;
347}
348
349static int
350FcGetDefaultObjectLangIndex (FcPattern *font, FcObject object, const FcChar8 *lang)
351{
352    FcPatternElt   *e = FcPatternObjectFindElt (font, object);
353    FcValueListPtr  v;
354    FcValue         value;
355    int             idx = -1;
356    int             defidx = -1;
357    int             i;
358
359    if (e)
360    {
361	for (v = FcPatternEltValues(e), i = 0; v; v = FcValueListNext(v), ++i)
362	{
363	    value = FcValueCanonicalize (&v->value);
364
365	    if (value.type == FcTypeString)
366	    {
367		FcLangResult res = FcLangCompare (value.u.s, lang);
368		if (res == FcLangEqual)
369		    return i;
370
371		if (res == FcLangDifferentCountry && idx < 0)
372		    idx = i;
373		if (defidx < 0)
374		{
375		    /* workaround for fonts that has non-English value
376		     * at the head of values.
377		     */
378		    res = FcLangCompare (value.u.s, (FcChar8 *)"en");
379		    if (res == FcLangEqual)
380			defidx = i;
381		}
382	    }
383	}
384    }
385
386    return (idx > 0) ? idx : (defidx > 0) ? defidx : 0;
387}
388
389static FcBool
390FcListAppend (FcListHashTable	*table,
391	      FcPattern		*font,
392	      FcObjectSet	*os,
393	      const FcChar8	*lang)
394{
395    int		    o;
396    FcPatternElt    *e;
397    FcValueListPtr  v;
398    FcChar32	    hash;
399    FcListBucket    **prev, *bucket;
400    int             familyidx = -1;
401    int             fullnameidx = -1;
402    int             styleidx = -1;
403    int             defidx = 0;
404    int             idx;
405
406    hash = FcListPatternHash (font, os);
407    for (prev = &table->buckets[hash % FC_LIST_HASH_SIZE];
408	 (bucket = *prev); prev = &(bucket->next))
409    {
410	if (bucket->hash == hash &&
411	    FcListPatternEqual (bucket->pattern, font, os))
412	    return FcTrue;
413    }
414    bucket = (FcListBucket *) malloc (sizeof (FcListBucket));
415    if (!bucket)
416	goto bail0;
417    bucket->next = 0;
418    bucket->hash = hash;
419    bucket->pattern = FcPatternCreate ();
420    if (!bucket->pattern)
421	goto bail1;
422
423    for (o = 0; o < os->nobject; o++)
424    {
425	if (!strcmp (os->objects[o], FC_FAMILY) || !strcmp (os->objects[o], FC_FAMILYLANG))
426	{
427	    if (familyidx < 0)
428		familyidx = FcGetDefaultObjectLangIndex (font, FC_FAMILYLANG_OBJECT, lang);
429	    defidx = familyidx;
430	}
431	else if (!strcmp (os->objects[o], FC_FULLNAME) || !strcmp (os->objects[o], FC_FULLNAMELANG))
432	{
433	    if (fullnameidx < 0)
434		fullnameidx = FcGetDefaultObjectLangIndex (font, FC_FULLNAMELANG_OBJECT, lang);
435	    defidx = fullnameidx;
436	}
437	else if (!strcmp (os->objects[o], FC_STYLE) || !strcmp (os->objects[o], FC_STYLELANG))
438	{
439	    if (styleidx < 0)
440		styleidx = FcGetDefaultObjectLangIndex (font, FC_STYLELANG_OBJECT, lang);
441	    defidx = styleidx;
442	}
443	else
444	    defidx = 0;
445
446	e = FcPatternObjectFindElt (font, FcObjectFromName (os->objects[o]));
447	if (e)
448	{
449	    for (v = FcPatternEltValues(e), idx = 0; v;
450		 v = FcValueListNext(v), ++idx)
451	    {
452		if (!FcPatternAdd (bucket->pattern,
453				   os->objects[o],
454				   FcValueCanonicalize(&v->value), defidx != idx))
455		    goto bail2;
456	    }
457	}
458    }
459    *prev = bucket;
460    ++table->entries;
461
462    return FcTrue;
463
464bail2:
465    FcPatternDestroy (bucket->pattern);
466bail1:
467    free (bucket);
468bail0:
469    return FcFalse;
470}
471
472FcFontSet *
473FcFontSetList (FcConfig	    *config,
474	       FcFontSet    **sets,
475	       int	    nsets,
476	       FcPattern    *p,
477	       FcObjectSet  *os)
478{
479    FcFontSet	    *ret;
480    FcFontSet	    *s;
481    int		    f;
482    int		    set;
483    FcListHashTable table;
484    int		    i;
485    FcListBucket    *bucket;
486    int             destroy_os = 0;
487
488    if (!config)
489    {
490	if (!FcInitBringUptoDate ())
491	    goto bail0;
492
493	config = FcConfigGetCurrent ();
494	if (!config)
495	    goto bail0;
496    }
497    FcListHashTableInit (&table);
498
499    if (!os)
500    {
501	os = FcObjectGetSet ();
502	destroy_os = 1;
503    }
504
505    /*
506     * Walk all available fonts adding those that
507     * match to the hash table
508     */
509    for (set = 0; set < nsets; set++)
510    {
511	s = sets[set];
512	if (!s)
513	    continue;
514	for (f = 0; f < s->nfont; f++)
515	    if (FcListPatternMatchAny (p,		/* pattern */
516				       s->fonts[f]))	/* font */
517	    {
518		FcChar8 *lang;
519
520		if (FcPatternObjectGetString (p, FC_NAMELANG_OBJECT, 0, &lang) != FcResultMatch)
521		{
522			lang = FcGetDefaultLang ();
523		}
524		if (!FcListAppend (&table, s->fonts[f], os, lang))
525		    goto bail1;
526	    }
527    }
528#if 0
529    {
530	int	max = 0;
531	int	full = 0;
532	int	ents = 0;
533	int	len;
534	for (i = 0; i < FC_LIST_HASH_SIZE; i++)
535	{
536	    if ((bucket = table.buckets[i]))
537	    {
538		len = 0;
539		for (; bucket; bucket = bucket->next)
540		{
541		    ents++;
542		    len++;
543		}
544		if (len > max)
545		    max = len;
546		full++;
547	    }
548	}
549	printf ("used: %d max: %d avg: %g\n", full, max,
550		(double) ents / FC_LIST_HASH_SIZE);
551    }
552#endif
553    /*
554     * Walk the hash table and build
555     * a font set
556     */
557    ret = FcFontSetCreate ();
558    if (!ret)
559	goto bail0;
560    for (i = 0; i < FC_LIST_HASH_SIZE; i++)
561	while ((bucket = table.buckets[i]))
562	{
563	    if (!FcFontSetAdd (ret, bucket->pattern))
564		goto bail2;
565	    table.buckets[i] = bucket->next;
566	    free (bucket);
567	}
568
569    return ret;
570
571bail2:
572    FcFontSetDestroy (ret);
573bail1:
574    FcListHashTableCleanup (&table);
575bail0:
576    if (destroy_os)
577	FcObjectSetDestroy (os);
578    return 0;
579}
580
581FcFontSet *
582FcFontList (FcConfig	*config,
583	    FcPattern	*p,
584	    FcObjectSet *os)
585{
586    FcFontSet	*sets[2];
587    int		nsets;
588
589    if (!config)
590    {
591	if (!FcInitBringUptoDate ())
592	    return 0;
593
594	config = FcConfigGetCurrent ();
595	if (!config)
596	    return 0;
597    }
598    nsets = 0;
599    if (config->fonts[FcSetSystem])
600	sets[nsets++] = config->fonts[FcSetSystem];
601    if (config->fonts[FcSetApplication])
602	sets[nsets++] = config->fonts[FcSetApplication];
603    return FcFontSetList (config, sets, nsets, p, os);
604}
605#define __fclist__
606#include "fcaliastail.h"
607#undef __fclist__
608