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