Change: test font for eligibility before adding as fallback

Avoids reloading fonts and scanning for missing glyphs for each searched font.
This commit is contained in:
Peter Nelson
2026-04-02 10:00:40 +01:00
committed by Peter Nelson
parent 9a0af4387b
commit f864aaf131
9 changed files with 111 additions and 55 deletions
+27
View File
@@ -246,6 +246,33 @@ static std::string GetFontCacheFontName(FontSize fs)
}
}
/**
* Test a fallback font, with optional OS-specific handle, for specific glyphs.
* @param fontsizes The fontsizes the font will be used for.
* @param glyphs Glyphs to search for.
* @param name Name of font to test.
* @param os_handle OS-specific handle or data of font.
* @return true iff the font can be used.
*/
/* static */ bool FontCache::TryFallback(FontSizes, const std::set<char32_t> &glyphs, const std::string &name, const std::any &os_handle)
{
/* Load the font without registering it. The font size does not matter. */
auto fc = FontProviderManager::LoadFont(FS_NORMAL, FontType::TrueType, false, name, os_handle);
if (fc == nullptr) return false;
size_t matching_chars = 0;
for (const char32_t &c : glyphs) {
if (fc->MapCharToGlyph(c, false) != 0) ++matching_chars;
}
if (matching_chars < glyphs.size()) {
Debug(fontcache, 1, "Font \"{}\" misses {} glyphs", name, glyphs.size() - matching_chars);
return false;
}
return true;
}
/**
* (Re)initialize the font cache related things, i.e. load the non-sprite fonts.
* @param fontsizes Font sizes to be initialised.
+2
View File
@@ -50,6 +50,8 @@ public:
static void AddFallback(FontSizes fontsizes, std::string_view name, const std::any &os_handle = {});
static bool TryFallback(FontSizes fontsizes, const std::set<char32_t> &glyphs, const std::string &name, const std::any &os_handle = {});
/**
* Get the FontSize of the font.
* @return The FontSize.
+8 -7
View File
@@ -215,7 +215,7 @@ public:
* something, no matter the name. As such, we can't use it to check for existence.
* We instead query the list of all font descriptors that match the given name which
* does not do this stupid name fallback. */
CFAutoRelease<CTFontDescriptorRef> name_desc(CTFontDescriptorCreateWithNameAndSize(font_name.get(), 0.0));
CFAutoRelease<CTFontDescriptorRef> name_desc(CTFontDescriptorCreateWithNameAndSize(name.get(), 0.0));
CFAutoRelease<CFSetRef> mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast<const void **>(reinterpret_cast<const void * const *>(&kCTFontNameAttribute)), 1, &kCFTypeSetCallBacks));
CFAutoRelease<CFArrayRef> descs(CTFontDescriptorCreateMatchingFontDescriptors(name_desc.get(), mandatory_attribs.get()));
@@ -298,10 +298,10 @@ public:
if (name.starts_with(".") || name.starts_with("LastResort")) continue;
/* Save result. */
FontCache::AddFallback(callback->missing_fontsizes, name);
if (!callback->FindMissingGlyphs()) {
result = FontCache::TryFallback(callback->missing_fontsizes, callback->missing_glyphs, std::string{name});
if (result) {
FontCache::AddFallback(callback->missing_fontsizes, name);
Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name);
result = true;
break;
}
}
@@ -310,11 +310,12 @@ public:
if (!result) {
/* For some OS versions, the font 'Arial Unicode MS' does not report all languages it
* supports. If we didn't find any other font, just try it, maybe we get lucky. */
FontCache::AddFallback(callback->missing_fontsizes, "Arial Unicode MS");
result = !callback->FindMissingGlyphs();
result = FontCache::TryFallback(callback->missing_fontsizes, callback->missing_glyphs, "Arial Unicode MS");
if (result) {
FontCache::AddFallback(callback->missing_fontsizes, "Arial Unicode MS");
}
}
callback->FindMissingGlyphs();
return result;
}
+18 -11
View File
@@ -162,7 +162,7 @@ bool FontConfigFindFallbackFont(const std::string &language_isocode, MissingGlyp
/* First create a pattern to match the wanted language. */
auto pat = AutoRelease<FcPattern, FcPatternDestroy>(FcNameParse(ToFcString(lang)));
/* We only want to know these attributes. */
auto os = AutoRelease<FcObjectSet, FcObjectSetDestroy>(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, nullptr));
auto os = AutoRelease<FcObjectSet, FcObjectSetDestroy>(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, FC_CHARSET, nullptr));
/* Get the list of filenames matching the wanted language. */
auto fs = AutoRelease<FcFontSet, FcFontSetDestroy>(FcFontList(nullptr, pat.get(), os.get()));
@@ -191,21 +191,28 @@ bool FontConfigFindFallbackFont(const std::string &language_isocode, MissingGlyp
int weight = GetPreferredWeightDistance(value);
if (best_weight != -1 && weight > best_weight) continue;
/* Test the font for matching glyphs. This is essentially the same as
* calling TryFallback() but avoids having to reopen the font, etc. */
size_t matching_chars = 0;
FcCharSet *charset;
FcPatternGetCharSet(font, FC_CHARSET, 0, &charset);
for (const char32_t &c : callback->missing_glyphs) {
if (FcCharSetHasChar(charset, c)) ++matching_chars;
}
if (matching_chars < callback->missing_glyphs.size()) {
Debug(fontcache, 1, "Font \"{}\" misses {} glyphs", FromFcString(file), callback->missing_glyphs.size() - matching_chars);
continue;
}
/* Possible match based on attributes, get index. */
int32_t index;
res = FcPatternGetInteger(font, FC_INDEX, 0, &index);
if (res != FcResultMatch) continue;
FontCache::AddFallback(callback->missing_fontsizes, FromFcString(file), index);
bool missing = callback->FindMissingGlyphs();
Debug(fontcache, 1, "Font \"{}\" misses{} glyphs", FromFcString(file), missing ? "" : " no");
if (!missing) {
best_weight = weight;
best_font = FromFcString(file);
best_index = index;
}
best_weight = weight;
best_font = FromFcString(file);
best_index = index;
}
if (best_font == nullptr) return false;
+2 -1
View File
@@ -76,8 +76,9 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT
char font_name[MAX_PATH];
convert_from_fs(logfont->elfFullName, font_name);
if (!FontCache::TryFallback(info->callback->missing_fontsizes, info->callback->missing_glyphs, font_name, logfont->elfLogFont)) return 1;
FontCache::AddFallback(info->callback->missing_fontsizes, font_name, logfont->elfLogFont);
if (info->callback->FindMissingGlyphs()) return 1;
Debug(fontcache, 1, "Fallback font: {}", font_name);
return 0; // stop enumerating
}
+32 -31
View File
@@ -2306,40 +2306,40 @@ std::string_view GetCurrentLanguageIsoCode()
return _langpack.langpack->isocode;
}
/**
* Check whether there are glyphs missing in the current language.
* @return If glyphs are missing, return \c true, else return \c false.
*/
bool MissingGlyphSearcher::FindMissingGlyphs()
void BaseStringMissingGlyphSearcher::DetermineRequiredGlyphs(FontSizes fontsizes)
{
FontCache::LoadFontCaches(this->fontsizes);
this->missing_fontsizes.Reset();
this->missing_glyphs.clear();
this->Reset();
for (auto text = this->NextString(); text.has_value(); text = this->NextString()) {
FontSize size = this->DefaultSize();
FontCache *fc = FontCache::Get(size);
FontSize fs = this->DefaultSize();
FontCache *fc = FontCache::Get(fs);
for (char32_t c : Utf8View(*text)) {
if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) {
size = (FontSize)(c - SCC_FIRST_FONT);
fc = FontCache::Get(size);
} else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) {
/* The character is printable, but not in the normal font. This is the case we were testing for. */
Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", static_cast<uint32_t>(c), FontSizeToName(size));
/* For now, assume all fontsizes need searching if any are missing. */
this->missing_fontsizes = this->fontsizes;
return true;
fs = (FontSize)(c - SCC_FIRST_FONT);
fc = FontCache::Get(fs);
continue;
}
if (!fontsizes.Test(fs)) continue;
if (!IsPrintable(c) || IsTextDirectionChar(c)) continue;
if (IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END)) continue;
if (fc->MapCharToGlyph(c, false) != 0) continue;
this->missing_fontsizes.Set(fs);
this->missing_glyphs.insert(c);
}
}
return false;
}
/** Helper for searching through the language pack. */
class LanguagePackGlyphSearcher : public MissingGlyphSearcher {
class LanguagePackGlyphSearcher : public BaseStringMissingGlyphSearcher {
public:
LanguagePackGlyphSearcher() : MissingGlyphSearcher(FONTSIZES_REQUIRED) {}
/**
* Create this language pack glyph searcher.
*/
LanguagePackGlyphSearcher() : BaseStringMissingGlyphSearcher(FONTSIZES_REQUIRED) {}
private:
uint i; ///< Iterator for the primary language tables.
@@ -2388,7 +2388,12 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
{
static LanguagePackGlyphSearcher pack_searcher;
if (searcher == nullptr) searcher = &pack_searcher;
bool bad_font = searcher->FindMissingGlyphs();
FontCache::LoadFontCaches(searcher->fontsizes);
searcher->DetermineRequiredGlyphs(searcher->fontsizes);
bool bad_font = searcher->missing_fontsizes.Any();
#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
if (bad_font) {
/* We found an unprintable character... lets try whether we can find
@@ -2397,6 +2402,9 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
FontCacheSettings backup = _fcsettings;
bad_font = !FontProviderManager::FindFallbackFont(_langpack.langpack->isocode, searcher);
if (!bad_font) {
FontCache::LoadFontCaches(searcher->missing_fontsizes);
}
_fcsettings = std::move(backup);
@@ -2410,13 +2418,6 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
builder.Put("The current font is missing some of the characters used in the texts for this language. Using system fallback font instead.");
ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING);
}
if (bad_font) {
/* Our fallback font does miss characters too, so keep the
* user chosen font as that is more likely to be any good than
* the wild guess we made */
FontCache::LoadFontCaches(searcher->fontsizes);
}
}
#endif
@@ -2432,12 +2433,12 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher)
ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING);
/* Reset the font width */
LoadStringWidthTable(searcher->fontsizes);
LoadStringWidthTable(searcher->missing_fontsizes);
return;
}
/* Update the font with cache */
LoadStringWidthTable(searcher->fontsizes);
LoadStringWidthTable(searcher->missing_fontsizes);
#if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA)
/*
+20 -3
View File
@@ -167,6 +167,25 @@ public:
const FontSizes fontsizes; ///< Font sizes this searcher will try to find.
FontSizes missing_fontsizes{}; ///< Font sizes to actually search for.
std::set<char32_t> missing_glyphs{}; ///< Glyphs to search for.
/**
* Determine set of glyphs required for the current language.
* @param fontsizes Font sizes to test.
**/
virtual void DetermineRequiredGlyphs(FontSizes fontsizes) = 0;
};
/** Base for missing glyph searchers that look for missing glyphs in strings. */
class BaseStringMissingGlyphSearcher : public MissingGlyphSearcher {
public:
/**
* Create this string glyph searcher.
* @param fontsizes Font sizes to consider.
*/
BaseStringMissingGlyphSearcher(FontSizes fontsizes) : MissingGlyphSearcher(fontsizes) {}
void DetermineRequiredGlyphs(FontSizes fontsizes) override;
/**
* Get the next string to search through.
@@ -184,10 +203,8 @@ public:
* Reset the search, i.e. begin from the beginning again.
*/
virtual void Reset() = 0;
bool FindMissingGlyphs();
};
void CheckForMissingGlyphs(MissingGlyphSearcher *search = nullptr);
void CheckForMissingGlyphs(MissingGlyphSearcher *searcher = nullptr);
#endif /* STRINGS_FUNC_H */
+1 -1
View File
@@ -83,7 +83,7 @@ static WindowDesc _textfile_desc(
_nested_textfile_widgets
);
TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), MissingGlyphSearcher(FS_MONO), file_type(file_type)
TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), BaseStringMissingGlyphSearcher(FS_MONO), file_type(file_type)
{
/* Init of nested tree is deferred.
* TextfileWindow::ConstructWindow must be called by the inheriting window. */
+1 -1
View File
@@ -19,7 +19,7 @@
std::optional<std::string> GetTextfile(TextfileType type, Subdirectory dir, std::string_view filename);
/** Window for displaying a textfile */
struct TextfileWindow : public Window, MissingGlyphSearcher {
struct TextfileWindow : public Window, BaseStringMissingGlyphSearcher {
TextfileType file_type{}; ///< Type of textfile to view.
Scrollbar *vscroll = nullptr; ///< Vertical scrollbar.
Scrollbar *hscroll = nullptr; ///< Horizontal scrollbar.