From 6c8348a598cd79874007e233b6f0f409185093b1 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Tue, 1 Sep 2015 15:47:40 +0200 Subject: directwrite, coretext: implement substitutions This adds simple and sensible substitutions for generic font family names. A helper function is introduced to reduce code duplication. --- libass/ass_coretext.c | 14 ++++++++++++++ libass/ass_directwrite.c | 14 ++++++++++++++ libass/ass_fontselect.h | 31 +++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/libass/ass_coretext.c b/libass/ass_coretext.c index 3553324..877018d 100644 --- a/libass/ass_coretext.c +++ b/libass/ass_coretext.c @@ -25,6 +25,12 @@ #define SAFE_CFRelease(x) do { if (x) CFRelease(x); } while(0) +static const ASS_FontMapping font_substitutions[] = { + {"sans-serif", "Helvetica"}, + {"serif", "Times"}, + {"monospace", "Courier"} +}; + static char *cfstr2buf(CFStringRef string) { const int encoding = kCFStringEncodingUTF8; @@ -266,10 +272,18 @@ static char *get_fallback(void *priv, const char *family, uint32_t codepoint) return res_family; } +static void get_substitutions(void *priv, const char *name, + ASS_FontProviderMetaData *meta) +{ + const int n = sizeof(font_substitutions) / sizeof(font_substitutions[0]); + ass_map_font(font_substitutions, n, name, meta); +} + static ASS_FontProviderFuncs coretext_callbacks = { .check_glyph = check_glyph, .destroy_font = destroy_font, .match_fonts = match_fonts, + .get_substitutions = get_substitutions, .get_fallback = get_fallback, }; diff --git a/libass/ass_directwrite.c b/libass/ass_directwrite.c index 86b0b4b..f11deb6 100644 --- a/libass/ass_directwrite.c +++ b/libass/ass_directwrite.c @@ -31,6 +31,12 @@ #define NAME_MAX_LENGTH 256 #define FALLBACK_DEFAULT_FONT L"Arial" +static const ASS_FontMapping font_substitutions[] = { + {"sans-serif", "Arial"}, + {"serif", "Times New Roman"}, + {"monospace", "Courier New"} +}; + /* * The private data stored for every font, detected by this backend. */ @@ -624,6 +630,13 @@ static void scan_fonts(IDWriteFactory *factory, } } +static void get_substitutions(void *priv, const char *name, + ASS_FontProviderMetaData *meta) +{ + const int n = sizeof(font_substitutions) / sizeof(font_substitutions[0]); + ass_map_font(font_substitutions, n, name, meta); +} + /* * Called by libass when the provider should perform the * specified task @@ -633,6 +646,7 @@ static ASS_FontProviderFuncs directwrite_callbacks = { .check_glyph = check_glyph, .destroy_font = destroy_font, .destroy_provider = destroy_provider, + .get_substitutions = get_substitutions, .get_fallback = get_fallback, }; diff --git a/libass/ass_fontselect.h b/libass/ass_fontselect.h index c989e1a..1e0959e 100644 --- a/libass/ass_fontselect.h +++ b/libass/ass_fontselect.h @@ -177,6 +177,37 @@ struct ass_font_stream { void *priv; }; + +typedef struct ass_font_mapping ASS_FontMapping; + +struct ass_font_mapping { + const char *from; + const char *to; +}; + +/** + * Simple font substitution helper. This can be used to implement basic + * mappings from one name to another. This is useful for supporting + * generic font families in font providers. + * + * \param map list of mappings + * \param len length of list of mappings + * \param name font name to map from + * \param meta metadata struct, mapped fonts will be stored into this + */ +inline void ass_map_font(const ASS_FontMapping *map, int len, const char *name, + ASS_FontProviderMetaData *meta) +{ + for (int i = 0; i < len; i++) { + if (strcasecmp(map[i].from, name) == 0) { + meta->n_fullname = 1; + meta->fullnames = calloc(1, sizeof(char *)); + meta->fullnames[0] = strdup(map[i].to); + return; + } + } +} + ASS_FontSelector * ass_fontselect_init(ASS_Library *library, FT_Library ftlibrary, const char *family, -- cgit v1.2.3 From 59e266bd41afd7f022ed293b9f61afa3481e0321 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Thu, 3 Sep 2015 00:04:59 +0200 Subject: fontselect: fix match_fonts semantics We don't want to add fonts multiple times, so call match_fonts lazily, i.e. only after selecting a font with a certain name failed. Since font matching interacts with glyph coverage checks, add a simple mechanism to determine whether matching failed because of name or glyph coverage. Additionally make sure to handle substitutions before any calls to match_fonts; this only correctly deals with single-name substitutions, though. --- libass/ass_fontselect.c | 82 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/libass/ass_fontselect.c b/libass/ass_fontselect.c index 95d8c95..fb09088 100644 --- a/libass/ass_fontselect.c +++ b/libass/ass_fontselect.c @@ -477,41 +477,19 @@ static int check_glyph(ASS_FontInfo *fi, uint32_t code) return provider->funcs.check_glyph(fi->priv, code); } -static char *select_font(ASS_FontSelector *priv, ASS_Library *library, - const char *family, unsigned bold, unsigned italic, - int *index, char **postscript_name, int *uid, - ASS_FontStream *stream, uint32_t code) +static char * +find_font(ASS_FontSelector *priv, ASS_Library *library, + ASS_FontProviderMetaData meta, unsigned bold, unsigned italic, + int *index, char **postscript_name, int *uid, ASS_FontStream *stream, + uint32_t code, bool *name_match) { ASS_FontInfo req = {0}; - char *family_trim = strdup_trimmed(family); - ASS_FontProvider *default_provider = priv->default_provider; - ASS_FontProviderMetaData meta = {0}; ASS_FontInfo *selected = NULL; - if (family_trim == NULL) - return NULL; - - if (default_provider && default_provider->funcs.match_fonts) - default_provider->funcs.match_fonts(library, default_provider, family_trim); - // do we actually have any fonts? if (!priv->n_font) return NULL; - ASS_FontProviderMetaData default_meta = { - .n_fullname = 1, - .fullnames = &family_trim, - }; - - // get a list of substitutes if applicable, and use it for matching - if (default_provider && default_provider->funcs.get_substitutions) { - default_provider->funcs.get_substitutions(default_provider->priv, - family_trim, &meta); - } - if (!meta.n_fullname) { - meta = default_meta; - } - // fill font request req.slant = italic; req.weight = bold; @@ -530,11 +508,13 @@ static char *select_font(ASS_FontSelector *priv, ASS_Library *library, // If there's a family match, compare font attributes // to determine best match in that particular family score = font_attributes_similarity(font, &req); + *name_match = true; } else if (matches_fullname(font, fullname)) { // If we don't have any match, compare fullnames against request // if there is a match now, assign lowest score possible. This means // the font should be chosen instantly, without further search. score = 0; + *name_match = true; } // Consider updating idx if score is better than current minimum @@ -587,6 +567,54 @@ static char *select_font(ASS_FontSelector *priv, ASS_Library *library, result = strdup(selected->path); } + return result; +} + +static char *select_font(ASS_FontSelector *priv, ASS_Library *library, + const char *family, unsigned bold, unsigned italic, + int *index, char **postscript_name, int *uid, + ASS_FontStream *stream, uint32_t code) +{ + ASS_FontProvider *default_provider = priv->default_provider; + ASS_FontProviderMetaData meta = {0}; + char *family_trim = strdup_trimmed(family); + char *result = NULL; + bool name_match = false; + + if (family_trim == NULL) + return NULL; + + ASS_FontProviderMetaData default_meta = { + .n_fullname = 1, + .fullnames = &family_trim, + }; + + // Get a list of substitutes if applicable, and use it for matching. + if (default_provider && default_provider->funcs.get_substitutions) { + default_provider->funcs.get_substitutions(default_provider->priv, + family_trim, &meta); + } + + if (!meta.n_fullname) { + meta = default_meta; + } + + result = find_font(priv, library, meta, bold, italic, index, + postscript_name, uid, stream, code, &name_match); + + // If no matching font was found, it might not exist in the font list + // yet. Call the match_fonts callback to fill in the missing fonts + // on demand, and retry the search for a match. + if (result == NULL && name_match == false && default_provider && + default_provider->funcs.match_fonts) { + // FIXME: what if substitution adds more than one alias? + default_provider->funcs.match_fonts(library, default_provider, + meta.fullnames[0]); + result = find_font(priv, library, meta, bold, italic, index, + postscript_name, uid, stream, code, &name_match); + } + + // cleanup free(family_trim); if (meta.fullnames != default_meta.fullnames) { for (int i = 0; i < meta.n_fullname; i++) -- cgit v1.2.3 From 6cf20043f5fa7c6aacf3abd49d94b350a24f6646 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Mon, 7 Sep 2015 01:29:27 +0200 Subject: fontconfig: handle fallback corner cases If no particular codepoint is requested (codepoint == 0), just return the first font family. Additionally, handle fontconfig errors, albeit they're unlikely to happen. --- libass/ass_fontconfig.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/libass/ass_fontconfig.c b/libass/ass_fontconfig.c index e97102b..d91dc6c 100644 --- a/libass/ass_fontconfig.c +++ b/libass/ass_fontconfig.c @@ -177,6 +177,17 @@ static char *get_fallback(void *priv, const char *family, uint32_t codepoint) if (!fc->fallbacks || fc->fallbacks->nfont == 0) return NULL; + if (codepoint == 0) { + char *family = NULL; + result = FcPatternGetString(fc->fallbacks->fonts[0], FC_FAMILY, 0, + (FcChar8 **)&family); + if (result == FcResultMatch) { + return strdup(family); + } else { + return NULL; + } + } + // fallback_chars is the union of all available charsets, so // if we can't find the glyph in there, the system does not // have any font to render this glyph. @@ -194,8 +205,11 @@ static char *get_fallback(void *priv, const char *family, uint32_t codepoint) char *family = NULL; result = FcPatternGetString(pattern, FC_FAMILY, 0, (FcChar8 **)&family); - family = strdup(family); - return family; + if (result == FcResultMatch) { + return strdup(family); + } else { + return NULL; + } } } -- cgit v1.2.3 From d0f2a97bfbfb1c87bbc66264df1ed194b12a6975 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Mon, 7 Sep 2015 01:34:48 +0200 Subject: fontselect: fix fallback family fallback --- libass/ass_fontselect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libass/ass_fontselect.c b/libass/ass_fontselect.c index fb09088..0c37bb5 100644 --- a/libass/ass_fontselect.c +++ b/libass/ass_fontselect.c @@ -665,7 +665,7 @@ char *ass_font_select(ASS_FontSelector *priv, ASS_Library *library, if (!search_family || !*search_family) search_family = "Arial"; char *fallback_family = default_provider->funcs.get_fallback( - default_provider->priv, family, code); + default_provider->priv, search_family, code); if (fallback_family) { res = select_font(priv, library, fallback_family, bold, italic, -- cgit v1.2.3 From e020bee2631950815ec7707d5a6f8a348de3a2d7 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Mon, 7 Sep 2015 02:12:43 +0200 Subject: test: use proper fallback name The correct generic family name is "sans-serif", the short form "Sans" is specific to fontconfig only. --- test/test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.c b/test/test.c index 175e8be..4884b28 100644 --- a/test/test.c +++ b/test/test.c @@ -107,7 +107,7 @@ static void init(int frame_w, int frame_h) } ass_set_frame_size(ass_renderer, frame_w, frame_h); - ass_set_fonts(ass_renderer, NULL, "Sans", + ass_set_fonts(ass_renderer, NULL, "sans-serif", ASS_FONTPROVIDER_AUTODETECT, NULL, 1); } -- cgit v1.2.3 From db8856e6485a5f8bab23869f49477aeb2a227328 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Mon, 7 Sep 2015 10:27:26 +0200 Subject: fontselect: call match_fonts for each alias In case a font provider actually uses more than one substitution. --- libass/ass_fontselect.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libass/ass_fontselect.c b/libass/ass_fontselect.c index 0c37bb5..fbdface 100644 --- a/libass/ass_fontselect.c +++ b/libass/ass_fontselect.c @@ -607,9 +607,12 @@ static char *select_font(ASS_FontSelector *priv, ASS_Library *library, // on demand, and retry the search for a match. if (result == NULL && name_match == false && default_provider && default_provider->funcs.match_fonts) { - // FIXME: what if substitution adds more than one alias? - default_provider->funcs.match_fonts(library, default_provider, - meta.fullnames[0]); + // TODO: consider changing the API to make more efficient + // implementations possible. + for (int i = 0; i < meta.n_fullname; i++) { + default_provider->funcs.match_fonts(library, default_provider, + meta.fullnames[i]); + } result = find_font(priv, library, meta, bold, italic, index, postscript_name, uid, stream, code, &name_match); } -- cgit v1.2.3 From 7fe2c2ec64d9ea9478d140639d66cd017d80e720 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Mon, 7 Sep 2015 10:48:17 +0200 Subject: fontconfig: fix memory leak in error path Found by clang static analysis. --- libass/ass_fontconfig.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libass/ass_fontconfig.c b/libass/ass_fontconfig.c index d91dc6c..184f090 100644 --- a/libass/ass_fontconfig.c +++ b/libass/ass_fontconfig.c @@ -289,6 +289,7 @@ ass_fontconfig_add_provider(ASS_Library *lib, ASS_FontSelector *selector, ass_msg(lib, MSGL_FATAL, "No valid fontconfig configuration found!"); FcConfigDestroy(fc->config); + free(fc); return NULL; } -- cgit v1.2.3 From 4a560cf94634f0c704c60cc75a514dddd0af6e1d Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Mon, 7 Sep 2015 10:56:59 +0200 Subject: fontselect: remove outdated remark fontselect is an internal only API for now. That said, it also seems much more sane to let library users deal with this resource management. --- libass/ass_fontselect.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libass/ass_fontselect.c b/libass/ass_fontselect.c index fbdface..a71ad4e 100644 --- a/libass/ass_fontselect.c +++ b/libass/ass_fontselect.c @@ -1031,10 +1031,6 @@ void ass_fontselect_free(ASS_FontSelector *priv) if (priv->embedded_provider) ass_font_provider_free(priv->embedded_provider); - // XXX: not quite sure, maybe we should track all registered - // providers and free them right here. or should that be the - // responsibility of the library user? - free(priv->font_infos); free(priv->path_default); free(priv->family_default); -- cgit v1.2.3 From af6a9194b19f3073e7044c48635f1adc332fca05 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Mon, 7 Sep 2015 11:03:28 +0200 Subject: fontselect: improve font display name choice --- libass/ass_fontselect.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libass/ass_fontselect.c b/libass/ass_fontselect.c index a71ad4e..2be6549 100644 --- a/libass/ass_fontselect.c +++ b/libass/ass_fontselect.c @@ -560,9 +560,13 @@ find_font(ASS_FontSelector *priv, ASS_Library *library, ASS_FontProvider *provider = selected->provider; stream->func = provider->funcs.get_data; stream->priv = selected->priv; - // FIXME: we should define a default family name in some way, - // possibly the first (or last) English name - result = strdup(selected->families[0]); + // Prefer PostScript name because it is unique. This is only + // used for display purposes so it doesn't matter that much, + // though. + if (selected->postscript_name) + result = strdup(selected->postscript_name); + else + result = strdup(selected->families[0]); } else result = strdup(selected->path); } -- cgit v1.2.3 From d6bb9af645526a810512eaf2c65252ac0e0e6b36 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Mon, 7 Sep 2015 11:06:17 +0200 Subject: fontselect: get rid of strdup for display name The name is always pulled from the font info, which is static, so there is no need to strdup. --- libass/ass_font.c | 5 ----- libass/ass_fontselect.c | 6 +++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/libass/ass_font.c b/libass/ass_font.c index 0164b67..b3c639d 100644 --- a/libass/ass_font.c +++ b/libass/ass_font.c @@ -153,7 +153,6 @@ static int add_face(ASS_FontSelector *fontsel, ASS_Font *font, uint32_t ch) if (font->faces_uid[i] == uid) { ass_msg(font->library, MSGL_INFO, "Got a font face that already is available! Skipping."); - free(path); return i; } } @@ -178,7 +177,6 @@ static int add_face(ASS_FontSelector *fontsel, ASS_Font *font, uint32_t ch) if (error) { ass_msg(font->library, MSGL_WARN, "Error opening memory font: '%s'", path); - free(path); return -1; } @@ -187,7 +185,6 @@ static int add_face(ASS_FontSelector *fontsel, ASS_Font *font, uint32_t ch) if (error) { ass_msg(font->library, MSGL_WARN, "Error opening font: '%s', %d", path, index); - free(path); return -1; } @@ -201,7 +198,6 @@ static int add_face(ASS_FontSelector *fontsel, ASS_Font *font, uint32_t ch) if (error) { ass_msg(font->library, MSGL_WARN, "Error opening font: '%s', %d", path, i); - free(path); return -1; } @@ -217,7 +213,6 @@ static int add_face(ASS_FontSelector *fontsel, ASS_Font *font, uint32_t ch) font->faces[font->n_faces] = face; font->faces_uid[font->n_faces++] = uid; ass_face_set_size(face, font->size); - free(path); return font->n_faces - 1; } diff --git a/libass/ass_fontselect.c b/libass/ass_fontselect.c index 2be6549..d48b170 100644 --- a/libass/ass_fontselect.c +++ b/libass/ass_fontselect.c @@ -564,11 +564,11 @@ find_font(ASS_FontSelector *priv, ASS_Library *library, // used for display purposes so it doesn't matter that much, // though. if (selected->postscript_name) - result = strdup(selected->postscript_name); + result = selected->postscript_name; else - result = strdup(selected->families[0]); + result = selected->families[0]; } else - result = strdup(selected->path); + result = selected->path; } return result; -- cgit v1.2.3