From c1d37d86999ec391d5830f16887024b719f9cd18 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sat, 6 Dec 2025 12:22:25 +0000 Subject: [PATCH] Add: Automatically load fonts for missing glyphs. (#14856) --- src/console_cmds.cpp | 2 +- src/fontcache.cpp | 9 ++++++-- src/fontcache.h | 3 ++- src/gfx_layout.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index e0402b4454..69ddd40c0d 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2326,7 +2326,7 @@ static bool ConContent(std::span argv) */ static std::string_view FontLoadReasonToName(FontLoadReason load_reason) { - static const std::string_view LOAD_REASON_TO_NAME[] = { "default", "configured", "language" }; + static const std::string_view LOAD_REASON_TO_NAME[] = { "default", "configured", "language", "missing" }; static_assert(std::size(LOAD_REASON_TO_NAME) == to_underlying(FontLoadReason::End)); return LOAD_REASON_TO_NAME[to_underlying(load_reason)]; } diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 379516ca3d..8f380f71e0 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -75,6 +75,7 @@ int FontCache::GetDefaultFontHeight(FontSize fs) for (const auto &fc : FontCache::caches) { if (fc == nullptr || fc->fs != fs) continue; + if (fc->load_reason == FontLoadReason::MissingFallback) continue; // Avoid dynamically loaded fonts affecting widget sizes. ascender = std::max(ascender, fc->ascender); descender = std::min(descender, fc->descender); } @@ -269,10 +270,11 @@ std::string GetFontCacheFontName(FontSize fs) } } -/* static */ void FontCache::LoadFallbackFonts(FontSize fs) +/* static */ void FontCache::LoadFallbackFonts(FontSize fs, FontLoadReason load_reason) { const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); for (auto it = setting->fallback_fonts.rbegin(); it != setting->fallback_fonts.rend(); ++it) { + if (it->load_reason != load_reason) continue; FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, false, it->name, it->os_handle), it->load_reason); } } @@ -318,12 +320,15 @@ std::string GetFontCacheFontName(FontSize fs) if (std::ranges::find(fontnames, extra_font) == std::end(fontnames)) fontnames.push_back(extra_font); } + /* First load fonts for missing glyphs discovered during string formatting. */ + FontCache::LoadFallbackFonts(fs, FontLoadReason::MissingFallback); + /* Load configured fonts in reverse order so that the first entry has priority. */ for (auto it = fontnames.rbegin(); it != fontnames.rend(); ++it) { if (*it == DEFAULT_FONT) { FontCache::LoadDefaultFonts(fs); } else if (*it == FALLBACK_FONT) { - FontCache::LoadFallbackFonts(fs); + FontCache::LoadFallbackFonts(fs, FontLoadReason::LanguageFallback); } else { FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, true, std::string{*it}, {}), FontLoadReason::Configured); } diff --git a/src/fontcache.h b/src/fontcache.h index 0b2a054b28..98da64d00f 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -24,6 +24,7 @@ enum class FontLoadReason : uint8_t { Default, Configured, LanguageFallback, + MissingFallback, End, }; @@ -51,7 +52,7 @@ protected: FontCache(FontSize fs) : fs(fs) {} static void Register(std::unique_ptr &&fc, FontLoadReason load_reason); static void LoadDefaultFonts(FontSize fs); - static void LoadFallbackFonts(FontSize fs); + static void LoadFallbackFonts(FontSize fs, FontLoadReason load_reason); public: virtual ~FontCache() = default; diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp index e39863bf39..a91f9d6ee5 100644 --- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -10,10 +10,15 @@ #include "stdafx.h" #include "core/math_func.hpp" #include "gfx_layout.h" +#include "gfx_func.h" #include "string_func.h" #include "strings_func.h" #include "core/utf8.hpp" #include "debug.h" +#include "timer/timer.h" +#include "timer/timer_window.h" +#include "viewport_func.h" +#include "window_func.h" #include "table/control_codes.h" @@ -37,6 +42,50 @@ /** Cache of ParagraphLayout lines. */ std::unique_ptr Layouter::linecache; +class RuntimeMissingGlyphSearcher : public MissingGlyphSearcher { + std::array, FS_END> glyphs{}; +public: + RuntimeMissingGlyphSearcher() : MissingGlyphSearcher(FONTSIZES_ALL) {} + + FontLoadReason GetLoadReason() override { return FontLoadReason::MissingFallback; } + + inline void Insert(FontSize fs, char32_t c) + { + this->glyphs[fs].insert(c); + this->search_timeout.Reset(); + } + + std::set GetRequiredGlyphs(FontSizes fontsizes) override + { + std::set r; + for (FontSize fs : fontsizes) { + r.merge(this->glyphs[fs]); + } + return r; + } + + TimeoutTimer search_timeout{std::chrono::milliseconds(250), [this]() + { + FontSizes changed_fontsizes{}; + for (FontSize fs = FS_BEGIN; fs != FS_END; ++fs) { + auto &missing = this->glyphs[fs]; + if (missing.empty()) continue; + + if (FontProviderManager::FindFallbackFont({}, fs, this)) changed_fontsizes.Set(fs); + missing.clear(); + } + + if (!changed_fontsizes.Any()) return; + + FontCache::LoadFontCaches(changed_fontsizes); + LoadStringWidthTable(changed_fontsizes); + UpdateAllVirtCoords(); + ReInitAllWindows(true); + }}; +}; + +static RuntimeMissingGlyphSearcher _missing_glyphs; + /** * Helper for getting a ParagraphLayouter of the given type. * @@ -98,6 +147,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s FontIndex font_index = FontCache::GetFontIndexForCharacter(state.fontsize, c); if (font_index == INVALID_FONT_INDEX) { + _missing_glyphs.Insert(state.fontsize, c); font_index = FontCache::GetDefaultFontIndex(state.fontsize); }