// Copyright (c) 2012, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // // Aegisub Project http://www.aegisub.org/ // // $Id$ /// @file font_file_lister_fontconfig.cpp /// @brief Font Config-based font collector /// @ingroup font_collector /// #include "config.h" #ifdef WITH_FONTCONFIG #include "font_file_lister_fontconfig.h" #include #include "compat.h" #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()); 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; } } 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); } } FontConfigFontFileLister::FontConfigFontFileLister(FontCollectorStatusCallback cb) : config(FcInitLoadConfig(), FcConfigDestroy) { cb(_("Updating font cache\n"), 0); 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); int weight = bold == 0 ? 80 : bold == 1 ? 200 : bold; int slant = italic ? 110 : 0; 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, NULL, &result), FcFontSetDestroy); agi::scoped_holder ffullname(MatchFullname(family.c_str(), weight, slant), FcFontSetDestroy); if (!fsorted || !ffullname) return ret; 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); } 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; FcChar8 *file; if(FcPatternGetString(rpat, FC_FILE, 0, &file) != FcResultMatch) return ret; FcCharSet *charset; if (FcPatternGetCharSet(rpat, FC_CHARSET, 0, &charset) == FcResultMatch) { for (std::set::const_iterator it = characters.begin(); it != characters.end(); ++it) { if (!FcCharSetHasChar(charset, *it)) ret.missing += *it; } } ret.paths.push_back((const char *)file); return ret; } #endif