mirror of https://github.com/odrling/Aegisub
Rewrite the fontconfig font lister
FcFontRenderPrepare discards excess family names and fullnames, so in some cases it may be impossible to verify that the font returned by it is the one we asked for. To work around this, prefilter the available fonts to only do fontconfig's matching on the ones with the correct names.
This commit is contained in:
parent
0b19908e7b
commit
5513d774bf
|
@ -35,56 +35,54 @@
|
||||||
#include <libaegisub/util_osx.h>
|
#include <libaegisub/util_osx.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/case_conv.hpp>
|
||||||
|
|
||||||
#ifndef AGI_PRE
|
#ifndef AGI_PRE
|
||||||
#include <wx/intl.h>
|
#include <wx/intl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// In SSA/ASS fonts are sometimes referenced by their "full name",
|
FcConfig *init_fontconfig() {
|
||||||
// which is usually a concatenation of family name and font
|
#ifdef __APPLE__
|
||||||
// style (ex. Ottawa Bold). Full name is available from
|
FcConfig *config = FcConfigCreate();
|
||||||
// FontConfig pattern element FC_FULLNAME, but it is never
|
std::string conf_path = agi::util::OSX_GetBundleResourcesDirectory() + "/etc/fonts/fonts.conf";
|
||||||
// used for font matching.
|
if (FcConfigParseAndLoad(config, (unsigned char *)conf_path.c_str(), FcTrue))
|
||||||
// Therefore, I'm removing words from the end of the name one
|
return config;
|
||||||
// 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) {
|
LOG_E("font_collector/fontconfig") << "Loading fontconfig configuration file failed";
|
||||||
if (*p == ' ' || *p == '-') {
|
FcConfigDestroy(config);
|
||||||
*p = '\0';
|
#endif
|
||||||
FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *)family.c_str());
|
return FcInitLoadConfig();
|
||||||
++family_cnt;
|
}
|
||||||
|
|
||||||
|
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) {
|
for (int i = 0; FcPatternGetString(pat, FC_FAMILY, i, &str) == FcResultMatch; ++i) {
|
||||||
agi::util::str_lower(a);
|
if (boost::to_lower_copy(std::string((char *)str)) == family) {
|
||||||
agi::util::str_lower(b);
|
FcFontSetAdd(dst, FcPatternDuplicate(pat));
|
||||||
return a.compare(b);
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FcConfig *init_fontconfig() {
|
skip_family:;
|
||||||
#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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
FontConfigFontFileLister::FontConfigFontFileLister(FontCollectorStatusCallback cb)
|
FontConfigFontFileLister::FontConfigFontFileLister(FontCollectorStatusCallback cb)
|
||||||
: config(init_fontconfig(), FcConfigDestroy)
|
: config(init_fontconfig(), FcConfigDestroy)
|
||||||
{
|
{
|
||||||
|
@ -92,119 +90,49 @@ FontConfigFontFileLister::FontConfigFontFileLister(FontCollectorStatusCallback c
|
||||||
FcConfigBuildFonts(config);
|
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<wxUniChar> const& characters) {
|
FontFileLister::CollectionResult FontConfigFontFileLister::GetFontPaths(wxString const& facename, int bold, bool italic, std::set<wxUniChar> const& characters) {
|
||||||
CollectionResult ret;
|
CollectionResult ret;
|
||||||
|
|
||||||
std::string family = STD_STR(facename);
|
std::string family = STD_STR(facename);
|
||||||
if (family[0] == '@')
|
if (family[0] == '@')
|
||||||
family.erase(0, 1);
|
family.erase(0, 1);
|
||||||
|
boost::to_lower(family);
|
||||||
|
|
||||||
int weight = bold == 0 ? 80 :
|
int weight = bold == 0 ? 80 :
|
||||||
bold == 1 ? 200 :
|
bold == 1 ? 200 :
|
||||||
bold;
|
bold;
|
||||||
int slant = italic ? 110 : 0;
|
int slant = italic ? 110 : 0;
|
||||||
|
|
||||||
|
// Create a fontconfig pattern to match the desired weight/slant
|
||||||
agi::scoped_holder<FcPattern*> pat(FcPatternCreate(), FcPatternDestroy);
|
agi::scoped_holder<FcPattern*> pat(FcPatternCreate(), FcPatternDestroy);
|
||||||
if (!pat) return ret;
|
if (!pat) return ret;
|
||||||
|
|
||||||
int family_cnt = add_families(pat, family);
|
|
||||||
|
|
||||||
FcPatternAddBool(pat, FC_OUTLINE, true);
|
FcPatternAddBool(pat, FC_OUTLINE, true);
|
||||||
FcPatternAddInteger(pat, FC_SLANT, slant);
|
FcPatternAddInteger(pat, FC_SLANT, slant);
|
||||||
FcPatternAddInteger(pat, FC_WEIGHT, weight);
|
FcPatternAddInteger(pat, FC_WEIGHT, weight);
|
||||||
|
|
||||||
FcDefaultSubstitute(pat);
|
FcDefaultSubstitute(pat);
|
||||||
|
|
||||||
if (!FcConfigSubstitute(config, pat, FcMatchPattern)) return ret;
|
if (!FcConfigSubstitute(config, pat, FcMatchPattern)) return ret;
|
||||||
|
|
||||||
FcResult result;
|
// Create a font set with only correctly named fonts
|
||||||
agi::scoped_holder<FcFontSet*> fsorted(FcFontSort(config, pat, true, nullptr, &result), FcFontSetDestroy);
|
// This is needed because the patterns returned by font matching only
|
||||||
agi::scoped_holder<FcFontSet*> ffullname(MatchFullname(family.c_str(), weight, slant), FcFontSetDestroy);
|
// include the first family and fullname, so we can't always verify that
|
||||||
if (!fsorted || !ffullname) return ret;
|
// we got the actual font we were asking for after the fact
|
||||||
|
|
||||||
agi::scoped_holder<FcFontSet*> fset(FcFontSetCreate(), FcFontSetDestroy);
|
agi::scoped_holder<FcFontSet*> fset(FcFontSetCreate(), FcFontSetDestroy);
|
||||||
for (int cur_font = 0; cur_font < ffullname->nfont; ++cur_font) {
|
find_font(FcConfigGetFonts(config, FcSetApplication), fset, family);
|
||||||
FcPattern *curp = ffullname->fonts[cur_font];
|
find_font(FcConfigGetFonts(config, FcSetSystem), fset, family);
|
||||||
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;
|
// Get the best match from fontconfig
|
||||||
for (cur_font = 0; cur_font < fset->nfont; ++cur_font) {
|
FcResult result;
|
||||||
FcBool outline;
|
FcFontSet *sets[] = { (FcFontSet*)fset };
|
||||||
result = FcPatternGetBool(fset->fonts[cur_font], FC_OUTLINE, 0, &outline);
|
agi::scoped_holder<FcPattern*> match(FcFontSetMatch(config, sets, 1, pat, &result), FcPatternDestroy);
|
||||||
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<FcPattern*> 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;
|
FcChar8 *file;
|
||||||
if(FcPatternGetString(rpat, FC_FILE, 0, &file) != FcResultMatch)
|
if(FcPatternGetString(match, FC_FILE, 0, &file) != FcResultMatch)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
FcCharSet *charset;
|
FcCharSet *charset;
|
||||||
if (FcPatternGetCharSet(rpat, FC_CHARSET, 0, &charset) == FcResultMatch) {
|
if (FcPatternGetCharSet(match, FC_CHARSET, 0, &charset) == FcResultMatch) {
|
||||||
for (wxUniChar chr : characters) {
|
for (wxUniChar chr : characters) {
|
||||||
if (!FcCharSetHasChar(charset, chr))
|
if (!FcCharSetHasChar(charset, chr))
|
||||||
ret.missing += chr;
|
ret.missing += chr;
|
||||||
|
|
Loading…
Reference in New Issue