doc/fcconfig.fncs | 10 +++++ fontconfig/fontconfig.h | 3 + src/fccfg.c | 12 ++++++ src/fcint.h | 3 + src/fcmatch.c | 22 ++++++++---- src/fcxml.c | 2 + test/run-test-conf.sh | 1 test/test-appfont.json | 75 +++++++++++++++++++++++++++++++++++++++++ test/test-conf.c | 86 ++++++++++++++++++++++++++++++++++++++---------- 9 files changed, 190 insertions(+), 24 deletions(-) New commits: commit 2e4e6655c716b495374b6b1f3dd7552e860c82c4 Merge: dd5c5fd 9268bb6 Author: Akira TAGOH <akira@xxxxxxxxx> Date: Fri Apr 25 06:56:45 2025 +0000 Merge branch 'issues/455' into 'main' Add API to allow changing the order of application fonts Closes #455 See merge request fontconfig/fontconfig!379 commit 9268bb64bebd1e46a0433d780ebdeea49fc53d95 Author: Akira TAGOH <akira@xxxxxxxxx> Date: Fri Apr 25 14:30:21 2025 +0900 Add FcConfigPerferAppFont() to allow changing the order of application fonts This basically takes effect for FcFont(Set)Sort only. FcFont(Set)Match works without this change and FcFont(Set)List doesn't need it. Fixes https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/455 Changelog: added diff --git a/doc/fcconfig.fncs b/doc/fcconfig.fncs index 0a072af..7517577 100644 --- a/doc/fcconfig.fncs +++ b/doc/fcconfig.fncs @@ -544,3 +544,13 @@ in configuration file. This function tries to match 'pat' with them and return FcFalse if 'pat' is rejected, otherwise FcTrue. @SINCE@ 2.15.1 @@ + +@RET@ void +@FUNC@ FcConfigPreferAppFont +@TYPE1@ FcConfig * @ARG1@ config +@TYPE2@ FcBool% @ARG2@ flag +@DESC@ +Set 'flag' to change the priority of Application fonts. default behavior is turned off +for backward compatibility. +@SINCE@ 2.17.0 +@@ diff --git a/fontconfig/fontconfig.h b/fontconfig/fontconfig.h index bd04884..6326636 100644 --- a/fontconfig/fontconfig.h +++ b/fontconfig/fontconfig.h @@ -485,6 +485,9 @@ FcConfigAppFontAddDir (FcConfig *config, FcPublic void FcConfigAppFontClear (FcConfig *config); +FcPublic void +FcConfigPreferAppFont (FcConfig *config, FcBool flag); + FcPublic FcBool FcConfigSubstituteWithPat (FcConfig *config, FcPattern *p, diff --git a/src/fccfg.c b/src/fccfg.c index 8b82b42..cfa0920 100644 --- a/src/fccfg.c +++ b/src/fccfg.c @@ -2776,6 +2776,18 @@ FcConfigAppFontClear (FcConfig *config) FcConfigDestroy (config); } +void +FcConfigPreferAppFont (FcConfig *config, FcBool flag) +{ + config = FcConfigReference (config); + if (!config) + return; + + config->prefer_app_fonts = flag; + + FcConfigDestroy (config); +} + /* * Manage filename-based font source selectors */ diff --git a/src/fcint.h b/src/fcint.h index 334cde0..e422c72 100644 --- a/src/fcint.h +++ b/src/fcint.h @@ -613,6 +613,9 @@ struct _FcConfig { FcFilterFontSetFunc filter_func; /* A predicate function to filter out config->fonts */ FcDestroyFunc destroy_data_func; /* A callback function to destroy config->filter_data */ void *filter_data; /* An user data to be used for filter_func */ + + FcBool prefer_app_fonts; /* Whether FcSetApplication has a priority than + FcSetSystem for lookup */ }; typedef struct _FcFileTime { diff --git a/src/fcmatch.c b/src/fcmatch.c index 444709c..d8523a1 100644 --- a/src/fcmatch.c +++ b/src/fcmatch.c @@ -564,7 +564,7 @@ FcCompareFamilies (FcPattern *pat, } } if (FcDebug() & FC_DBG_MATCHV) { - printf ("%s: %g ", FcObjectName (FC_FAMILY_OBJECT), strong_value); + printf ("%s: %g (%g) ", FcObjectName (FC_FAMILY_OBJECT), strong_value, weak_value); FcValueListPrint (v1orig); printf (", "); FcValueListPrint (v2orig); @@ -1107,7 +1107,7 @@ FcFontSetSortDestroy (FcFontSet *fs) } FcFontSet * -FcFontSetSort (FcConfig *config FC_UNUSED, +FcFontSetSort (FcConfig *config, FcFontSet **sets, int nsets, FcPattern *p, @@ -1186,6 +1186,13 @@ FcFontSetSort (FcConfig *config FC_UNUSED, newp->pattern = s->fonts[f]; if (!FcCompare (p, newp->pattern, newp->score, result, &data)) goto bail1; + /* TODO: Should we check a FcPattern in FcFontSet? + * This way may not work if someone has own list of application fonts + * That said, just to reduce the cost for lookup so far. + */ + if (config->prefer_app_fonts && s != config->fonts[FcSetApplication]) { + newp->score[PRI_ORDER] += 1000; + } if (FcDebug() & FC_DBG_MATCHV) { printf ("Score"); for (i = 0; i < PRI_END; i++) { @@ -1260,12 +1267,13 @@ FcFontSetSort (FcConfig *config FC_UNUSED, free (nodes); - if (FcDebug() & FC_DBG_MATCH) { - printf ("First font "); - FcPatternPrint (ret->fonts[0]); - } - if (ret->nfont > 0) + if (ret->nfont > 0) { *result = FcResultMatch; + if (FcDebug() & FC_DBG_MATCH) { + printf ("First font "); + FcPatternPrint (ret->fonts[0]); + } + } return ret; diff --git a/test/run-test-conf.sh b/test/run-test-conf.sh index 88407cd..e87bcc5 100644 --- a/test/run-test-conf.sh +++ b/test/run-test-conf.sh @@ -54,6 +54,7 @@ for i in \ test-issue-286.json \ test-style-match.json \ test-filter.json \ + test-appfont.json \ ; do echo $RUNNER $TESTDIR/$i ... $RUNNER $TESTDIR/../conf.d/10-autohint.conf $TESTDIR/$i diff --git a/test/test-appfont.json b/test/test-appfont.json new file mode 100644 index 0000000..4ecc15d --- /dev/null +++ b/test/test-appfont.json @@ -0,0 +1,75 @@ +{ + "fonts": [ + { + "family": [ + "Foo" + ], + "style": [ + "Regular" + ], + "lang": "en", + "charset": [ + "a", + "b" + ], + "file": "/system/path/to/Foo.ttf", + "fontversion": 1, + } + ], + "appfonts": [ + { + "family": [ + "Foo" + ], + "style": [ + "Regular" + ], + "lang": "en", + "charset": [ + "a", + "b" + ], + "file": "/app/path/to/Foo.ttf", + "fontversion": 2, + } + ], + "tests": [ + { + "config": { + "prefer_app_font": false + }, + "method": "sort", + "query": { + "family": "Foo", + "style": "Regular" + }, + "result_fs": [ + { + "family": "Foo", + "style": "Regular", + "file": "/system/path/to/Foo.ttf", + "fontversion": 1 + } + ] + }, + { + "config": { + "prefer_app_font": true + }, + "method": "sort", + "query": { + "family": "Foo", + "style": "Regular" + }, + "result_fs": [ + { + "family": "Foo", + "style": "Regular", + "file": "/app/path/to/Foo.ttf", + "fontversion": 2 + } + ] + } + + ] +} diff --git a/test/test-conf.c b/test/test-conf.c index f73c983..54e5d70 100644 --- a/test/test-conf.c +++ b/test/test-conf.c @@ -42,6 +42,25 @@ struct _FcConfig { FcFontSet *fonts[FcSetApplication + 1]; }; +static void +apply_config (FcConfig *config, json_object *obj) +{ + json_object_iter iter; + + json_object_object_foreachC (obj, iter) + { + if (strcmp (iter.key, "prefer_app_font") == 0) { + if (json_object_get_type (iter.val) != json_type_boolean) { + fprintf (stderr, "W: invalid type of prefer_app_font: (%s)\n", json_type_to_name (json_object_get_type (iter.val))); + continue; + } + FcConfigPreferAppFont (config, json_object_get_boolean (iter.val)); + } else { + fprintf (stderr, "W: unknown object in config: %s\n", iter.key); + } + } +} + static FcPattern * build_pattern (json_object *obj) { @@ -137,7 +156,7 @@ build_pattern (json_object *obj) continue; } if (nchar != 1) { - fprintf (stderr, "E: charset entry not not one codepoint\n"); + fprintf (stderr, "E: charset entry not a codepoint\n"); FcValueDestroy (v); continue; } @@ -318,7 +337,7 @@ bail: static FcBool build_fonts (FcConfig *config, json_object *root) { - json_object *fonts, *filter; + json_object *fonts, *filter, *appfonts; FcFontSet *fs; FcPattern *filterpat; @@ -340,6 +359,16 @@ build_fonts (FcConfig *config, json_object *root) if (config->fonts[FcSetSystem]) FcFontSetDestroy (config->fonts[FcSetSystem]); config->fonts[FcSetSystem] = fs; + if (json_object_object_get_ex (root, "appfonts", &appfonts)) { + if (json_object_get_type (appfonts) != json_type_array) { + fprintf (stderr, "W: Invalid appfonts defined\n"); + return FcFalse; + } + fs = build_fs (config, appfonts, FcTrue); + if (config->fonts[FcSetApplication]) + FcFontSetDestroy (config->fonts[FcSetApplication]); + config->fonts[FcSetApplication] = fs; + } return FcTrue; } @@ -348,7 +377,7 @@ static FcBool run_test (FcConfig *config, json_object *root) { json_object *tests; - int i, n, fail = 0; + int i, j, n, fail = 0; if (!json_object_object_get_ex (root, "tests", &tests) || json_object_get_type (tests) != json_type_array) { @@ -361,15 +390,22 @@ run_test (FcConfig *config, json_object *root) json_object_iter iter; FcPattern *query = NULL; FcPattern *result = NULL; - FcPattern *filterpat = NULL; FcFontSet *result_fs = NULL; const char *method = NULL; + FcFontSet *fs = NULL; + FcResult res; if (json_object_get_type (obj) != json_type_object) continue; json_object_object_foreachC (obj, iter) { - if (strcmp (iter.key, "method") == 0) { + if (strcmp (iter.key, "config") == 0) { + if (json_object_get_type (iter.val) != json_type_object) { + fprintf (stderr, "W: invalid type of config: (%s)\n", json_type_to_name (json_object_get_type (iter.val))); + continue; + } + apply_config (config, iter.val); + } else if (strcmp (iter.key, "method") == 0) { if (json_object_get_type (iter.val) != json_type_string) { fprintf (stderr, "W: invalid type of method: (%s)\n", json_type_to_name (json_object_get_type (iter.val))); continue; @@ -407,7 +443,6 @@ run_test (FcConfig *config, json_object *root) } if (method != NULL && strcmp (method, "match") == 0) { FcPattern *match = NULL; - FcResult res; if (!query) { fprintf (stderr, "E: no query defined.\n"); @@ -467,8 +502,6 @@ run_test (FcConfig *config, json_object *root) } } } else if (method != NULL && strcmp (method, "list") == 0) { - FcFontSet *fs = NULL; - if (!query) { fprintf (stderr, "E: no query defined.\n"); fail++; @@ -484,8 +517,7 @@ run_test (FcConfig *config, json_object *root) fprintf (stderr, "E: failed on FcFontList\n"); fail++; } else { - int i; - + process_fs: if (fs->nfont != result_fs->nfont) { printf ("E: The number of results is different:\n"); printf (" actual result: %d\n", fs->nfont); @@ -493,26 +525,26 @@ run_test (FcConfig *config, json_object *root) fail++; goto bail2; } - for (i = 0; i < fs->nfont; i++) { + for (j = 0; j < fs->nfont; j++) { FcPatternIter iter; int x, vc; - FcPatternIterStart (result_fs->fonts[i], &iter); + FcPatternIterStart (result_fs->fonts[j], &iter); do { - vc = FcPatternIterValueCount (result_fs->fonts[i], &iter); + vc = FcPatternIterValueCount (result_fs->fonts[j], &iter); for (x = 0; x < vc; x++) { FcValue vr, vm; - if (FcPatternIterGetValue (result_fs->fonts[i], &iter, x, &vr, NULL) != FcResultMatch) { + if (FcPatternIterGetValue (result_fs->fonts[j], &iter, x, &vr, NULL) != FcResultMatch) { fprintf (stderr, "E: unable to obtain a value from the expected result\n"); fail++; goto bail2; } - if (FcPatternGet (fs->fonts[i], FcPatternIterGetObject (result_fs->fonts[i], &iter), x, &vm) != FcResultMatch) { + if (FcPatternGet (fs->fonts[j], FcPatternIterGetObject (result_fs->fonts[j], &iter), x, &vm) != FcResultMatch) { vm.type = FcTypeVoid; } if (!FcValueEqual (vm, vr)) { - printf ("E: failed to compare %s:\n", FcPatternIterGetObject (result_fs->fonts[i], &iter)); + printf ("E: failed to compare %s:\n", FcPatternIterGetObject (result_fs->fonts[j], &iter)); printf (" actual result:"); FcValuePrint (vm); printf ("\n expected result:"); @@ -522,12 +554,32 @@ run_test (FcConfig *config, json_object *root) goto bail2; } } - } while (FcPatternIterNext (result_fs->fonts[i], &iter)); + } while (FcPatternIterNext (result_fs->fonts[j], &iter)); } bail2: if (fs) FcFontSetDestroy (fs); } + } else if (method != NULL && + (strcmp (method, "sort") == 0 || + strcmp (method, "sort_all") == 0)) { + if (!query) { + fprintf (stderr, "E: no query defined.\n"); + fail++; + goto bail2; + } + if (!result_fs) { + fprintf (stderr, "E: no result_fs defined.\n"); + fail++; + goto bail2; + } + fs = FcFontSort (config, query, method[4] == 0 ? FcTrue : FcFalse, NULL, &res); + if (!fs) { + fprintf (stderr, "E: failed on FcFontSort\n"); + fail++; + } else { + goto process_fs; + } } else { fprintf (stderr, "W: unknown testing method: %s\n", method); } commit d89c253ab4ea29a2976a0b8eb6e524ddb9975762 Author: Akira TAGOH <akira@xxxxxxxxx> Date: Thu Apr 24 18:41:19 2025 +0900 Make sure that the debugging facilities are initialized at loading config phase Changelog: fixed diff --git a/src/fcxml.c b/src/fcxml.c index d2e25fc..328e109 100644 --- a/src/fcxml.c +++ b/src/fcxml.c @@ -3246,6 +3246,8 @@ FcConfigParseAndLoadFromMemoryInternal (FcConfig *config, size_t buflen; #endif + FcInitDebug(); + if (!buffer) return FcFalse; len = strlen ((const char *)buffer);