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