1/*
2 * fontconfig/test/test-conf.c
3 *
4 * Copyright © 2000 Keith Packard
5 * Copyright © 2018 Akira TAGOH
6 *
7 * Permission to use, copy, modify, distribute, and sell this software and its
8 * documentation for any purpose is hereby granted without fee, provided that
9 * the above copyright notice appear in all copies and that both that
10 * copyright notice and this permission notice appear in supporting
11 * documentation, and that the name of the author(s) not be used in
12 * advertising or publicity pertaining to distribution of the software without
13 * specific, written prior permission.  The authors make no
14 * representations about the suitability of this software for any purpose.  It
15 * is provided "as is" without express or implied warranty.
16 *
17 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
18 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
19 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
20 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
22 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
23 * PERFORMANCE OF THIS SOFTWARE.
24 */
25#include <stdio.h>
26#include <string.h>
27#include <fontconfig/fontconfig.h>
28#include <json.h>
29
30struct _FcConfig {
31    FcStrSet	*configDirs;	    /* directories to scan for fonts */
32    FcStrSet	*fontDirs;
33    FcStrSet	*cacheDirs;
34    FcStrSet	*configFiles;	    /* config files loaded */
35    void	*subst[FcMatchKindEnd];
36    int		maxObjects;	    /* maximum number of tests in all substs */
37    FcStrSet	*acceptGlobs;
38    FcStrSet	*rejectGlobs;
39    FcFontSet	*acceptPatterns;
40    FcFontSet	*rejectPatterns;
41    FcFontSet	*fonts[FcSetApplication + 1];
42};
43
44static FcPattern *
45build_pattern (json_object *obj)
46{
47    json_object_iter iter;
48    FcPattern *pat = FcPatternCreate ();
49
50    json_object_object_foreachC (obj, iter)
51    {
52	FcValue v;
53	FcBool destroy_v = FcFalse;
54	FcMatrix matrix;
55
56	if (json_object_get_type (iter.val) == json_type_boolean)
57	{
58	    v.type = FcTypeBool;
59	    v.u.b = json_object_get_boolean (iter.val);
60	}
61	else if (json_object_get_type (iter.val) == json_type_double)
62	{
63	    v.type = FcTypeDouble;
64	    v.u.d = json_object_get_double (iter.val);
65	}
66	else if (json_object_get_type (iter.val) == json_type_int)
67	{
68	    v.type = FcTypeInteger;
69	    v.u.i = json_object_get_int (iter.val);
70	}
71	else if (json_object_get_type (iter.val) == json_type_string)
72	{
73	    const FcObjectType *o = FcNameGetObjectType (iter.key);
74	    if (o && (o->type == FcTypeRange || o->type == FcTypeDouble || o->type == FcTypeInteger))
75	    {
76		const FcConstant *c = FcNameGetConstant ((const FcChar8 *) json_object_get_string (iter.val));
77		if (!c) {
78		    fprintf (stderr, "E: value is not a known constant\n");
79		    fprintf (stderr, "   key: %s\n", iter.key);
80		    fprintf (stderr, "   val: %s\n", json_object_get_string (iter.val));
81		    continue;
82		}
83		if (strcmp (c->object, iter.key) != 0)
84		{
85		    fprintf (stderr, "E: value is a constant of different object\n");
86		    fprintf (stderr, "   key: %s\n", iter.key);
87		    fprintf (stderr, "   val: %s\n", json_object_get_string (iter.val));
88		    fprintf (stderr, "   key implied by value: %s\n", c->object);
89		    continue;
90		}
91		v.type = FcTypeInteger;
92		v.u.i = c->value;
93	    }
94	    else if (strcmp (json_object_get_string (iter.val), "DontCare") == 0)
95	    {
96		v.type = FcTypeBool;
97		v.u.b = FcDontCare;
98	    }
99	    else
100	    {
101		v.type = FcTypeString;
102		v.u.s = (const FcChar8 *) json_object_get_string (iter.val);
103	    }
104	}
105	else if (json_object_get_type (iter.val) == json_type_null)
106	{
107	    v.type = FcTypeVoid;
108	}
109	else if (json_object_get_type (iter.val) == json_type_array)
110	{
111	    json_object *o;
112	    json_type type;
113	    int i, n;
114
115	    n = json_object_array_length (iter.val);
116	    if (n == 0) {
117		fprintf (stderr, "E: value is an empty array\n");
118		continue;
119	    }
120
121	    o = json_object_array_get_idx (iter.val, 0);
122	    type = json_object_get_type (o);
123	    if (type == json_type_string) {
124		const FcObjectType *fc_o = FcNameGetObjectType (iter.key);
125		if (fc_o && fc_o->type == FcTypeCharSet) {
126		    FcCharSet* cs = FcCharSetCreate ();
127		    if (!cs) {
128			fprintf (stderr, "E: failed to create charset\n");
129			continue;
130		    }
131		    v.type = FcTypeCharSet;
132		    v.u.c = cs;
133		    destroy_v = FcTrue;
134		    for (i = 0; i < n; i++)
135	            {
136			const FcChar8 *src;
137			int len, nchar, wchar;
138			FcBool valid;
139			FcChar32 dst;
140
141			o = json_object_array_get_idx (iter.val, i);
142			type = json_object_get_type (o);
143			if (type != json_type_string) {
144			    fprintf (stderr, "E: charset value not string\n");
145			    FcValueDestroy (v);
146			    continue;
147			}
148			src = (const FcChar8 *) json_object_get_string (o);
149			len = json_object_get_string_len (o);
150			valid = FcUtf8Len (src, len, &nchar, &wchar);
151			if (valid == FcFalse) {
152			    fprintf (stderr, "E: charset entry not well formed\n");
153			    FcValueDestroy (v);
154			    continue;
155			}
156			if (nchar != 1) {
157			    fprintf (stderr, "E: charset entry not not one codepoint\n");
158			    FcValueDestroy (v);
159			    continue;
160			}
161			FcUtf8ToUcs4 (src, &dst, len);
162			if (FcCharSetAddChar (cs, dst) == FcFalse) {
163			    fprintf (stderr, "E: failed to add to charset\n");
164			    FcValueDestroy (v);
165			    continue;
166			}
167		    }
168		} else if (fc_o && fc_o->type == FcTypeString) {
169		    for (i = 0; i < n; i++)
170		    {
171			o = json_object_array_get_idx (iter.val, i);
172			type = json_object_get_type (o);
173			if (type != json_type_string) {
174			    fprintf (stderr, "E: unable to convert to string\n");
175			    continue;
176			}
177			v.type = FcTypeString;
178			v.u.s = (const FcChar8 *) json_object_get_string (o);
179			FcPatternAdd (pat, iter.key, v, FcTrue);
180			v.type = FcTypeVoid;
181		    }
182		    continue;
183		} else {
184		    FcLangSet* ls = FcLangSetCreate ();
185		    if (!ls) {
186			fprintf (stderr, "E: failed to create langset\n");
187			continue;
188		    }
189		    v.type = FcTypeLangSet;
190		    v.u.l = ls;
191		    destroy_v = FcTrue;
192		    for (i = 0; i < n; i++)
193	            {
194			o = json_object_array_get_idx (iter.val, i);
195			type = json_object_get_type (o);
196			if (type != json_type_string) {
197			    fprintf (stderr, "E: langset value not string\n");
198			    FcValueDestroy (v);
199			    continue;
200			}
201			if (FcLangSetAdd (ls, (const FcChar8 *)json_object_get_string (o)) == FcFalse) {
202			    fprintf (stderr, "E: failed to add to langset\n");
203			    FcValueDestroy (v);
204			    continue;
205			}
206		    }
207		}
208	    } else if (type == json_type_double || type == json_type_int) {
209		const FcObjectType *fc_o = FcNameGetObjectType (iter.key);
210		double values[4];
211
212		if (fc_o && fc_o->type == FcTypeDouble) {
213		    for (i = 0; i < n; i++)
214		    {
215			o = json_object_array_get_idx (iter.val, i);
216			type = json_object_get_type (o);
217			if (type == json_type_double) {
218			    v.type = FcTypeDouble;
219			    v.u.d = json_object_get_double (o);
220			} else if (type == json_type_int) {
221			    v.type = FcTypeInteger;
222			    v.u.i = json_object_get_int (o);
223			} else {
224			    fprintf (stderr, "E: unable to convert to double\n");
225			    continue;
226			}
227			FcPatternAdd (pat, iter.key, v, FcTrue);
228			v.type = FcTypeVoid;
229		    }
230		    continue;
231		} else {
232		    if (n != 2 && n != 4) {
233			fprintf (stderr, "E: array starting with number not range or matrix\n");
234			continue;
235		    }
236		    for (i = 0; i < n; i++) {
237			o = json_object_array_get_idx (iter.val, i);
238			type = json_object_get_type (o);
239			if (type != json_type_double && type != json_type_int) {
240			    fprintf (stderr, "E: numeric array entry not a number\n");
241			    continue;
242			}
243			values[i] = json_object_get_double (o);
244		    }
245		    if (n == 2) {
246			v.type = FcTypeRange;
247			v.u.r = FcRangeCreateDouble (values[0], values[1]);
248			if (!v.u.r) {
249			    fprintf (stderr, "E: failed to create range\n");
250			    continue;
251			}
252			destroy_v = FcTrue;
253		    } else {
254			v.type = FcTypeMatrix;
255			v.u.m = &matrix;
256			matrix.xx = values[0];
257			matrix.xy = values[1];
258			matrix.yx = values[2];
259			matrix.yy = values[3];
260		    }
261		}
262	    } else {
263		fprintf (stderr, "E: array format not recognized\n");
264		continue;
265	    }
266	}
267	else
268	{
269	    fprintf (stderr, "W: unexpected object to build a pattern: (%s %s)", iter.key, json_type_to_name (json_object_get_type (iter.val)));
270	    continue;
271	}
272	if (v.type != FcTypeVoid)
273	    FcPatternAdd (pat, iter.key, v, FcTrue);
274	if (destroy_v)
275	    FcValueDestroy (v);
276    }
277    return pat;
278}
279
280static FcFontSet *
281build_fs (json_object *obj)
282{
283    FcFontSet *fs = FcFontSetCreate ();
284    int i, n;
285
286    n = json_object_array_length (obj);
287    for (i = 0; i < n; i++)
288    {
289	json_object *o = json_object_array_get_idx (obj, i);
290	FcPattern *pat;
291
292	if (json_object_get_type (o) != json_type_object)
293	    continue;
294	pat = build_pattern (o);
295	FcFontSetAdd (fs, pat);
296    }
297
298    return fs;
299}
300
301static FcBool
302build_fonts (FcConfig *config, json_object *root)
303{
304    json_object *fonts;
305    FcFontSet *fs;
306
307    if (!json_object_object_get_ex (root, "fonts", &fonts) ||
308	json_object_get_type (fonts) != json_type_array)
309    {
310	fprintf (stderr, "W: No fonts defined\n");
311	return FcFalse;
312    }
313    fs = build_fs (fonts);
314    /* FcConfigSetFonts (config, fs, FcSetSystem); */
315    if (config->fonts[FcSetSystem])
316	FcFontSetDestroy (config->fonts[FcSetSystem]);
317    config->fonts[FcSetSystem] = fs;
318
319    return FcTrue;
320}
321
322static FcBool
323run_test (FcConfig *config, json_object *root)
324{
325    json_object *tests;
326    int i, n, fail = 0;
327
328    if (!json_object_object_get_ex (root, "tests", &tests) ||
329	json_object_get_type (tests) != json_type_array)
330    {
331	fprintf (stderr, "W: No test cases defined\n");
332	return FcFalse;
333    }
334    n = json_object_array_length (tests);
335    for (i = 0; i < n; i++)
336    {
337	json_object *obj = json_object_array_get_idx (tests, i);
338	json_object_iter iter;
339	FcPattern *query = NULL;
340	FcPattern *result = NULL;
341	FcFontSet *result_fs = NULL;
342	const char *method = NULL;
343
344	if (json_object_get_type (obj) != json_type_object)
345	    continue;
346	json_object_object_foreachC (obj, iter)
347	{
348	    if (strcmp (iter.key, "method") == 0)
349	    {
350		if (json_object_get_type (iter.val) != json_type_string)
351		{
352		    fprintf (stderr, "W: invalid type of method: (%s)\n", json_type_to_name (json_object_get_type (iter.val)));
353		    continue;
354		}
355		method = json_object_get_string (iter.val);
356	    }
357	    else if (strcmp (iter.key, "query") == 0)
358	    {
359		if (json_object_get_type (iter.val) != json_type_object)
360		{
361		    fprintf (stderr, "W: invalid type of query: (%s)\n", json_type_to_name (json_object_get_type (iter.val)));
362		    continue;
363		}
364		if (query)
365		    FcPatternDestroy (query);
366		query = build_pattern (iter.val);
367	    }
368	    else if (strcmp (iter.key, "result") == 0)
369	    {
370		if (json_object_get_type (iter.val) != json_type_object)
371		{
372		    fprintf (stderr, "W: invalid type of result: (%s)\n", json_type_to_name (json_object_get_type (iter.val)));
373		    continue;
374		}
375		if (result)
376		    FcPatternDestroy (result);
377		result = build_pattern (iter.val);
378	    }
379	    else if (strcmp (iter.key, "result_fs") == 0)
380	    {
381		if (json_object_get_type (iter.val) != json_type_array)
382		{
383		    fprintf (stderr, "W: invalid type of result_fs: (%s)\n", json_type_to_name (json_object_get_type (iter.val)));
384		    continue;
385		}
386		if (result_fs)
387		    FcFontSetDestroy (result_fs);
388		result_fs = build_fs (iter.val);
389	    }
390	    else
391	    {
392		fprintf (stderr, "W: unknown object: %s\n", iter.key);
393	    }
394	}
395	if (method != NULL && strcmp (method, "match") == 0)
396	{
397	    FcPattern *match;
398	    FcResult res;
399
400	    if (!query)
401	    {
402		fprintf (stderr, "E: no query defined.\n");
403		fail++;
404		goto bail;
405	    }
406	    if (!result)
407	    {
408		fprintf (stderr, "E: no result defined.\n");
409		fail++;
410		goto bail;
411	    }
412	    FcConfigSubstitute (config, query, FcMatchPattern);
413	    FcDefaultSubstitute (query);
414	    match = FcFontMatch (config, query, &res);
415	    if (match)
416	    {
417		FcPatternIter iter;
418		int x, vc;
419
420		FcPatternIterStart (result, &iter);
421		do
422		{
423		    vc = FcPatternIterValueCount (result, &iter);
424		    for (x = 0; x < vc; x++)
425		    {
426			FcValue vr, vm;
427
428			if (FcPatternIterGetValue (result, &iter, x, &vr, NULL) != FcResultMatch)
429			{
430			    fprintf (stderr, "E: unable to obtain a value from the expected result\n");
431			    fail++;
432			    goto bail;
433			}
434			if (FcPatternGet (match, FcPatternIterGetObject (result, &iter), x, &vm) != FcResultMatch)
435			{
436			    vm.type = FcTypeVoid;
437			}
438			if (!FcValueEqual (vm, vr))
439			{
440			    printf ("E: failed to compare %s:\n", FcPatternIterGetObject (result, &iter));
441			    printf ("   actual result:");
442			    FcValuePrint (vm);
443			    printf ("\n   expected result:");
444			    FcValuePrint (vr);
445			    printf ("\n");
446			    fail++;
447			    goto bail;
448			}
449		    }
450		} while (FcPatternIterNext (result, &iter));
451	    bail:
452		FcPatternDestroy (match);
453	    }
454	    else
455	    {
456		fprintf (stderr, "E: no match\n");
457		fail++;
458	    }
459	}
460	else if (method != NULL && strcmp (method, "list") == 0)
461	{
462	    FcFontSet *fs;
463
464	    if (!query)
465	    {
466		fprintf (stderr, "E: no query defined.\n");
467		fail++;
468		goto bail2;
469	    }
470	    if (!result_fs)
471	    {
472		fprintf (stderr, "E: no result_fs defined.\n");
473		fail++;
474		goto bail2;
475	    }
476	    fs = FcFontList (config, query, NULL);
477	    if (!fs)
478	    {
479		fprintf (stderr, "E: failed on FcFontList\n");
480		fail++;
481	    }
482	    else
483	    {
484		int i;
485
486		if (fs->nfont != result_fs->nfont)
487		{
488		    printf ("E: The number of results is different:\n");
489		    printf ("   actual result: %d\n", fs->nfont);
490		    printf ("   expected result: %d\n", result_fs->nfont);
491		    fail++;
492		    goto bail2;
493		}
494		for (i = 0; i < fs->nfont; i++)
495		{
496		    FcPatternIter iter;
497		    int x, vc;
498
499		    FcPatternIterStart (result_fs->fonts[i], &iter);
500		    do
501		    {
502			vc = FcPatternIterValueCount (result_fs->fonts[i], &iter);
503			for (x = 0; x < vc; x++)
504			{
505			    FcValue vr, vm;
506
507			    if (FcPatternIterGetValue (result_fs->fonts[i], &iter, x, &vr, NULL) != FcResultMatch)
508			    {
509				fprintf (stderr, "E: unable to obtain a value from the expected result\n");
510				fail++;
511				goto bail2;
512			    }
513			    if (FcPatternGet (fs->fonts[i], FcPatternIterGetObject (result_fs->fonts[i], &iter), x, &vm) != FcResultMatch)
514			    {
515				vm.type = FcTypeVoid;
516			    }
517			    if (!FcValueEqual (vm, vr))
518			    {
519				printf ("E: failed to compare %s:\n", FcPatternIterGetObject (result_fs->fonts[i], &iter));
520				printf ("   actual result:");
521				FcValuePrint (vm);
522				printf ("\n   expected result:");
523				FcValuePrint (vr);
524				printf ("\n");
525				fail++;
526				goto bail2;
527			    }
528			}
529		    } while (FcPatternIterNext (result_fs->fonts[i], &iter));
530		}
531	    bail2:
532		FcFontSetDestroy (fs);
533	    }
534	}
535	else
536	{
537	    fprintf (stderr, "W: unknown testing method: %s\n", method);
538	}
539	if (method)
540	    method = NULL;
541	if (result)
542	{
543	    FcPatternDestroy (result);
544	    result = NULL;
545	}
546	if (result_fs)
547	{
548	    FcFontSetDestroy (result_fs);
549	    result_fs = NULL;
550	}
551	if (query)
552	{
553	    FcPatternDestroy (query);
554	    query = NULL;
555	}
556    }
557
558    return fail == 0;
559}
560
561static FcBool
562run_scenario (FcConfig *config, char *file)
563{
564    FcBool ret = FcTrue;
565    json_object *root, *scenario;
566
567    root = json_object_from_file (file);
568    if (!root)
569    {
570	fprintf (stderr, "E: Unable to read the file: %s\n", file);
571	return FcFalse;
572    }
573    if (!build_fonts (config, root))
574    {
575	ret = FcFalse;
576	goto bail1;
577    }
578    if (!run_test (config, root))
579    {
580	ret = FcFalse;
581	goto bail1;
582    }
583
584bail1:
585    json_object_put (root);
586
587    return ret;
588}
589
590static FcBool
591load_config (FcConfig *config, char *file)
592{
593    FILE *fp;
594    long len;
595    char *buf = NULL;
596    FcBool ret = FcTrue;
597
598    if ((fp = fopen(file, "rb")) == NULL)
599	return FcFalse;
600    fseek (fp, 0L, SEEK_END);
601    len = ftell (fp);
602    fseek (fp, 0L, SEEK_SET);
603    buf = malloc (sizeof (char) * (len + 1));
604    if (!buf)
605    {
606	ret = FcFalse;
607	goto bail1;
608    }
609    fread (buf, (size_t)len, sizeof (char), fp);
610    buf[len] = 0;
611
612    ret = FcConfigParseAndLoadFromMemory (config, (const FcChar8 *) buf, FcTrue);
613bail1:
614    fclose (fp);
615    if (buf)
616	free (buf);
617
618    return ret;
619}
620
621int
622main (int argc, char **argv)
623{
624    FcConfig *config;
625    int retval = 0;
626
627    if (argc < 3)
628    {
629	fprintf(stderr, "Usage: %s <conf file> <test scenario>\n", argv[0]);
630	return 1;
631    }
632
633    config = FcConfigCreate ();
634    if (!load_config (config, argv[1]))
635    {
636	fprintf(stderr, "E: Failed to load config\n");
637	retval = 1;
638	goto bail1;
639    }
640    if (!run_scenario (config, argv[2]))
641    {
642	retval = 1;
643	goto bail1;
644    }
645bail1:
646    FcConfigDestroy (config);
647
648    return retval;
649}
650