doc/fcconfig.fncs | 31 ++++++++++++++++- fontconfig/fontconfig.h | 13 +++++++ src/fccfg.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++-- src/fcinit.c | 27 ++++++++++++-- src/fcint.h | 6 ++- test/Makefile.am | 4 ++ test/run-test-conf.sh | 1 test/test-conf.c | 64 ++++++++++++++++++++++++++++++++--- test/test-filter.c | 59 ++++++++++++++++++++++++++++++++ test/test-filter.json | 65 +++++++++++++++++++++++++++++++++++ 10 files changed, 342 insertions(+), 15 deletions(-) New commits: commit bd83c04aa6f3cb864ba60dc5eaf2b41c4c269c63 Merge: be2a400 94614ac Author: Akira TAGOH <akira@xxxxxxxxx> Date: Fri Aug 9 14:31:08 2024 +0000 Merge branch 'filter-fs' into 'main' Add FcConfigSetFontSetFilter See merge request fontconfig/fontconfig!328 commit 94614ac73cf412e0d43ecc31dcce57071cfa6be4 Author: Akira TAGOH <akira@xxxxxxxxx> Date: Fri Aug 9 22:04:28 2024 +0900 Add FcConfigSetFontSetFilter To pre-filtering when loading fonts from caches. FcFontSet in FcConfig will be rebuilt if FcConfig is already initialized. diff --git a/doc/fcconfig.fncs b/doc/fcconfig.fncs index 49e691e..0a072af 100644 --- a/doc/fcconfig.fncs +++ b/doc/fcconfig.fncs @@ -71,6 +71,36 @@ in <parameter>config</parameter> since 2.12.0, returning FcFalse if that call fa Returns the current default configuration. @@ +@RET@ FcConfig * +@FUNC@ FcConfigSetFontSetFilter +@TYPE1@ FcConfig * @ARG1@ config +@TYPE2@ FcFilterFontSetFunc% @ARG2@ filter_func +@TYPE3@ FcDestroyFunc% @ARG3@ destroy_data_func +@TYPE4@ void * @ARG4@ user_data +@PURPOSE@ Set a predicate function to filter fontsets +@DESC@ +Sets <parameter>filter_func</parameter> as a predicate function and filter out +fontsets in <parameter>config</parameter> as desired. +<parameter>filter_func</parameter> will be called with a font pattern and +<parameter>user_data</parameter> only when loading caches. +When <parameter>config</parameter> is going to be destroyed, +<parameter>user_data</parameter> will be destroyed through +<parameter>destroy_data_func</parameter> if it is set. +@SINCE@ 2.16.0 +@@ + +@RET@ FcBool +@FUNC@ FcConfigAcceptFilter +@TYPE1@ FcConfig * @ARG1@ config +@TYPE2@ const FcPattern * @ARG2@ font +@PURPOSE@ Test whether the given pattern matches filter +@DESC@ +This triggers a predicate function set by <function>FcConfigSetFontSetFilter</function> +and return FcTrue if <parameter>font</parameter> matches something they expect. +otherwise FcFalse. +@SINCE@ 2.16.0 +@@ + @RET@ FcBool @FUNC@ FcConfigUptoDate @TYPE1@ FcConfig * @ARG1@ config @@ -514,4 +544,3 @@ in configuration file. This function tries to match 'pat' with them and return FcFalse if 'pat' is rejected, otherwise FcTrue. @SINCE@ 2.15.1 @@ - diff --git a/fontconfig/fontconfig.h b/fontconfig/fontconfig.h index 47d7b83..d7df876 100644 --- a/fontconfig/fontconfig.h +++ b/fontconfig/fontconfig.h @@ -337,6 +337,9 @@ typedef struct _FcStrSet FcStrSet; typedef struct _FcCache FcCache; +typedef void (* FcDestroyFunc) (void *data); +typedef FcBool (* FcFilterFontSetFunc) (const FcPattern *font, void *user_data); + _FCFUNCPROTOBEGIN /* fcblanks.c */ @@ -457,6 +460,10 @@ FcPublic FcBool FcConfigAcceptFont (FcConfig *config, const FcPattern *font); +FcPublic FcBool +FcConfigAcceptFilter (FcConfig *config, + const FcPattern *font); + FcPublic FcBool FcConfigAppFontAddFile (FcConfig *config, const FcChar8 *file); @@ -486,6 +493,12 @@ FcPublic void FcConfigSetSysRoot (FcConfig *config, const FcChar8 *sysroot); +FcPublic FcConfig * +FcConfigSetFontSetFilter (FcConfig *config, + FcFilterFontSetFunc filter_func, + FcDestroyFunc destroy_data_func, + void *user_data); + FcPublic void FcConfigFileInfoIterInit (FcConfig *config, FcConfigFileInfoIter *iter); diff --git a/src/fccfg.c b/src/fccfg.c index 41ee611..d1f5856 100644 --- a/src/fccfg.c +++ b/src/fccfg.c @@ -25,6 +25,7 @@ /* Objects MT-safe for readonly access. */ #include "fcint.h" +#include "fontconfig/fontconfig.h" #ifdef HAVE_DIRENT_H #include <dirent.h> #endif @@ -202,6 +203,10 @@ FcConfigCreate (void) if (!config->availConfigFiles) goto bail10; + config->filter_func = NULL; + config->filter_data = NULL; + config->destroy_data_func = NULL; + FcRefInit (&config->ref, 1); return config; @@ -390,6 +395,9 @@ FcConfigDestroy (FcConfig *config) if (config->sysRoot) FcStrFree (config->sysRoot); + if (config->filter_data && config->destroy_data_func) + config->destroy_data_func (config->filter_data); + free (config); } } @@ -453,10 +461,18 @@ FcConfigAddCache (FcConfig *config, FcCache *cache, continue; } + /* + * Check to see if font is banned by client + */ + if (!FcConfigAcceptFilter (config, font)) + { + free (relocated_font_file); + continue; + } if (relocated_font_file) { - font = FcPatternCacheRewriteFile (font, cache, relocated_font_file); - free (relocated_font_file); + font = FcPatternCacheRewriteFile (font, cache, relocated_font_file); + free (relocated_font_file); } if (FcFontSetAdd (config->fonts[set], font)) @@ -811,6 +827,63 @@ FcConfigSetFonts (FcConfig *config, config->fonts[set] = fonts; } +FcConfig * +FcConfigSetFontSetFilter (FcConfig *config, + FcFilterFontSetFunc filter_func, + FcDestroyFunc destroy_data_func, + void *user_data) +{ + FcBool rebuild = FcFalse; + + if (!config) + { + /* Do not use FcConfigEnsure() here for optimization */ + retry: + config = fc_atomic_ptr_get (&_fcConfig); + if (!config) + config = FcConfigCreate (); + else + rebuild = FcTrue; + } + else + rebuild = FcTrue; + if (config->filter_data == user_data && + config->filter_func == filter_func) + { + /* No need to update */ + rebuild = FcFalse; + } + else + { + if (config->filter_data && config->destroy_data_func) + { + config->destroy_data_func (config->filter_data); + } + config->filter_func = filter_func; + config->destroy_data_func = destroy_data_func; + config->filter_data = user_data; + } + + if (rebuild) + { + /* Rebuild FontSet */ + FcConfigBuildFonts (config); + } + else + { + /* Initialize FcConfig with regular procedure */ + config = FcInitLoadOwnConfigAndFonts (config); + + if (!config || !fc_atomic_ptr_cmpexch (&_fcConfig, NULL, config)) + { + if (config) + FcConfigDestroy (config); + goto retry; + } + } + + return config; +} FcBlanks * FcBlanksCreate (void) @@ -2985,6 +3058,16 @@ FcConfigAcceptFont (FcConfig *config, return FcTrue; } +FcBool +FcConfigAcceptFilter (FcConfig *config, + const FcPattern *font) +{ + if (config && config->filter_func) + { + return config->filter_func (font, config->filter_data); + } + return FcTrue; +} const FcChar8 * FcConfigGetSysRoot (const FcConfig *config) { diff --git a/src/fcinit.c b/src/fcinit.c index c05cdc5..e267821 100644 --- a/src/fcinit.c +++ b/src/fcinit.c @@ -65,6 +65,26 @@ bail0: return 0; } +static FcConfig * +FcInitFallbackConfigWithFilter (FcConfig *config, const FcChar8 *sysroot) +{ + FcConfig *fallback = FcInitFallbackConfig (sysroot); + + /* Copy filter data */ + fallback->filter_func = config->filter_func; + fallback->filter_data = config->filter_data; + fallback->destroy_data_func = config->destroy_data_func; + config->filter_func = NULL; + config->filter_data = NULL; + config->destroy_data_func = NULL; + /* Rebuild fontset */ + FcConfigBuildFonts (fallback); + + FcConfigDestroy (config); + + return fallback; +} + int FcGetVersion (void) { @@ -89,9 +109,7 @@ FcInitLoadOwnConfig (FcConfig *config) if (!FcConfigParseAndLoad (config, 0, FcTrue)) { const FcChar8 *sysroot = FcConfigGetSysRoot (config); - FcConfig *fallback = FcInitFallbackConfig (sysroot); - - FcConfigDestroy (config); + FcConfig *fallback = FcInitFallbackConfigWithFilter (config, sysroot); return fallback; } @@ -144,8 +162,7 @@ FcInitLoadOwnConfig (FcConfig *config) "Fontconfig error: out of memory"); if (prefix) FcStrFree (prefix); - fallback = FcInitFallbackConfig (sysroot); - FcConfigDestroy (config); + fallback = FcInitFallbackConfigWithFilter (config, sysroot); return fallback; } diff --git a/src/fcint.h b/src/fcint.h index 86676b3..8a3c0ac 100644 --- a/src/fcint.h +++ b/src/fcint.h @@ -328,8 +328,6 @@ typedef struct _FcEdit { FcValueBinding binding; } FcEdit; -typedef void (* FcDestroyFunc) (void *data); - typedef struct _FcPtrList FcPtrList; /* need to sync with FcConfigFileInfoIter at fontconfig.h */ typedef struct _FcPtrListIter { @@ -580,6 +578,10 @@ struct _FcConfig { FcChar8 *sysRoot; /* override the system root directory */ FcStrSet *availConfigFiles; /* config files available */ FcPtrList *rulesetList; /* List of rulesets being installed */ + + 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 */ }; typedef struct _FcFileTime { diff --git a/test/Makefile.am b/test/Makefile.am index 3b79c78..3b4e3e8 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -49,6 +49,7 @@ TESTDATA = \ test-70-no-bitmaps-and-emoji.json \ test-70-no-bitmaps-except-emoji.json \ test-90-synthetic.json \ + test-filter.json \ test-issue-286.json \ test-style-match.json \ $(NULL) @@ -176,6 +177,9 @@ check_PROGRAMS += test-family-matching test_family_matching_LDADD = $(top_builddir)/src/libfontconfig.la TESTS += test-family-matching +check_PROGRAMS += test-filter +test_filter_LDADD = $(top_builddir)/src/libfontconfig.la + EXTRA_DIST=run-test.sh run-test-conf.sh wrapper-script.sh $(TESTDATA) out.expected-long-family-names out.expected-no-long-family-names CLEANFILES = \ diff --git a/test/run-test-conf.sh b/test/run-test-conf.sh index cc41185..88407cd 100644 --- a/test/run-test-conf.sh +++ b/test/run-test-conf.sh @@ -53,6 +53,7 @@ done for i in \ test-issue-286.json \ test-style-match.json \ + test-filter.json \ ; do echo $RUNNER $TESTDIR/$i ... $RUNNER $TESTDIR/../conf.d/10-autohint.conf $TESTDIR/$i diff --git a/test/test-conf.c b/test/test-conf.c index 8b298ef..b5f702b 100644 --- a/test/test-conf.c +++ b/test/test-conf.c @@ -278,7 +278,7 @@ build_pattern (json_object *obj) } static FcFontSet * -build_fs (FcConfig *config, json_object *obj) +build_fs (FcConfig *config, json_object *obj, FcBool filter) { FcFontSet *fs = FcFontSetCreate (); int i, n; @@ -292,7 +292,8 @@ build_fs (FcConfig *config, json_object *obj) if (json_object_get_type (o) != json_type_object) continue; pat = build_pattern (o); - if (FcConfigAcceptFont (config, pat)) + if (FcConfigAcceptFont (config, pat) && + (!filter || FcConfigAcceptFilter (config, pat))) FcFontSetAdd (fs, pat); else FcPatternDestroy(pat); @@ -301,19 +302,71 @@ build_fs (FcConfig *config, json_object *obj) return fs; } +static FcBool +filter_func (const FcPattern *f, void *user_data) +{ + FcPattern *filter = (FcPattern *)user_data; + FcPatternIter iter; + FcBool ret = FcTrue; + + FcPatternIterStart (filter, &iter); + if (!(ret = FcPatternIterIsValid (filter, &iter))) + goto bail; + do + { + const char *obj = FcPatternIterGetObject (filter, &iter); + int i, n = FcPatternIterValueCount(filter, &iter); + + for (i = 0; i < n; i++) + { + FcValue v, v2; + FcValueBinding b; + + if (FcPatternIterGetValue (filter, &iter, i, &v, &b) != FcResultMatch) + { + ret = FcFalse; + goto bail; + } + if (FcPatternGet (f, obj, 0, &v2) != FcResultMatch) + { + ret = FcFalse; + goto bail; + } + if (!FcValueEqual (v, v2)) + { + ret = FcFalse; + goto bail; + } + } + } while (FcPatternIterNext (filter, &iter)); +bail: + return ret; +} + static FcBool build_fonts (FcConfig *config, json_object *root) { - json_object *fonts; + json_object *fonts, *filter; FcFontSet *fs; + FcPattern *filterpat; + if (json_object_object_get_ex (root, "filter", &filter)) + { + if (json_object_get_type (filter) != json_type_object) + { + fprintf (stderr, "W: Invalid filter defined\n"); + return FcFalse; + } + filterpat = build_pattern (filter); + FcConfigSetFontSetFilter(config, filter_func, (FcDestroyFunc)FcPatternDestroy, filterpat); + } if (!json_object_object_get_ex (root, "fonts", &fonts) || json_object_get_type (fonts) != json_type_array) { fprintf (stderr, "W: No fonts defined\n"); return FcFalse; } - fs = build_fs (config, fonts); + fs = build_fs (config, fonts, FcTrue); /* FcConfigSetFonts (config, fs, FcSetSystem); */ if (config->fonts[FcSetSystem]) FcFontSetDestroy (config->fonts[FcSetSystem]); @@ -341,6 +394,7 @@ 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; @@ -388,7 +442,7 @@ run_test (FcConfig *config, json_object *root) } if (result_fs) FcFontSetDestroy (result_fs); - result_fs = build_fs (config, iter.val); + result_fs = build_fs (config, iter.val, FcFalse); } else if (strcmp (iter.key, "$comment") == 0) { diff --git a/test/test-filter.c b/test/test-filter.c new file mode 100644 index 0000000..9eebdf0 --- /dev/null +++ b/test/test-filter.c @@ -0,0 +1,59 @@ +#include <stdio.h> +#include <fontconfig/fontconfig.h> +#include <time.h> + +static FcBool +filter (const FcPattern *f, void *user_data) +{ + FcChar8 *s = NULL; + + if (FcPatternGetString (f, FC_FONT_WRAPPER, 0, &s) == FcResultMatch) + { + /* accept "SFNT" only */ + if (FcStrCmp (s, (FcChar8 *)"SFNT") == 0) + return FcTrue; + } + return FcFalse; +} + +int +main (void) +{ + FcPattern *p; + FcObjectSet *os; + FcFontSet *fs; + int i, ret = 0; + FcChar8 *s = NULL, *f; + + FcConfigSetFontSetFilter(NULL, filter, NULL, NULL); + p = FcPatternCreate (); + os = FcObjectSetBuild (FC_FAMILY, FC_STYLE, FC_FILE, FC_FONT_WRAPPER, NULL); + fs = FcFontList (NULL, p, os); + FcObjectSetDestroy (os); + FcPatternDestroy (p); + + printf ("%d matched\n", fs->nfont); + for (i = 0; i < fs->nfont; i++) + { + if (FcPatternGetString (fs->fonts[i], FC_FONT_WRAPPER, 0, &s) == FcResultMatch) + { + f = FcPatternFormat (fs->fonts[i], (FcChar8 *)"%{=fclist}\n"); + printf ("%s", f); + FcStrFree (f); + if (FcStrCmp (s, (FcChar8 *)"SFNT") != 0) + { + printf ("failed:\n"); + fail: + ret = 1; + } + } + else + { + printf ("no font wrapper\n"); + goto fail; + } + } + FcFontSetDestroy (fs); + + return ret; +} diff --git a/test/test-filter.json b/test/test-filter.json new file mode 100644 index 0000000..25bce57 --- /dev/null +++ b/test/test-filter.json @@ -0,0 +1,65 @@ +{ + "fonts": [ + { + "family": [ + "Foo" + ], + "style": [ + "Regular" + ], + "file": "/path/to/Foo.ttf", + "fontwrapper": "SFNT" + }, + { + "family": [ + "Bar" + ], + "style": [ + "Regular" + ], + "file": "/path/to/Bar.otf", + "fontwrapper": "CFF" + }, + { + "family": [ + "Baz" + ], + "style": [ + "Regular" + ], + "file": "/path/to/Baz.woff", + "fontwrapper": "WOFF" + }, + { + "family": [ + "Blah" + ], + "style": [ + "Regular" + ], + "file": "/path/to/Baz.bdf" + } + ], + "filter": { + "fontwrapper": "SFNT" + }, + "tests": [ + { + "method": "list", + "query": { + }, + "result_fs": [ + { + "family": [ + "Foo" + ], + "style": [ + "Regular" + ], + "file": "/path/to/Foo.ttf", + "fontwrapper": "SFNT" + } + ] + } + ] +}