diff --git a/aegisub/src/font_file_lister_fontconfig.cpp b/aegisub/src/font_file_lister_fontconfig.cpp index 4515ae0b4..7633371ad 100644 --- a/aegisub/src/font_file_lister_fontconfig.cpp +++ b/aegisub/src/font_file_lister_fontconfig.cpp @@ -35,56 +35,54 @@ #include #endif +#include + #ifndef AGI_PRE #include #endif namespace { - // In SSA/ASS fonts are sometimes referenced by their "full name", - // which is usually a concatenation of family name and font - // style (ex. Ottawa Bold). Full name is available from - // FontConfig pattern element FC_FULLNAME, but it is never - // used for font matching. - // Therefore, I'm removing words from the end of the name one - // by one, and adding shortened names to the pattern. It seems - // that the first value (full name in this case) has - // precedence in matching. - // An alternative approach could be to reimplement FcFontSort - // using FC_FULLNAME instead of FC_FAMILY. - int add_families(FcPattern *pat, std::string family) { - int family_cnt = 1; - FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *)family.c_str()); +FcConfig *init_fontconfig() { +#ifdef __APPLE__ + FcConfig *config = FcConfigCreate(); + std::string conf_path = agi::util::OSX_GetBundleResourcesDirectory() + "/etc/fonts/fonts.conf"; + if (FcConfigParseAndLoad(config, (unsigned char *)conf_path.c_str(), FcTrue)) + return config; - for (std::string::reverse_iterator p = family.rbegin(); p != family.rend(); ++p) { - if (*p == ' ' || *p == '-') { - *p = '\0'; - FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *)family.c_str()); - ++family_cnt; + LOG_E("font_collector/fontconfig") << "Loading fontconfig configuration file failed"; + FcConfigDestroy(config); +#endif + return FcInitLoadConfig(); +} + +void find_font(FcFontSet *src, FcFontSet *dst, std::string const& family) { + if (!src) return; + + for (FcPattern *pat : boost::make_iterator_range(&src->fonts[0], &src->fonts[src->nfont])) { + int val; + if (FcPatternGetBool(pat, FC_OUTLINE, 0, &val) != FcResultMatch || val != FcTrue) continue; + + FcChar8 *str; + for (int i = 0; FcPatternGetString(pat, FC_FULLNAME, i, &str) == FcResultMatch; ++i) { + if (boost::to_lower_copy(std::string((char *)str)) == family) { + FcFontSetAdd(dst, FcPatternDuplicate(pat)); + goto skip_family; } } - return family_cnt; - } - int strcasecmp(std::string a, std::string b) { - agi::util::str_lower(a); - agi::util::str_lower(b); - return a.compare(b); - } + for (int i = 0; FcPatternGetString(pat, FC_FAMILY, i, &str) == FcResultMatch; ++i) { + if (boost::to_lower_copy(std::string((char *)str)) == family) { + FcFontSetAdd(dst, FcPatternDuplicate(pat)); + break; + } + } - FcConfig *init_fontconfig() { -#ifdef __APPLE__ - FcConfig *config = FcConfigCreate(); - std::string conf_path = agi::util::OSX_GetBundleResourcesDirectory() + "/etc/fonts/fonts.conf"; - if (FcConfigParseAndLoad(config, (unsigned char *)conf_path.c_str(), FcTrue)) - return config; - - LOG_E("font_collector/fontconfig") << "Loading fontconfig configuration file failed"; - FcConfigDestroy(config); -#endif - return FcInitLoadConfig(); + skip_family:; } } +} + FontConfigFontFileLister::FontConfigFontFileLister(FontCollectorStatusCallback cb) : config(init_fontconfig(), FcConfigDestroy) { @@ -92,119 +90,49 @@ FontConfigFontFileLister::FontConfigFontFileLister(FontCollectorStatusCallback c FcConfigBuildFonts(config); } -FcFontSet *FontConfigFontFileLister::MatchFullname(const char *family, int weight, int slant) { - FcFontSet *sets[2]; - FcFontSet *result = FcFontSetCreate(); - int nsets = 0; - - if ((sets[nsets] = FcConfigGetFonts(config, FcSetSystem))) - nsets++; - if ((sets[nsets] = FcConfigGetFonts(config, FcSetApplication))) - nsets++; - - // Run over font sets and patterns and try to match against full name - for (int i = 0; i < nsets; i++) { - FcFontSet *set = sets[i]; - for (int fi = 0; fi < set->nfont; fi++) { - FcPattern *pat = set->fonts[fi]; - char *fullname; - int pi = 0, at; - FcBool ol; - while (FcPatternGetString(pat, FC_FULLNAME, pi++, (FcChar8 **)&fullname) == FcResultMatch) { - if (FcPatternGetBool(pat, FC_OUTLINE, 0, &ol) != FcResultMatch || ol != FcTrue) - continue; - if (FcPatternGetInteger(pat, FC_SLANT, 0, &at) != FcResultMatch || at < slant) - continue; - if (FcPatternGetInteger(pat, FC_WEIGHT, 0, &at) != FcResultMatch || at < weight) - continue; - if (strcasecmp(fullname, family) == 0) { - FcFontSetAdd(result, FcPatternDuplicate(pat)); - break; - } - } - } - } - - return result; -} - FontFileLister::CollectionResult FontConfigFontFileLister::GetFontPaths(wxString const& facename, int bold, bool italic, std::set const& characters) { CollectionResult ret; std::string family = STD_STR(facename); if (family[0] == '@') family.erase(0, 1); + boost::to_lower(family); int weight = bold == 0 ? 80 : bold == 1 ? 200 : bold; int slant = italic ? 110 : 0; + // Create a fontconfig pattern to match the desired weight/slant agi::scoped_holder pat(FcPatternCreate(), FcPatternDestroy); if (!pat) return ret; - int family_cnt = add_families(pat, family); - FcPatternAddBool(pat, FC_OUTLINE, true); FcPatternAddInteger(pat, FC_SLANT, slant); FcPatternAddInteger(pat, FC_WEIGHT, weight); FcDefaultSubstitute(pat); - if (!FcConfigSubstitute(config, pat, FcMatchPattern)) return ret; - FcResult result; - agi::scoped_holder fsorted(FcFontSort(config, pat, true, nullptr, &result), FcFontSetDestroy); - agi::scoped_holder ffullname(MatchFullname(family.c_str(), weight, slant), FcFontSetDestroy); - if (!fsorted || !ffullname) return ret; - + // Create a font set with only correctly named fonts + // This is needed because the patterns returned by font matching only + // include the first family and fullname, so we can't always verify that + // we got the actual font we were asking for after the fact agi::scoped_holder fset(FcFontSetCreate(), FcFontSetDestroy); - for (int cur_font = 0; cur_font < ffullname->nfont; ++cur_font) { - FcPattern *curp = ffullname->fonts[cur_font]; - FcPatternReference(curp); - FcFontSetAdd(fset, curp); - } - for (int cur_font = 0; cur_font < fsorted->nfont; ++cur_font) { - FcPattern *curp = fsorted->fonts[cur_font]; - FcPatternReference(curp); - FcFontSetAdd(fset, curp); - } + find_font(FcConfigGetFonts(config, FcSetApplication), fset, family); + find_font(FcConfigGetFonts(config, FcSetSystem), fset, family); - int cur_font; - for (cur_font = 0; cur_font < fset->nfont; ++cur_font) { - FcBool outline; - result = FcPatternGetBool(fset->fonts[cur_font], FC_OUTLINE, 0, &outline); - if (result == FcResultMatch && outline) break; - } - - if (cur_font >= fset->nfont) return ret; - - // Remove all extra family names from original pattern. - // After this, FcFontRenderPrepare will select the most relevant family - // name in case there are more than one of them. - for (; family_cnt > 1; --family_cnt) - FcPatternRemove(pat, FC_FAMILY, family_cnt - 1); - - agi::scoped_holder rpat(FcFontRenderPrepare(config, pat, fset->fonts[cur_font]), FcPatternDestroy); - if (!rpat) return ret; - - FcChar8 *r_family; - if (FcPatternGetString(rpat, FC_FAMILY, 0, &r_family) != FcResultMatch) - return ret; - - FcChar8 *r_fullname; - if (FcPatternGetString(rpat, FC_FULLNAME, 0, &r_fullname) != FcResultMatch) - return ret; - - if (strcasecmp(family, (char*)r_family) && strcasecmp(family, (char*)r_fullname)) - return ret; + // Get the best match from fontconfig + FcResult result; + FcFontSet *sets[] = { (FcFontSet*)fset }; + agi::scoped_holder match(FcFontSetMatch(config, sets, 1, pat, &result), FcPatternDestroy); FcChar8 *file; - if(FcPatternGetString(rpat, FC_FILE, 0, &file) != FcResultMatch) + if(FcPatternGetString(match, FC_FILE, 0, &file) != FcResultMatch) return ret; FcCharSet *charset; - if (FcPatternGetCharSet(rpat, FC_CHARSET, 0, &charset) == FcResultMatch) { + if (FcPatternGetCharSet(match, FC_CHARSET, 0, &charset) == FcResultMatch) { for (wxUniChar chr : characters) { if (!FcCharSetHasChar(charset, chr)) ret.missing += chr;