From 1829f7926d634b9062704bcd502b7ed1b5addee3 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sat, 6 Dec 2025 10:47:12 +0000 Subject: [PATCH] Change: Support side-by-side fallback FontCaches instead of hierarchical. (#13303) The text layouter system can now support using different fonts for different glyphs, including mixing scalable and sprite glyphs. --- src/console_cmds.cpp | 35 +++-- src/fontcache.cpp | 183 ++++++++++++++++++++------- src/fontcache.h | 139 ++++++++++++++++---- src/fontcache/freetypefontcache.cpp | 33 ++--- src/fontcache/spritefontcache.cpp | 41 +++--- src/fontcache/spritefontcache.h | 7 +- src/fontcache/truetypefontcache.cpp | 4 - src/gfx.cpp | 19 ++- src/gfx_layout.cpp | 88 ++++++------- src/gfx_layout.h | 35 +++-- src/gfx_layout_fallback.cpp | 31 ++--- src/gfx_layout_fallback.h | 19 +++ src/gfx_layout_icu.cpp | 100 ++++++++++----- src/lang/english.txt | 2 + src/os/macosx/font_osx.cpp | 35 ++--- src/os/macosx/font_osx.h | 2 +- src/os/macosx/string_osx.cpp | 74 +++++------ src/os/unix/font_unix.cpp | 49 ++++--- src/os/unix/font_unix.h | 2 +- src/os/windows/font_win32.cpp | 42 +++--- src/os/windows/font_win32.h | 4 +- src/os/windows/string_uniscribe.cpp | 98 +++++++++----- src/settings_gui.cpp | 34 ++++- src/strings.cpp | 88 +++++++------ src/strings_func.h | 43 ++++--- src/survey.cpp | 14 +- src/table/settings/misc_settings.ini | 6 + src/tests/mock_fontcache.h | 7 +- src/textfile_gui.cpp | 15 +-- src/textfile_gui.h | 4 +- src/widgets/settings_widget.h | 2 + src/zoom_func.h | 10 ++ 32 files changed, 784 insertions(+), 481 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 7b97fab118..e0402b4454 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2319,6 +2319,18 @@ static bool ConContent(std::span argv) } #endif /* defined(WITH_ZLIB) */ +/** + * Get string representation of a font load reason. + * @param load_reason The font load reason. + * @return String representation. + */ +static std::string_view FontLoadReasonToName(FontLoadReason load_reason) +{ + static const std::string_view LOAD_REASON_TO_NAME[] = { "default", "configured", "language" }; + static_assert(std::size(LOAD_REASON_TO_NAME) == to_underlying(FontLoadReason::End)); + return LOAD_REASON_TO_NAME[to_underlying(load_reason)]; +} + static bool ConFont(std::span argv) { if (argv.empty()) { @@ -2367,17 +2379,18 @@ static bool ConFont(std::span argv) SetFont(argfs, font, size); } - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { - FontCache *fc = FontCache::Get(fs); - FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); - /* Make sure all non sprite fonts are loaded. */ - if (!setting->font.empty() && !fc->HasParent()) { - FontCache::LoadFontCaches(fs); - fc = FontCache::Get(fs); - } - IConsolePrint(CC_DEFAULT, "{} font:", FontSizeToName(fs)); - IConsolePrint(CC_DEFAULT, "Currently active: \"{}\", size {}", fc->GetFontName(), fc->GetFontSize()); - IConsolePrint(CC_DEFAULT, "Requested: \"{}\", size {}", setting->font, setting->size); + IConsolePrint(CC_INFO, "Configured fonts:"); + for (uint i = 0; FontSize fs : FONTSIZES_ALL) { + const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); + IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\", size {}", i, FontSizeToName(fs), setting->font, setting->size); + ++i; + } + + IConsolePrint(CC_INFO, "Currently active fonts:"); + for (uint i = 0; const auto &fc : FontCache::Get()) { + if (fc == nullptr) continue; + IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\" size {} [{}]", i, FontSizeToName(fc->GetSize()), fc->GetFontName(), fc->GetFontSize(), FontLoadReasonToName(fc->GetFontLoadReason())); + ++i; } return true; diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 4589ff88d1..379516ca3d 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -8,6 +8,8 @@ /** @file fontcache.cpp Cache for characters from fonts. */ #include "stdafx.h" + +#include "core/string_consumer.hpp" #include "fontcache.h" #include "blitter/factory.hpp" #include "gfx_layout.h" @@ -17,13 +19,14 @@ #include "viewport_func.h" #include "window_func.h" #include "fileio_func.h" +#include "zoom_func.h" #include "safeguards.h" /** Default unscaled heights for the different sizes of fonts. */ /* static */ const int FontCache::DEFAULT_FONT_HEIGHT[FS_END] = {10, 6, 18, 10}; /** Default unscaled ascenders for the different sizes of fonts. */ -/* static */ const int FontCache::DEFAULT_FONT_ASCENDER[FS_END] = {8, 5, 15, 8}; +/* static */ const int FontCache::DEFAULT_FONT_ASCENDER[FS_END] = {8, 6, 15, 8}; FontCacheSettings _fcsettings; @@ -33,10 +36,10 @@ FontCacheSettings _fcsettings; * @param fonttype Font type requested. * @return FontCache of the font if loaded, or nullptr. */ -/* static */ std::unique_ptr FontProviderManager::LoadFont(FontSize fs, FontType fonttype) +/* static */ std::unique_ptr FontProviderManager::LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span os_handle) { for (auto &provider : FontProviderManager::GetProviders()) { - auto fc = provider->LoadFont(fs, fonttype); + auto fc = provider->LoadFont(fs, fonttype, search, font_name, os_handle); if (fc != nullptr) return fc; } @@ -52,10 +55,10 @@ FontCacheSettings _fcsettings; * @param callback The function to call to check for missing glyphs. * @return true if a font has been set, false otherwise. */ -/* static */ bool FontProviderManager::FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) +/* static */ bool FontProviderManager::FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) { return std::ranges::any_of(FontProviderManager::GetProviders(), - [&](auto *provider) { return provider->FindFallbackFont(settings, language_isocode, callback); }); + [&](auto *provider) { return provider->FindFallbackFont(language_isocode, fontsizes, callback); }); } int FontCache::GetDefaultFontHeight(FontSize fs) @@ -63,21 +66,33 @@ int FontCache::GetDefaultFontHeight(FontSize fs) return FontCache::DEFAULT_FONT_HEIGHT[fs]; } -/** - * Get the font name of a given font size. - * @param fs The font size to look up. - * @return The font name. - */ -std::string FontCache::GetName(FontSize fs) +/* static */ void FontCache::UpdateCharacterHeight(FontSize fs) { - FontCache *fc = FontCache::Get(fs); - if (fc != nullptr) { - return fc->GetFontName(); - } else { - return "[NULL]"; + FontMetrics &metrics = FontCache::metrics[fs]; + + int ascender = 0; + int descender = 0; + + for (const auto &fc : FontCache::caches) { + if (fc == nullptr || fc->fs != fs) continue; + ascender = std::max(ascender, fc->ascender); + descender = std::min(descender, fc->descender); } + + if (ascender == 0 && descender == 0) { + /* It's possible that no font is loaded yet, in which case use default values. */ + ascender = ScaleGUITrad(FontCache::DEFAULT_FONT_ASCENDER[fs]); + descender = ScaleGUITrad(FontCache::DEFAULT_FONT_ASCENDER[fs] - FontCache::DEFAULT_FONT_HEIGHT[fs]); + } + + metrics.height = ascender - descender; + metrics.baseline = ascender; } +int FontCache::GetGlyphYOffset() +{ + return FontCache::GetFontBaseline(this->fs) - this->ascender; +} /** * Get height of a character for a given font size. @@ -86,20 +101,22 @@ std::string FontCache::GetName(FontSize fs) */ int GetCharacterHeight(FontSize size) { - return FontCache::Get(size)->GetHeight(); + uint height = FontCache::GetCharacterHeight(size); + if (height == 0) height = ScaleGUITrad(FontCache::GetDefaultFontHeight(FS_MONO)); + return height; } - -/* static */ std::array, FS_END> FontCache::caches{}; +/* static */ FontCache::FontCaches FontCache::caches; +/* static */ std::array FontCache::metrics{}; +/* static */ std::array FontCache::default_font_index{}; /** * Initialise font caches with the base sprite font cache for all sizes. */ /* static */ void FontCache::InitializeFontCaches() { - for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { - if (FontCache::Get(fs) != nullptr) continue; - FontCache::Register(FontProviderManager::LoadFont(fs, FontType::Sprite)); + for (FontSize fs : FONTSIZES_ALL) { + UpdateCharacterHeight(fs); } } @@ -130,19 +147,13 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) if (!changed) return; if (fontsize != FS_MONO) { - /* Try to reload only the modified font. */ - FontCacheSettings backup = _fcsettings; - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { - if (fs == fontsize) continue; - FontCache *fc = FontCache::Get(fs); - GetFontCacheSubSetting(fs)->font = fc->HasParent() ? fc->GetFontName() : ""; - } + /* Check if fallback fonts are needed. */ CheckForMissingGlyphs(); - _fcsettings = std::move(backup); } else { FontCache::LoadFontCaches(fontsize); } + FontCache::UpdateCharacterHeight(fontsize); LoadStringWidthTable(fontsize); UpdateAllVirtCoords(); ReInitAllWindows(true); @@ -156,7 +167,7 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) */ static bool IsDefaultFont(const FontCacheSubSetting &setting) { - return setting.font.empty() && setting.os_handle == nullptr; + return setting.font.empty(); } /** @@ -193,7 +204,7 @@ static std::string GetDefaultTruetypeFont(FontSize fs) * @param fs Font size. * @return Full path of default font file. */ -static std::string GetDefaultTruetypeFontFile([[maybe_unused]] FontSize fs) +std::string GetDefaultTruetypeFontFile([[maybe_unused]] FontSize fs) { #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) /* Find font file. */ @@ -216,18 +227,54 @@ std::string GetFontCacheFontName(FontSize fs) return GetDefaultTruetypeFontFile(fs); } -/** - * Register a FontCache for its font size. - * @param fc FontCache to register. - */ -/* static */ void FontCache::Register(std::unique_ptr &&fc) +/* static */ void FontCache::Register(std::unique_ptr &&fc, FontLoadReason load_reason) { if (fc == nullptr) return; FontSize fs = fc->fs; - fc->parent = std::move(FontCache::caches[fs]); - FontCache::caches[fs] = std::move(fc); + /* Find an empty font cache slot. */ + auto it = std::find(std::begin(FontCache::caches), std::end(FontCache::caches), nullptr); + if (it == std::end(FontCache::caches)) it = FontCache::caches.insert(it, nullptr); + + /* Set up our font index and make us the default font cache for this font size. */ + fc->font_index = static_cast(std::distance(std::begin(FontCache::caches), it)); + fc->load_reason = load_reason; + FontCache::default_font_index[fs] = fc->font_index; + + /* Register this font cache in the slot. */ + *it = std::move(fc); +} + +/** + * Add a fallback font, with optional OS-specific handle. + * @param fontsizes Fontsizes to add fallback to. + * @param name Name of font to add. + * @param handle OS-specific handle or data of font. + */ +/* static */ void FontCache::AddFallback(FontSizes fontsizes, FontLoadReason load_reason, std::string_view name, std::span os_data) +{ + for (FontSize fs : fontsizes) { + GetFontCacheSubSetting(fs)->fallback_fonts.emplace_back(load_reason, std::string{name}, std::vector{os_data.begin(), os_data.end()}); + } +} + +/* static */ void FontCache::LoadDefaultFonts(FontSize fs) +{ + /* Load the sprite font, even if it's not preferred. */ + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::Sprite, false, {}, {}), FontLoadReason::Default); + if (!_fcsettings.prefer_sprite) { + /* Load the default truetype font if sprite font is not preferred. */ + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, false, GetDefaultTruetypeFontFile(fs), {}), FontLoadReason::Default); + } +} + +/* static */ void FontCache::LoadFallbackFonts(FontSize fs) +{ + const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); + for (auto it = setting->fallback_fonts.rbegin(); it != setting->fallback_fonts.rend(); ++it) { + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, false, it->name, it->os_handle), it->load_reason); + } } /** @@ -236,17 +283,53 @@ std::string GetFontCacheFontName(FontSize fs) */ /* static */ void FontCache::LoadFontCaches(FontSizes fontsizes) { - FontCache::InitializeFontCaches(); + static constexpr std::string_view FALLBACK_FONT = "fallback"; + static constexpr std::string_view DEFAULT_FONT = "default"; + static constexpr std::initializer_list extra_prefer_default = {DEFAULT_FONT, FALLBACK_FONT}; + static constexpr std::initializer_list extra_prefer_fallback = {FALLBACK_FONT, DEFAULT_FONT}; for (FontSize fs : fontsizes) { Layouter::ResetFontCache(fs); + FontCache::default_font_index[fs] = INVALID_FONT_INDEX; + } - /* Unload everything except the sprite font cache. */ - while (FontCache::Get(fs)->HasParent()) { - FontCache::caches[fs] = std::move(FontCache::caches[fs]->parent); + /* Remove all existing FontCaches. */ + if (fontsizes == FONTSIZES_ALL) { + FontCache::caches.clear(); + } else { + for (auto it = std::begin(FontCache::caches); it != std::end(FontCache::caches); ++it) { + if (*it == nullptr) continue; + if (!fontsizes.Test((*it)->fs)) continue; + it->reset(); + } + } + + for (FontSize fs : fontsizes) { + /* Parse configured fonts, separated by ';' into a list. */ + std::vector fontnames; + StringConsumer consumer(GetFontCacheSubSetting(fs)->font); + do { + auto fontname = StrTrimView(consumer.ReadUntilChar(';', StringConsumer::SKIP_ONE_SEPARATOR), " \t"); + if (!fontname.empty()) fontnames.push_back(fontname); + } while (consumer.AnyBytesLeft()); + + /* Add the default and fallback fonts as lowest priority if not manually specified. */ + for (const auto &extra_font : _fcsettings.prefer_default ? extra_prefer_default : extra_prefer_fallback) { + if (std::ranges::find(fontnames, extra_font) == std::end(fontnames)) fontnames.push_back(extra_font); } - FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType)); + /* 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); + } else { + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, true, std::string{*it}, {}), FontLoadReason::Configured); + } + } + + FontCache::UpdateCharacterHeight(fs); } } @@ -256,8 +339,14 @@ std::string GetFontCacheFontName(FontSize fs) */ /* static */ void FontCache::ClearFontCaches(FontSizes fontsizes) { + for (const auto &fc : FontCache::caches) { + if (fc == nullptr) continue; + if (!fontsizes.Test(fc->GetSize())) continue; + fc->ClearFontCache(); + } + for (FontSize fs : fontsizes) { - FontCache::Get(fs)->ClearFontCache(); + FontCache::UpdateCharacterHeight(fs); } } @@ -266,7 +355,5 @@ std::string GetFontCacheFontName(FontSize fs) */ /* static */ void FontCache::UninitializeFontCaches() { - for (auto &fc : FontCache::caches) { - fc.reset(); - } + FontCache::caches.clear(); } diff --git a/src/fontcache.h b/src/fontcache.h index b03f84514d..0b2a054b28 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -16,20 +16,42 @@ /** Glyphs are characters from a font. */ typedef uint32_t GlyphID; -static const GlyphID SPRITE_GLYPH = 1U << 30; +using FontIndex = uint8_t; + +static const FontIndex INVALID_FONT_INDEX = std::numeric_limits::max(); + +enum class FontLoadReason : uint8_t { + Default, + Configured, + LanguageFallback, + End, +}; /** Font cache for basic fonts. */ class FontCache { protected: - static std::array, FS_END> caches; ///< All the font caches. - std::unique_ptr parent; ///< The parent of this font cache. + using FontCaches = std::vector>; + static FontCaches caches; + + struct FontMetrics { + int height = 0; + int baseline = 0; + }; + + static std::array metrics; + static std::array default_font_index; + const FontSize fs; ///< The size of the font. + FontIndex font_index; ///< The index of the font. + FontLoadReason load_reason; ///< Reason why the font is loaded. int height = 0; ///< The height of the font. int ascender = 0; ///< The ascender value of the font. int descender = 0; ///< The descender value of the font. FontCache(FontSize fs) : fs(fs) {} - static void Register(std::unique_ptr &&fc); + static void Register(std::unique_ptr &&fc, FontLoadReason load_reason); + static void LoadDefaultFonts(FontSize fs); + static void LoadFallbackFonts(FontSize fs); public: virtual ~FontCache() = default; @@ -46,12 +68,36 @@ public: static int GetDefaultFontHeight(FontSize fs); + static inline int GetFontBaseline(FontSize fs) + { + return FontCache::metrics[fs].baseline; + } + + static void AddFallback(FontSizes fontsizes, FontLoadReason load_reason, std::string_view name, std::span os_data = {}); + + /** + * Add a fallback font, with OS-specific handle. + * @param fontsizes Fontsizes to add fallback to. + * @param name Name of font to add. + * @param handle OS-specific handle or data of font. + */ + template + static void AddFallbackWithHandle(FontSizes fontsizes, FontLoadReason load_reason, std::string_view name, T &handle) + { + auto os_data = std::as_bytes(std::span(&handle, 1)); + FontCache::AddFallback(fontsizes, load_reason, name, os_data); + } + /** * Get the FontSize of the font. * @return The FontSize. */ inline FontSize GetSize() const { return this->fs; } + inline FontIndex GetIndex() const { return this->font_index; } + + inline FontLoadReason GetFontLoadReason() const { return this->load_reason; } + /** * Get the height of the font. * @return The height of the font. @@ -102,10 +148,9 @@ public: /** * Map a character into a glyph. * @param key The character. - * @param fallback Allow fallback to the parent font. * @return The glyph ID used to draw the character. */ - virtual GlyphID MapCharToGlyph(char32_t key, bool fallback = true) = 0; + virtual GlyphID MapCharToGlyph(char32_t key) = 0; /** * Get the native OS font handle, if there is one. @@ -122,25 +167,57 @@ public: */ virtual std::string GetFontName() = 0; + virtual int GetGlyphYOffset(); + + /** + * Get span of all FontCaches. + * @return Span of all FontCaches. + */ + static inline std::span> Get() + { + return FontCache::caches; + } + /** * Get the font cache of a given font size. * @param fs The font size to look up. * @return The font cache. */ - static inline FontCache *Get(FontSize fs) + static inline FontCache *Get(FontIndex font_index) { - assert(fs < FS_END); - return FontCache::caches[fs].get(); + assert(font_index < FontCache::caches.size()); + return FontCache::caches[font_index].get(); } - static std::string GetName(FontSize fs); - - /** - * Check whether the font cache has a parent. - */ - inline bool HasParent() + static inline int GetCharacterHeight(FontSize fs) { - return this->parent != nullptr; + return FontCache::metrics[fs].height; + } + + static void UpdateCharacterHeight(FontSize fs); + + static inline FontIndex GetDefaultFontIndex(FontSize fs) + { + return FontCache::default_font_index[fs]; + } + + static inline class FontCache *GetDefaultFontCache(FontSize fs) + { + FontIndex index = FontCache::GetDefaultFontIndex(fs); + if (index != INVALID_FONT_INDEX) return FontCache::Get(index); + NOT_REACHED(); + } + + static inline FontIndex GetFontIndexForCharacter(FontSize fs, char32_t c) + { + for (auto it = std::rbegin(FontCache::caches); it != std::rend(FontCache::caches); ++it) { + FontCache *fc = it->get(); + if (fc == nullptr) continue; + if (fc->GetSize() != fs) continue; + if (fc->MapCharToGlyph(c) == 0) continue; + return std::distance(std::begin(FontCache::caches), std::next(it).base()); + } + return INVALID_FONT_INDEX; } /** @@ -152,28 +229,33 @@ public: /** Get the Sprite for a glyph */ inline const Sprite *GetGlyph(FontSize size, char32_t key) { - FontCache *fc = FontCache::Get(size); + FontIndex font_index = FontCache::GetFontIndexForCharacter(size, key); + FontCache *fc = font_index != INVALID_FONT_INDEX ? FontCache::Get(font_index) : FontCache::GetDefaultFontCache(size); + if (fc == nullptr) return nullptr; return fc->GetGlyph(fc->MapCharToGlyph(key)); } /** Get the width of a glyph */ inline uint GetGlyphWidth(FontSize size, char32_t key) { - FontCache *fc = FontCache::Get(size); + FontIndex font_index = FontCache::GetFontIndexForCharacter(size, key); + FontCache *fc = font_index != INVALID_FONT_INDEX ? FontCache::Get(font_index) : FontCache::GetDefaultFontCache(size); + if (fc == nullptr) return 0; return fc->GetGlyphWidth(fc->MapCharToGlyph(key)); } -inline bool GetDrawGlyphShadow(FontSize size) -{ - return FontCache::Get(size)->GetDrawGlyphShadow(); -} - /** Settings for a single font. */ struct FontCacheSubSetting { std::string font; ///< The name of the font, or path to the font. uint size; ///< The (requested) size of the font. - const void *os_handle = nullptr; ///< Optional native OS font info. Only valid during font search. + struct FontCacheFallback { + FontLoadReason load_reason = FontLoadReason::LanguageFallback; + std::string name; + std::vector os_handle; + }; + + std::vector fallback_fonts; }; /** Settings for the four different fonts. */ @@ -184,6 +266,7 @@ struct FontCacheSettings { FontCacheSubSetting mono; ///< The mono space font used for license/readme viewers. bool prefer_sprite; ///< Whether to prefer the built-in sprite font over resizable fonts. bool global_aa; ///< Whether to anti alias all font sizes. + bool prefer_default; ///< Prefer OpenTTD's default font over autodetected fallback fonts. }; extern FontCacheSettings _fcsettings; @@ -229,14 +312,14 @@ public: ProviderManager::Unregister(*this); } - virtual std::unique_ptr LoadFont(FontSize fs, FontType fonttype) const = 0; - virtual bool FindFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) const = 0; + virtual std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span os_handle) const = 0; + virtual bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, class MissingGlyphSearcher *callback) const = 0; }; class FontProviderManager : ProviderManager { public: - static std::unique_ptr LoadFont(FontSize fs, FontType fonttype); - static bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); + static std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span os_handle); + static bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, class MissingGlyphSearcher *callback); }; /* Implemented in spritefontcache.cpp */ diff --git a/src/fontcache/freetypefontcache.cpp b/src/fontcache/freetypefontcache.cpp index 254b722415..33500d787a 100644 --- a/src/fontcache/freetypefontcache.cpp +++ b/src/fontcache/freetypefontcache.cpp @@ -41,7 +41,7 @@ public: FreeTypeFontCache(FontSize fs, FT_Face face, int pixels); ~FreeTypeFontCache(); void ClearFontCache() override; - GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override; + GlyphID MapCharToGlyph(char32_t key) override; std::string GetFontName() override { return fmt::format("{}, {}", face->family_name, face->style_name); } bool IsBuiltInFont() override { return false; } const void *GetOSHandle() override { return &face; } @@ -194,17 +194,11 @@ const Sprite *FreeTypeFontCache::InternalGetGlyph(GlyphID key, bool aa) } -GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) +GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key) { assert(IsPrintable(key)); - FT_UInt glyph = FT_Get_Char_Index(this->face, key); - - if (glyph == 0 && allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { - return this->parent->MapCharToGlyph(key); - } - - return glyph; + return FT_Get_Char_Index(this->face, key); } FT_Library _ft_library = nullptr; @@ -226,15 +220,10 @@ public: * format is 'font family name' or 'font family name, font style'. * @param fs The font size to load. */ - std::unique_ptr LoadFont(FontSize fs, FontType fonttype) const override + std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span os_handle) const override { if (fonttype != FontType::TrueType) return nullptr; - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); - - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return nullptr; - if (_ft_library == nullptr) { if (FT_Init_FreeType(&_ft_library) != FT_Err_Ok) { ShowInfo("Unable to initialize FreeType, using sprite fonts instead"); @@ -248,7 +237,9 @@ public: /* If font is an absolute path to a ttf, try loading that first. */ int32_t index = 0; - if (settings->os_handle != nullptr) index = *static_cast(settings->os_handle); + if (os_handle.size() == sizeof(index)) { + index = *reinterpret_cast(os_handle.data()); + } FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face); if (error != FT_Err_Ok) { @@ -260,8 +251,8 @@ public: } #ifdef WITH_FONTCONFIG - /* Try loading based on font face name (OS-wide fonts). */ - if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face); + /* If allowed to search, try loading based on font face name (OS-wide fonts). */ + if (error != FT_Err_Ok && search) error = GetFontByFaceName(font, &face); #endif /* WITH_FONTCONFIG */ if (error != FT_Err_Ok) { @@ -272,10 +263,10 @@ public: return LoadFont(fs, face, font, GetFontCacheFontSize(fs)); } - bool FindFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) const override + bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override { #ifdef WITH_FONTCONFIG - if (FontConfigFindFallbackFont(settings, language_isocode, callback)) return true; + if (FontConfigFindFallbackFont(language_isocode, fontsizes, callback)) return true; #endif /* WITH_FONTCONFIG */ return false; @@ -309,7 +300,7 @@ private: if (error != FT_Err_Ok) { FT_Done_Face(face); - ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), error); + ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}", font_name, FontSizeToName(fs), error); return nullptr; } diff --git a/src/fontcache/spritefontcache.cpp b/src/fontcache/spritefontcache.cpp index 8d67237266..c20aa68445 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -23,16 +23,6 @@ static const int ASCII_LETTERSTART = 32; ///< First printable ASCII letter. -/** - * Scale traditional pixel dimensions to font zoom level, for drawing sprite fonts. - * @param value Pixel amount at #ZOOM_BASE (traditional "normal" interface size). - * @return Pixel amount at _font_zoom (current interface size). - */ -static int ScaleFontTrad(int value) -{ - return UnScaleByZoom(value * ZOOM_BASE, _font_zoom); -} - static std::array, FS_END> _char_maps{}; ///< Glyph map for each font size. /** @@ -116,37 +106,48 @@ void InitializeUnicodeGlyphMap() */ SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs) { - this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); - this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2; + this->UpdateMetrics(); } void SpriteFontCache::ClearFontCache() { Layouter::ResetFontCache(this->fs); - this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); - this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2; + this->UpdateMetrics(); +} + +void SpriteFontCache::UpdateMetrics() +{ + this->height = ScaleFontTrad(DEFAULT_FONT_HEIGHT[this->fs]); + this->ascender = ScaleGUITrad(DEFAULT_FONT_ASCENDER[this->fs]); + this->descender = ScaleGUITrad(DEFAULT_FONT_ASCENDER[this->fs] - DEFAULT_FONT_HEIGHT[this->fs]); + this->scaled_ascender = ScaleFontTrad(DEFAULT_FONT_ASCENDER[this->fs]); +} + +int SpriteFontCache::GetGlyphYOffset() +{ + return FontCache::GetFontBaseline(this->fs) - this->scaled_ascender; } const Sprite *SpriteFontCache::GetGlyph(GlyphID key) { - SpriteID sprite = static_cast(key & ~SPRITE_GLYPH); + SpriteID sprite = static_cast(key); if (sprite == 0) sprite = GetUnicodeGlyph(this->fs, '?'); return GetSprite(sprite, SpriteType::Font); } uint SpriteFontCache::GetGlyphWidth(GlyphID key) { - SpriteID sprite = static_cast(key & ~SPRITE_GLYPH); + SpriteID sprite = static_cast(key); if (sprite == 0) sprite = GetUnicodeGlyph(this->fs, '?'); return SpriteExists(sprite) ? GetSprite(sprite, SpriteType::Font)->width + ScaleFontTrad(this->fs != FS_NORMAL ? 1 : 0) : 0; } -GlyphID SpriteFontCache::MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback) +GlyphID SpriteFontCache::MapCharToGlyph(char32_t key) { assert(IsPrintable(key)); SpriteID sprite = GetUnicodeGlyph(this->fs, key); if (sprite == 0) return 0; - return SPRITE_GLYPH | sprite; + return static_cast(sprite); } bool SpriteFontCache::GetDrawGlyphShadow() @@ -158,14 +159,14 @@ class SpriteFontCacheFactory : public FontCacheFactory { public: SpriteFontCacheFactory() : FontCacheFactory("sprite", "Sprite font provider") {} - std::unique_ptr LoadFont(FontSize fs, FontType fonttype) const override + std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool, const std::string &, std::span) const override { if (fonttype != FontType::Sprite) return nullptr; return std::make_unique(fs); } - bool FindFallbackFont(struct FontCacheSettings *, const std::string &, class MissingGlyphSearcher *) const override + bool FindFallbackFont(const std::string &, FontSizes, class MissingGlyphSearcher *) const override { return false; } diff --git a/src/fontcache/spritefontcache.h b/src/fontcache/spritefontcache.h index 5403348cbe..183e31e7dc 100644 --- a/src/fontcache/spritefontcache.h +++ b/src/fontcache/spritefontcache.h @@ -17,12 +17,17 @@ class SpriteFontCache : public FontCache { public: SpriteFontCache(FontSize fs); void ClearFontCache() override; + int GetGlyphYOffset() override; const Sprite *GetGlyph(GlyphID key) override; uint GetGlyphWidth(GlyphID key) override; bool GetDrawGlyphShadow() override; - GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override; + GlyphID MapCharToGlyph(char32_t key) override; std::string GetFontName() override { return "sprite"; } bool IsBuiltInFont() override { return true; } + +private: + void UpdateMetrics(); + int scaled_ascender; }; #endif /* SPRITEFONTCACHE_H */ diff --git a/src/fontcache/truetypefontcache.cpp b/src/fontcache/truetypefontcache.cpp index 890464e039..fe0a358c69 100644 --- a/src/fontcache/truetypefontcache.cpp +++ b/src/fontcache/truetypefontcache.cpp @@ -64,8 +64,6 @@ bool TrueTypeFontCache::GetDrawGlyphShadow() uint TrueTypeFontCache::GetGlyphWidth(GlyphID key) { - if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyphWidth(key); - GlyphEntry *glyph = this->GetGlyphPtr(key); if (glyph == nullptr || glyph->data == nullptr) { this->GetGlyph(key); @@ -77,8 +75,6 @@ uint TrueTypeFontCache::GetGlyphWidth(GlyphID key) const Sprite *TrueTypeFontCache::GetGlyph(GlyphID key) { - if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyph(key); - /* Check for the glyph in our cache */ GlyphEntry *glyph = this->GetGlyphPtr(key); if (glyph != nullptr && glyph->data != nullptr) return glyph->GetSprite(); diff --git a/src/gfx.cpp b/src/gfx.cpp index 87b6c7c50e..8ad6de81cc 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -537,7 +537,7 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, * another size would be chosen it won't have truncated too little for * the truncation dots. */ - truncation_layout.emplace(GetEllipsis(), INT32_MAX, line.GetVisualRun(0).GetFont()->fc->GetSize()); + truncation_layout.emplace(GetEllipsis(), INT32_MAX, line.GetVisualRun(0).GetFont().GetFontCache().GetSize()); truncation_width = truncation_layout->GetBounds().width; /* Is there enough space even for an ellipsis? */ @@ -593,15 +593,15 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, const ParagraphLayouter::VisualRun &run = line.GetVisualRun(run_index); const auto &glyphs = run.GetGlyphs(); const auto &positions = run.GetPositions(); - const Font *f = run.GetFont(); + const Font &f = run.GetFont(); - FontCache *fc = f->fc; - TextColour colour = f->colour; + FontCache &fc = f.GetFontCache(); + TextColour colour = f.colour; if (colour == TC_INVALID || HasFlag(initial_colour, TC_FORCED)) colour = initial_colour; bool colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK; /* Update the last colour for the truncation ellipsis. */ last_colour = colour; - if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue; + if (do_shadow && (!fc.GetDrawGlyphShadow() || !colour_has_shadow)) continue; SetColourRemap(do_shadow ? TC_BLACK : colour); for (int i = 0; i < run.GetGlyphCount(); i++) { @@ -616,13 +616,10 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, /* Truncated away. */ if (truncation && (begin_x < min_x || end_x > max_x)) continue; + /* Outside the clipping area. */ + if (begin_x > dpi_right || end_x < dpi_left) continue; - const Sprite *sprite = fc->GetGlyph(glyph); - /* Check clipping (the "+ 1" is for the shadow). */ - if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue; - - if (do_shadow && (glyph & SPRITE_GLYPH) != 0) continue; - + const Sprite *sprite = fc.GetGlyph(glyph); GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap); } } diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp index 32f0f7bd46..e39863bf39 100644 --- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -37,21 +37,6 @@ /** Cache of ParagraphLayout lines. */ std::unique_ptr Layouter::linecache; -/** Cache of Font instances. */ -Layouter::FontColourMap Layouter::fonts[FS_END]; - - -/** - * Construct a new font. - * @param size The font size to use for this font. - * @param colour The colour to draw this font in. - */ -Font::Font(FontSize size, TextColour colour) : - fc(FontCache::Get(size)), colour(colour) -{ - assert(size < FS_END); -} - /** * Helper for getting a ParagraphLayouter of the given type. * @@ -71,7 +56,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s const typename T::CharType *buffer_last = buff_begin + str.size() + 1; typename T::CharType *buff = buff_begin; FontMap &font_mapping = line.runs; - Font *f = Layouter::GetFont(state.fontsize, state.cur_colour); + Font f{state.font_index, state.cur_colour}; font_mapping.clear(); @@ -80,7 +65,10 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s * whenever the font changes, and convert the wide characters into a format * usable by ParagraphLayout. */ - for (char32_t c : Utf8View(str)) { + Utf8View view(str); + for (auto it = view.begin(); it != view.end(); /* nothing */) { + auto cur = it; + uint32_t c = *it++; if (c == '\0' || c == '\n') { /* Caller should already have filtered out these characters. */ NOT_REACHED(); @@ -95,19 +83,40 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s } else { /* Filter out non printable characters */ if (!IsPrintable(c)) continue; - /* Filter out text direction characters that shouldn't be drawn, and - * will not be handled in the fallback case because they are mostly - * needed for RTL languages which need more proper shaping support. */ - if (!T::SUPPORTS_RTL && IsTextDirectionChar(c)) continue; - buff += T::AppendToBuffer(buff, buffer_last, c); - if (buff >= buffer_last) break; - continue; + + if (IsTextDirectionChar(c)) { + /* Filter out text direction characters that shouldn't be drawn, and + * will not be handled in the fallback case because they are mostly + * needed for RTL languages which need more proper shaping support. */ + if constexpr (!T::SUPPORTS_RTL) continue; + + buff += T::AppendToBuffer(buff, buffer_last, c); + if (buff >= buffer_last) break; + continue; + } + + FontIndex font_index = FontCache::GetFontIndexForCharacter(state.fontsize, c); + + if (font_index == INVALID_FONT_INDEX) { + font_index = FontCache::GetDefaultFontIndex(state.fontsize); + } + + if (state.font_index == font_index) { + buff += T::AppendToBuffer(buff, buffer_last, c); + if (buff >= buffer_last) break; + continue; + } + + /* This character goes in the next run so don't advance. */ + state.font_index = font_index; + + it = cur; } - if (font_mapping.empty() || font_mapping.back().first != buff - buff_begin) { + if (buff - buff_begin > 0 && (font_mapping.empty() || font_mapping.back().first != buff - buff_begin)) { font_mapping.emplace_back(buff - buff_begin, f); } - f = Layouter::GetFont(state.fontsize, state.cur_colour); + f = {state.font_index, state.cur_colour}; } /* Better safe than sorry. */ @@ -116,6 +125,14 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s if (font_mapping.empty() || font_mapping.back().first != buff - buff_begin) { font_mapping.emplace_back(buff - buff_begin, f); } + + if constexpr (!std::is_same_v) { + /* Don't layout if all runs use a built-in font and we're not using the fallback layouter. */ + if (std::ranges::all_of(font_mapping, [](const auto &i) { return i.second.GetFontCache().IsBuiltInFont(); })) { + return; + } + } + line.layout = T::GetParagraphLayout(buff_begin, buff, font_mapping); line.state_after = state; } @@ -128,7 +145,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s */ Layouter::Layouter(std::string_view str, int maxw, FontSize fontsize) : string(str) { - FontState state(TC_INVALID, fontsize); + FontState state(TC_INVALID, fontsize, FontCache::GetDefaultFontIndex(fontsize)); while (true) { auto line_length = str.find_first_of('\n'); @@ -338,18 +355,6 @@ ptrdiff_t Layouter::GetCharAtPosition(int x, size_t line_index) const return -1; } -/** - * Get a static font instance. - */ -Font *Layouter::GetFont(FontSize size, TextColour colour) -{ - FontColourMap::iterator it = fonts[size].find(colour); - if (it != fonts[size].end()) return it->second.get(); - - fonts[size][colour] = std::make_unique(size, colour); - return fonts[size][colour].get(); -} - /** * Perform initialization of layout engine. */ @@ -362,12 +367,9 @@ void Layouter::Initialize() /** * Reset cached font information. - * @param size Font size to reset. */ -void Layouter::ResetFontCache(FontSize size) +void Layouter::ResetFontCache([[maybe_unused]] FontSize size) { - fonts[size].clear(); - /* We must reset the linecache since it references the just freed fonts */ ResetLineCache(); diff --git a/src/gfx_layout.h b/src/gfx_layout.h index b37741a59b..fa33c1f55c 100644 --- a/src/gfx_layout.h +++ b/src/gfx_layout.h @@ -12,8 +12,6 @@ #include "misc/lrucache.hpp" #include "fontcache.h" -#include "gfx_func.h" -#include "core/math_func.hpp" #include @@ -22,12 +20,13 @@ * of the same text, e.g. on line breaks. */ struct FontState { - FontSize fontsize; ///< Current font size. - TextColour cur_colour; ///< Current text colour. + FontSize fontsize; ///< Current font size. + FontIndex font_index; ///< Current font index. + TextColour cur_colour; ///< Current text colour. std::vector colour_stack; ///< Stack of colours to assist with colour switching. - FontState() : fontsize(FS_END), cur_colour(TC_INVALID) {} - FontState(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour) {} + FontState() : fontsize(FS_END), font_index(INVALID_FONT_INDEX), cur_colour(TC_INVALID) {} + FontState(TextColour colour, FontSize fontsize, FontIndex font_index) : fontsize(fontsize), font_index(font_index), cur_colour(colour) {} auto operator<=>(const FontState &) const = default; @@ -67,6 +66,7 @@ struct FontState { inline void SetFontSize(FontSize f) { this->fontsize = f; + this->font_index = FontCache::GetDefaultFontIndex(this->fontsize); } }; @@ -85,9 +85,10 @@ template <> struct std::hash { std::size_t operator()(const FontState &state) const noexcept { size_t h1 = std::hash{}(state.fontsize); - size_t h2 = std::hash{}(state.cur_colour); - size_t h3 = std::hash>{}(state.colour_stack); - return h1 ^ (h2 << 1) ^ (h3 << 2); + size_t h2 = std::hash{}(state.font_index); + size_t h3 = std::hash{}(state.cur_colour); + size_t h4 = std::hash>{}(state.colour_stack); + return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3); } }; @@ -96,14 +97,14 @@ template <> struct std::hash { */ class Font { public: - FontCache *fc; ///< The font we are using. - TextColour colour; ///< The colour this font has to be. + FontIndex font_index = INVALID_FONT_INDEX; ///< The font we are using. + TextColour colour = TC_INVALID; ///< The colour this font has to be. - Font(FontSize size, TextColour colour); + inline FontCache &GetFontCache() const { return *FontCache::Get(this->font_index); } }; /** Mapping from index to font. The pointer is owned by FontColourMap. */ -using FontMap = std::vector>; +using FontMap = std::vector>; /** * Interface to glue fallback and normal layouter into one. @@ -129,7 +130,7 @@ public: class VisualRun { public: virtual ~VisualRun() = default; - virtual const Font *GetFont() const = 0; + virtual const Font &GetFont() const = 0; virtual int GetGlyphCount() const = 0; virtual std::span GetGlyphs() const = 0; virtual std::span GetPositions() const = 0; @@ -205,18 +206,14 @@ private: static LineCacheItem &GetCachedParagraphLayout(std::string_view str, const FontState &state); - using FontColourMap = std::map>; - static FontColourMap fonts[FS_END]; public: - static Font *GetFont(FontSize size, TextColour colour); - Layouter(std::string_view str, int maxw = INT32_MAX, FontSize fontsize = FS_NORMAL); Dimension GetBounds(); ParagraphLayouter::Position GetCharPosition(std::string_view::const_iterator ch) const; ptrdiff_t GetCharAtPosition(int x, size_t line_index) const; static void Initialize(); - static void ResetFontCache(FontSize size); + static void ResetFontCache(FontSize fs); static void ResetLineCache(); }; diff --git a/src/gfx_layout_fallback.cpp b/src/gfx_layout_fallback.cpp index d2a43fb602..e24dd3de7b 100644 --- a/src/gfx_layout_fallback.cpp +++ b/src/gfx_layout_fallback.cpp @@ -10,6 +10,7 @@ #include "stdafx.h" #include "gfx_layout_fallback.h" +#include "gfx_func.h" #include "string_func.h" #include "zoom_func.h" @@ -43,15 +44,15 @@ public: std::vector positions; ///< The positions of the glyphs. std::vector glyph_to_char; ///< The char index of the glyphs. - Font *font; ///< The font used to layout these. + Font font; ///< The font used to layout these. public: - FallbackVisualRun(Font *font, const char32_t *chars, int glyph_count, int char_offset, int x); - const Font *GetFont() const override { return this->font; } + FallbackVisualRun(const Font &font, const char32_t *chars, int glyph_count, int char_offset, int x); + const Font &GetFont() const override { return this->font; } int GetGlyphCount() const override { return static_cast(this->glyphs.size()); } std::span GetGlyphs() const override { return this->glyphs; } std::span GetPositions() const override { return this->positions; } - int GetLeading() const override { return this->GetFont()->fc->GetHeight(); } + int GetLeading() const override { return GetCharacterHeight(this->GetFont().GetFontCache().GetSize()); } std::span GetGlyphToCharMap() const override { return this->glyph_to_char; } }; @@ -109,26 +110,20 @@ public: * @param char_offset This run's offset from the start of the layout input string. * @param x The initial x position for this run. */ -FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(Font *font, const char32_t *chars, int char_count, int char_offset, int x) : +FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(const Font &font, const char32_t *chars, int char_count, int char_offset, int x) : font(font) { - const bool isbuiltin = font->fc->IsBuiltInFont(); - this->glyphs.reserve(char_count); this->glyph_to_char.reserve(char_count); this->positions.reserve(char_count); + FontCache &fc = this->font.GetFontCache(); + int y_offset = fc.GetGlyphYOffset();; int advance = x; for (int i = 0; i < char_count; i++) { - const GlyphID &glyph_id = this->glyphs.emplace_back(font->fc->MapCharToGlyph(chars[i])); - int x_advance = font->fc->GetGlyphWidth(glyph_id); - if (isbuiltin) { - this->positions.emplace_back(advance, advance + x_advance - 1, font->fc->GetAscender()); // Apply sprite font's ascender. - } else if (chars[i] >= SCC_SPRITE_START && chars[i] <= SCC_SPRITE_END) { - this->positions.emplace_back(advance, advance + x_advance - 1, (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2); // Align sprite font to centre - } else { - this->positions.emplace_back(advance, advance + x_advance - 1, 0); // No ascender adjustment. - } + const GlyphID &glyph_id = this->glyphs.emplace_back(fc.MapCharToGlyph(chars[i])); + int x_advance = fc.GetGlyphWidth(glyph_id); + this->positions.emplace_back(advance, advance + x_advance - 1, y_offset); // No ascender adjustment. advance += x_advance; this->glyph_to_char.push_back(char_offset + i); } @@ -233,7 +228,8 @@ std::unique_ptr FallbackParagraphLayout::NextLine assert(iter != this->runs.end()); } - const FontCache *fc = iter->second->fc; + const FontCache *fc = &iter->second.GetFontCache(); + assert(fc != nullptr); const char32_t *next_run = this->buffer_begin + iter->first; const char32_t *begin = this->buffer; @@ -251,6 +247,7 @@ std::unique_ptr FallbackParagraphLayout::NextLine if (this->buffer == next_run) { int w = l->GetWidth(); + assert(iter->second.font_index != INVALID_FONT_INDEX); l->emplace_back(iter->second, begin, this->buffer - begin, begin - this->buffer_begin, w); ++iter; assert(iter != this->runs.end()); diff --git a/src/gfx_layout_fallback.h b/src/gfx_layout_fallback.h index bd93ac0ab7..ad1858062c 100644 --- a/src/gfx_layout_fallback.h +++ b/src/gfx_layout_fallback.h @@ -26,5 +26,24 @@ public: static size_t AppendToBuffer(char32_t *buff, const char32_t *buffer_last, char32_t c); }; +/** + * Swap paired brackets for fallback RTL layouting. + * @param c Character to swap. + * @return Swapped character, or original character if it is not a paired bracket. + */ +inline char32_t SwapRtlPairedCharacters(char32_t c) +{ + /* There are many more paired brackets, but for fallback purposes we only handle ASCII brackets. */ + /* https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt */ + switch (c) { + case U'(': return U')'; + case U')': return U'('; + case U'[': return U']'; + case U']': return U'['; + case U'{': return U'}'; + case U'}': return U'{'; + default: return c; + } +} #endif /* GFX_LAYOUT_FALLBACK_H */ diff --git a/src/gfx_layout_icu.cpp b/src/gfx_layout_icu.cpp index d7ee97e7ce..a5ec766972 100644 --- a/src/gfx_layout_icu.cpp +++ b/src/gfx_layout_icu.cpp @@ -11,6 +11,9 @@ #include "gfx_layout_icu.h" #include "debug.h" +#include "gfx_func.h" +#include "gfx_layout_fallback.h" +#include "string_func.h" #include "strings_func.h" #include "language.h" #include "table/control_codes.h" @@ -40,7 +43,7 @@ public: int length; ///< Length of the run in the buffer. UBiDiLevel level; ///< Embedding level of the run. UScriptCode script; ///< Script of the run. - Font *font; ///< Font of the run. + Font font; ///< Font of the run. std::vector glyphs; ///< The glyphs of the run. Valid after Shape() is called. std::vector advance; ///< The advance (width) of the glyphs. Valid after Shape() is called. @@ -48,9 +51,10 @@ public: std::vector positions; ///< The positions of the glyphs. Valid after Shape() is called. int total_advance = 0; ///< The total advance of the run. Valid after Shape() is called. - ICURun(int start, int length, UBiDiLevel level, UScriptCode script = USCRIPT_UNKNOWN, Font *font = nullptr) : start(start), length(length), level(level), script(script), font(font) {} + ICURun(int start, int length, UBiDiLevel level, UScriptCode script, const Font &font) : start(start), length(length), level(level), script(script), font(font) {} void Shape(UChar *buff, size_t length); + void FallbackShape(UChar *buff); }; /** @@ -66,7 +70,7 @@ public: std::vector glyph_to_char; int total_advance; - const Font *font; + Font font; public: ICUVisualRun(const ICURun &run, int x); @@ -75,8 +79,8 @@ public: std::span GetPositions() const override { return this->positions; } std::span GetGlyphToCharMap() const override { return this->glyph_to_char; } - const Font *GetFont() const override { return this->font; } - int GetLeading() const override { return this->font->fc->GetHeight(); } + const Font &GetFont() const override { return this->font; } + int GetLeading() const override { return GetCharacterHeight(this->font.GetFontCache().GetSize()); } int GetGlyphCount() const override { return this->glyphs.size(); } int GetAdvance() const { return this->total_advance; } }; @@ -141,6 +145,46 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) : } } +/** + * Manually shape a run for built-in non-truetype fonts. + * Similar to but not quite the same as \a UniscribeRun::FallbackShape. + * @param buff The complete buffer of the run. + */ +void ICURun::FallbackShape(UChar *buff) +{ + FontCache &fc = this->font.GetFontCache(); + + this->glyphs.reserve(this->length); + this->glyph_to_char.reserve(this->length); + + /* Read each UTF-16 character, mapping to an appropriate glyph. */ + for (int i = this->start; i < this->start + this->length; i += Utf16IsLeadSurrogate(buff[i]) ? 2 : 1) { + char32_t c = Utf16DecodeChar(reinterpret_cast(buff + i)); + if (this->level & 1) c = SwapRtlPairedCharacters(c); + this->glyphs.emplace_back(fc.MapCharToGlyph(c)); + this->glyph_to_char.push_back(i); + } + + /* Reverse the sequence if this run is RTL. */ + if (this->level & 1) { + std::reverse(std::begin(this->glyphs), std::end(this->glyphs)); + std::reverse(std::begin(this->glyph_to_char), std::end(this->glyph_to_char)); + } + + this->positions.reserve(this->glyphs.size()); + + /* Set positions of each glyph. */ + int y_offset = fc.GetGlyphYOffset(); + int advance = 0; + for (const GlyphID glyph : this->glyphs) { + int x_advance = fc.GetGlyphWidth(glyph); + this->positions.emplace_back(advance, advance + x_advance - 1, y_offset); + this->advance.push_back(x_advance); + advance += x_advance; + } + this->total_advance = advance; +} + /** * Shape a single run. * @@ -149,7 +193,20 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) : */ void ICURun::Shape(UChar *buff, size_t buff_length) { - auto hbfont = hb_ft_font_create_referenced(*(static_cast(font->fc->GetOSHandle()))); + FontCache &fc = this->font.GetFontCache(); + + /* Make sure any former run is lost. */ + this->glyphs.clear(); + this->glyph_to_char.clear(); + this->positions.clear(); + this->advance.clear(); + + if (fc.IsBuiltInFont()) { + this->FallbackShape(buff); + return; + } + + auto hbfont = hb_ft_font_create_referenced(*(static_cast(fc.GetOSHandle()))); /* Match the flags with how we render the glyphs. */ hb_ft_font_set_load_flags(hbfont, GetFontAAState() ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO); @@ -170,12 +227,6 @@ void ICURun::Shape(UChar *buff, size_t buff_length) auto glyph_info = hb_buffer_get_glyph_infos(hbbuf, &glyph_count); auto glyph_pos = hb_buffer_get_glyph_positions(hbbuf, &glyph_count); - /* Make sure any former run is lost. */ - this->glyphs.clear(); - this->glyph_to_char.clear(); - this->positions.clear(); - this->advance.clear(); - /* Reserve space, as we already know the size. */ this->glyphs.reserve(glyph_count); this->glyph_to_char.reserve(glyph_count); @@ -183,20 +234,12 @@ void ICURun::Shape(UChar *buff, size_t buff_length) this->advance.reserve(glyph_count); /* Prepare the glyphs/position. ICUVisualRun will give the position an offset if needed. */ + int y_offset = fc.GetGlyphYOffset(); hb_position_t advance = 0; for (unsigned int i = 0; i < glyph_count; i++) { - int x_advance; - - if (buff[glyph_info[i].cluster] >= SCC_SPRITE_START && buff[glyph_info[i].cluster] <= SCC_SPRITE_END && glyph_info[i].codepoint == 0) { - auto glyph = this->font->fc->MapCharToGlyph(buff[glyph_info[i].cluster]); - x_advance = this->font->fc->GetGlyphWidth(glyph); - this->glyphs.push_back(glyph); - this->positions.emplace_back(advance, advance + x_advance - 1, (this->font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(this->font->fc->GetSize()))) / 2); // Align sprite font to centre - } else { - x_advance = glyph_pos[i].x_advance / FONT_SCALE; - this->glyphs.push_back(glyph_info[i].codepoint); - this->positions.emplace_back(glyph_pos[i].x_offset / FONT_SCALE + advance, glyph_pos[i].x_offset / FONT_SCALE + advance + x_advance - 1, glyph_pos[i].y_offset / FONT_SCALE); - } + int x_advance = glyph_pos[i].x_advance / FONT_SCALE; + this->glyphs.push_back(glyph_info[i].codepoint); + this->positions.emplace_back(glyph_pos[i].x_offset / FONT_SCALE + advance, glyph_pos[i].x_offset / FONT_SCALE + advance + x_advance - 1, glyph_pos[i].y_offset / FONT_SCALE + y_offset); this->glyph_to_char.push_back(glyph_info[i].cluster); this->advance.push_back(x_advance); @@ -280,7 +323,7 @@ std::vector ItemizeBidi(UChar *buff, size_t length) UBiDiLevel level; ubidi_getLogicalRun(ubidi, start_pos, &logical_pos, &level); - runs.emplace_back(start_pos, logical_pos - start_pos, level); + runs.emplace_back(start_pos, logical_pos - start_pos, level, USCRIPT_UNKNOWN, Font{}); } assert(static_cast(count) == runs.size()); @@ -311,7 +354,7 @@ std::vector ItemizeScript(UChar *buff, size_t length, std::vectorstart + cur_run->length); assert(stop_pos - cur_pos > 0); - runs.emplace_back(cur_pos, stop_pos - cur_pos, cur_run->level, script_itemizer.getScriptCode()); + runs.emplace_back(cur_pos, stop_pos - cur_pos, cur_run->level, script_itemizer.getScriptCode(), Font{}); if (stop_pos == cur_run->start + cur_run->length) cur_run++; cur_pos = stop_pos; @@ -359,11 +402,6 @@ std::vector ItemizeStyle(std::vector &runs_current, FontMap &fon /* Can't layout an empty string. */ if (length == 0) return nullptr; - /* Can't layout our in-built sprite fonts. */ - for (auto const &[position, font] : font_mapping) { - if (font->fc->IsBuiltInFont()) return nullptr; - } - auto runs = ItemizeBidi(buff, length); runs = ItemizeScript(buff, length, runs); runs = ItemizeStyle(runs, font_mapping); diff --git a/src/lang/english.txt b/src/lang/english.txt index c95775c37d..3bcfc2b4a5 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1090,6 +1090,8 @@ STR_GAME_OPTIONS_GUI_SCALE_BEVELS_TOOLTIP :Check this box STR_GAME_OPTIONS_GUI_FONT_SPRITE :Use traditional sprite font STR_GAME_OPTIONS_GUI_FONT_SPRITE_TOOLTIP :Check this box if you prefer to use the traditional fixed-size sprite font +STR_GAME_OPTIONS_GUI_FONT_DEFAULT :Prefer default font +STR_GAME_OPTIONS_GUI_FONT_DEFAULT_TOOLTIP :Check this box if you prefer to use the default font over discovered fallback fonts STR_GAME_OPTIONS_GUI_FONT_AA :Anti-alias fonts STR_GAME_OPTIONS_GUI_FONT_AA_TOOLTIP :Check this box to anti-alias resizable fonts diff --git a/src/os/macosx/font_osx.cpp b/src/os/macosx/font_osx.cpp index e406a790b2..58e46fbd50 100644 --- a/src/os/macosx/font_osx.cpp +++ b/src/os/macosx/font_osx.cpp @@ -94,7 +94,7 @@ void CoreTextFontCache::SetFontSize(int pixels) Debug(fontcache, 2, "Loaded font '{}' with size {}", this->font_name, pixels); } -GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) +GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key) { assert(IsPrintable(key)); @@ -112,10 +112,6 @@ GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) return glyph[0]; } - if (allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { - return this->parent->MapCharToGlyph(key); - } - return 0; } @@ -211,29 +207,19 @@ public: * fallback search, use it. Otherwise, try to resolve it by font name. * @param fs The font size to load. */ - std::unique_ptr LoadFont(FontSize fs, FontType fonttype) const override + std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span) const override { if (fonttype != FontType::TrueType) return nullptr; - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); - - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return nullptr; - CFAutoRelease font_ref; - if (settings->os_handle != nullptr) { - font_ref.reset(static_cast(const_cast(settings->os_handle))); - CFRetain(font_ref.get()); // Increase ref count to match a later release. - } - - if (!font_ref && MacOSVersionIsAtLeast(10, 6, 0)) { + if (MacOSVersionIsAtLeast(10, 6, 0)) { /* Might be a font file name, try load it. */ font_ref.reset(LoadFontFromFile(font)); if (!font_ref) ShowInfo("Unable to load file '{}' for {} font, using default OS font selection instead", font, FontSizeToName(fs)); } - if (!font_ref) { + if (!font_ref && search) { CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, font.c_str(), kCFStringEncodingUTF8)); /* Simply creating the font using CTFontCreateWithNameAndSize will *always* return @@ -259,7 +245,7 @@ public: return std::make_unique(fs, std::move(font_ref), GetFontCacheFontSize(fs)); } - bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) const override + bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override { /* Determine fallback font using CoreText. This uses the language isocode * to find a suitable font. CoreText is available from 10.5 onwards. */ @@ -305,7 +291,7 @@ public: /* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */ if (symbolic_traits & kCTFontBoldTrait) continue; /* Select monospaced fonts if asked for. */ - if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue; + if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != fontsizes.Test(FS_MONO)) continue; /* Get font name. */ char buffer[128]; @@ -323,8 +309,9 @@ public: if (name.starts_with(".") || name.starts_with("LastResort")) continue; /* Save result. */ - callback->SetFontNames(settings, name); - if (!callback->FindMissingGlyphs()) { + FontCache::AddFallback(fontsizes, callback->GetLoadReason(), name); + + if (callback->FindMissingGlyphs().None()) { Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name); result = true; break; @@ -335,8 +322,8 @@ 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. */ - callback->SetFontNames(settings, "Arial Unicode MS"); - result = !callback->FindMissingGlyphs(); + FontCache::AddFallback(fontsizes, callback->GetLoadReason(), "Arial Unicode MS"); + result = callback->FindMissingGlyphs().None(); } callback->FindMissingGlyphs(); diff --git a/src/os/macosx/font_osx.h b/src/os/macosx/font_osx.h index 2ba91d485d..48de29d62e 100644 --- a/src/os/macosx/font_osx.h +++ b/src/os/macosx/font_osx.h @@ -28,7 +28,7 @@ public: ~CoreTextFontCache() {} void ClearFontCache() override; - GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override; + GlyphID MapCharToGlyph(char32_t key) override; std::string GetFontName() override { return font_name; } bool IsBuiltInFont() override { return false; } const void *GetOSHandle() override { return font.get(); } diff --git a/src/os/macosx/string_osx.cpp b/src/os/macosx/string_osx.cpp index f142fb48b0..ed43d8cbba 100644 --- a/src/os/macosx/string_osx.cpp +++ b/src/os/macosx/string_osx.cpp @@ -9,6 +9,7 @@ #include "../../stdafx.h" #include "string_osx.h" +#include "../../gfx_func.h" #include "../../string_func.h" #include "../../strings_func.h" #include "../../core/utf8.hpp" @@ -52,7 +53,7 @@ extern "C" { /** Cached current locale. */ static CFAutoRelease _osx_locale; /** CoreText cache for font information, cleared when OTTD changes fonts. */ -static CFAutoRelease _font_cache[FS_END]; +static std::unordered_map> _font_cache; /** @@ -60,7 +61,6 @@ static CFAutoRelease _font_cache[FS_END]; */ class CoreTextParagraphLayout : public ParagraphLayouter { private: - const CoreTextParagraphLayoutFactory::CharType *text_buffer; ptrdiff_t length; const FontMap &font_map; @@ -77,18 +77,18 @@ public: std::vector glyph_to_char; int total_advance = 0; - Font *font; + Font font; public: - CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff); + CoreTextVisualRun(CTRunRef run, const Font &font); CoreTextVisualRun(CoreTextVisualRun &&other) = default; std::span GetGlyphs() const override { return this->glyphs; } std::span GetPositions() const override { return this->positions; } std::span GetGlyphToCharMap() const override { return this->glyph_to_char; } - const Font *GetFont() const override { return this->font; } - int GetLeading() const override { return this->font->fc->GetHeight(); } + const Font &GetFont() const override { return this->font; } + int GetLeading() const override { return GetCharacterHeight(this->font.GetFontCache().GetSize()); } int GetGlyphCount() const override { return (int)this->glyphs.size(); } int GetAdvance() const { return this->total_advance; } }; @@ -96,7 +96,7 @@ public: /** A single line worth of VisualRuns. */ class CoreTextLine : public std::vector, public ParagraphLayouter::Line { public: - CoreTextLine(CFAutoRelease line, const FontMap &font_mapping, const CoreTextParagraphLayoutFactory::CharType *buff) + CoreTextLine(CFAutoRelease line, const FontMap &font_mapping) { CFArrayRef runs = CTLineGetGlyphRuns(line.get()); for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) { @@ -104,9 +104,9 @@ public: /* Extract font information for this run. */ CFRange chars = CTRunGetStringRange(run); - auto map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair::first); + const auto &map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair::first); - this->emplace_back(run, map->second, buff); + this->emplace_back(run, map->second); } } @@ -122,7 +122,7 @@ public: } }; - CoreTextParagraphLayout(CFAutoRelease typesetter, const CoreTextParagraphLayoutFactory::CharType *buffer, ptrdiff_t len, const FontMap &font_mapping) : text_buffer(buffer), length(len), font_map(font_mapping), typesetter(std::move(typesetter)) + CoreTextParagraphLayout(CFAutoRelease typesetter, ptrdiff_t len, const FontMap &font_mapping) : length(len), font_map(font_mapping), typesetter(std::move(typesetter)) { this->Reflow(); } @@ -137,17 +137,17 @@ public: /** Get the width of an encoded sprite font character. */ -static CGFloat SpriteFontGetWidth(void *ref_con) +static CGFloat CustomFontGetWidth(void *ref_con) { - FontSize fs = (FontSize)((size_t)ref_con >> 24); - char32_t c = (char32_t)((size_t)ref_con & 0xFFFFFF); + FontIndex fi = static_cast(reinterpret_cast(ref_con) >> 24); + char32_t c = static_cast(reinterpret_cast(ref_con) & 0xFFFFFF); - return GetGlyphWidth(fs, c); + return FontCache::Get(fi)->GetGlyphWidth(c); } static const CTRunDelegateCallbacks _sprite_font_callback = { kCTRunDelegateCurrentVersion, nullptr, nullptr, nullptr, - &SpriteFontGetWidth + &CustomFontGetWidth }; /* static */ std::unique_ptr CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &font_mapping) @@ -158,11 +158,6 @@ static const CTRunDelegateCallbacks _sprite_font_callback = { ptrdiff_t length = buff_end - buff; if (length == 0) return nullptr; - /* Can't layout our in-built sprite fonts. */ - for (const auto &[position, font] : font_mapping) { - if (font->fc->IsBuiltInFont()) return nullptr; - } - /* Make attributed string with embedded font information. */ CFAutoRelease str(CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)); CFAttributedStringBeginEditing(str.get()); @@ -179,25 +174,26 @@ static const CTRunDelegateCallbacks _sprite_font_callback = { for (const auto &[position, font] : font_mapping) { if (position - last == 0) continue; - CTFontRef font_handle = static_cast(font->fc->GetOSHandle()); + FontCache &fc = font.GetFontCache(); + CTFontRef font_handle = static_cast(fc.GetOSHandle()); if (font_handle == nullptr) { - if (!_font_cache[font->fc->GetSize()]) { + if (!_font_cache[fc.GetIndex()]) { /* Cache font information. */ - CFAutoRelease font_name(CFStringCreateWithCString(kCFAllocatorDefault, font->fc->GetFontName().c_str(), kCFStringEncodingUTF8)); - _font_cache[font->fc->GetSize()].reset(CTFontCreateWithName(font_name.get(), font->fc->GetFontSize(), nullptr)); + CFAutoRelease font_name(CFStringCreateWithCString(kCFAllocatorDefault, fc.GetFontName().c_str(), kCFStringEncodingUTF8)); + _font_cache[fc.GetIndex()].reset(CTFontCreateWithName(font_name.get(), fc.GetFontSize(), nullptr)); } - font_handle = _font_cache[font->fc->GetSize()].get(); + font_handle = _font_cache[fc.GetIndex()].get(); } CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle); - CGColorRef colour = CGColorCreateGenericGray((uint8_t)font->colour / 255.0f, 1.0f); // We don't care about the real colours, just that they are different. + CGColorRef colour = CGColorCreateGenericGray((uint8_t)font.colour / 255.0f, 1.0f); // We don't care about the real colours, just that they are different. CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, colour); CGColorRelease(colour); - /* Install a size callback for our special private-use sprite glyphs in case the font does not provide them. */ - for (ssize_t c = last; c < position; c++) { - if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END && font->fc->MapCharToGlyph(buff[c], false) == 0) { - CFAutoRelease del(CTRunDelegateCreate(&_sprite_font_callback, (void *)(size_t)(buff[c] | (font->fc->GetSize() << 24)))); + /* Install a size callback for our custom fonts. */ + if (fc.IsBuiltInFont()) { + for (ssize_t c = last; c < position; c++) { + CFAutoRelease del(CTRunDelegateCreate(&_sprite_font_callback, reinterpret_cast(static_cast(buff[c] | (fc.GetIndex() << 24))))); /* According to the official documentation, if a run delegate is used, the char should always be 0xFFFC. */ CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacement_str.get()); CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get()); @@ -211,7 +207,7 @@ static const CTRunDelegateCallbacks _sprite_font_callback = { /* Create and return typesetter for the string. */ CFAutoRelease typesetter(CTTypesetterCreateWithAttributedString(str.get())); - return typesetter ? std::make_unique(std::move(typesetter), buff, length, font_mapping) : nullptr; + return typesetter ? std::make_unique(std::move(typesetter), length, font_mapping) : nullptr; } /* virtual */ std::unique_ptr CoreTextParagraphLayout::NextLine(int max_width) @@ -227,10 +223,10 @@ static const CTRunDelegateCallbacks _sprite_font_callback = { this->cur_offset += len; if (!line) return nullptr; - return std::make_unique(std::move(line), this->font_map, this->text_buffer); + return std::make_unique(std::move(line), this->font_map); } -CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff) : font(font) +CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, const Font &font) : font(font) { this->glyphs.resize(CTRunGetGlyphCount(run)); @@ -247,19 +243,15 @@ CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font CTRunGetAdvances(run, CFRangeMake(0, 0), advs.get()); this->positions.reserve(this->glyphs.size()); + int y_offset = this->font.GetFontCache().GetGlyphYOffset(); + /* Convert glyph array to our data type. At the same time, substitute * the proper glyphs for our private sprite glyphs. */ auto gl = std::make_unique(this->glyphs.size()); CTRunGetGlyphs(run, CFRangeMake(0, 0), gl.get()); for (size_t i = 0; i < this->glyphs.size(); i++) { - if (buff[this->glyph_to_char[i]] >= SCC_SPRITE_START && buff[this->glyph_to_char[i]] <= SCC_SPRITE_END && (gl[i] == 0 || gl[i] == 3)) { - /* A glyph of 0 indicates not found, while apparently 3 is what char 0xFFFC maps to. */ - this->glyphs[i] = font->fc->MapCharToGlyph(buff[this->glyph_to_char[i]]); - this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2); // Align sprite font to centre - } else { - this->glyphs[i] = gl[i]; - this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y); - } + this->glyphs[i] = gl[i]; + this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y + y_offset); } this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nullptr, nullptr, nullptr)); } diff --git a/src/os/unix/font_unix.cpp b/src/os/unix/font_unix.cpp index 978695a853..3fa88cefc5 100644 --- a/src/os/unix/font_unix.cpp +++ b/src/os/unix/font_unix.cpp @@ -120,6 +120,10 @@ FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face) } } + if (err != FT_Err_Ok) { + ShowInfo("Unable to find '{}' font", font_name); + } + return err; } @@ -139,7 +143,7 @@ static int GetPreferredWeightDistance(int weight) return 0; } -bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) +bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) { bool ret = false; @@ -148,22 +152,25 @@ bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string & auto fc_instance = AutoRelease(FcConfigReference(nullptr)); assert(fc_instance != nullptr); + /* Get set of required characters. */ + auto chars = callback->GetRequiredGlyphs(fontsizes); + /* Fontconfig doesn't handle full language isocodes, only the part * before the _ of e.g. en_GB is used, so "remove" everything after * the _. */ - std::string lang = fmt::format(":lang={}", language_isocode.substr(0, language_isocode.find('_'))); + std::string lang = language_isocode.empty() ? "" : fmt::format(":lang={}", language_isocode.substr(0, language_isocode.find('_'))); /* First create a pattern to match the wanted language. */ auto pat = AutoRelease(FcNameParse(ToFcString(lang))); /* We only want to know these attributes. */ - auto os = AutoRelease(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, nullptr)); + auto os = AutoRelease(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(FcFontList(nullptr, pat.get(), os.get())); if (fs == nullptr) return ret; int best_weight = -1; - const char *best_font = nullptr; + std::string best_font; int best_index = 0; for (FcPattern *font : std::span(fs->fonts, fs->nfont)) { @@ -174,7 +181,7 @@ bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string & /* Get a font with the right spacing .*/ int value = 0; FcPatternGetInteger(font, FC_SPACING, 0, &value); - if (callback->Monospace() != (value == FC_MONO) && value != FC_DUAL) continue; + if (fontsizes.Test(FS_MONO) != (value == FC_MONO) && value != FC_DUAL) continue; /* Do not use those that explicitly say they're slanted. */ FcPatternGetInteger(font, FC_SLANT, 0, &value); @@ -185,26 +192,32 @@ bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string & int weight = GetPreferredWeightDistance(value); if (best_weight != -1 && weight > best_weight) continue; + size_t matching_chars = 0; + FcCharSet *charset; + FcPatternGetCharSet(font, FC_CHARSET, 0, &charset); + for (const char32_t &c : chars) { + if (FcCharSetHasChar(charset, c)) ++matching_chars; + } + + if (matching_chars < chars.size()) { + Debug(fontcache, 1, "Font \"{}\" misses {} glyphs", reinterpret_cast(file), chars.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; - callback->SetFontNames(settings, 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; + if (best_font.empty()) return false; + + FontCache::AddFallbackWithHandle(fontsizes, callback->GetLoadReason(), best_font, best_index); + FontCache::LoadFontCaches(fontsizes); - callback->SetFontNames(settings, best_font, &best_index); - FontCache::LoadFontCaches(callback->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); return true; } diff --git a/src/os/unix/font_unix.h b/src/os/unix/font_unix.h index bd1eea4416..37392d800d 100644 --- a/src/os/unix/font_unix.h +++ b/src/os/unix/font_unix.h @@ -19,7 +19,7 @@ FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face); -bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); +bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback); #endif /* WITH_FONTCONFIG */ diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index 24100a1704..d3a4c7be17 100644 --- a/src/os/windows/font_win32.cpp +++ b/src/os/windows/font_win32.cpp @@ -31,8 +31,8 @@ #include "../../safeguards.h" struct EFCParam { - FontCacheSettings *settings; - LOCALESIGNATURE locale; + LOCALESIGNATURE locale; + FontSizes fontsizes; MissingGlyphSearcher *callback; std::vector fonts; @@ -58,7 +58,7 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT /* Don't use SYMBOL fonts */ if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1; /* Use monospaced fonts when asked for it. */ - if (info->callback->Monospace() && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1; + if (info->fontsizes.Test(FS_MONO) && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1; /* The font has to have at least one of the supported locales to be usable. */ auto check_bitfields = [&]() { @@ -77,8 +77,8 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT char font_name[MAX_PATH]; convert_from_fs(logfont->elfFullName, font_name); - info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont); - if (info->callback->FindMissingGlyphs()) return 1; + FontCache::AddFallbackWithHandle(info->fontsizes, info->callback->GetLoadReason(), font_name, logfont->elfLogFont); + if (info->callback->FindMissingGlyphs().None()) return 1; Debug(fontcache, 1, "Fallback font: {}", font_name); return 0; // stop enumerating } @@ -158,7 +158,7 @@ void Win32FontCache::SetFontSize(int pixels) this->fontname = FS2OTTD((LPWSTR)((BYTE *)otm + (ptrdiff_t)otm->otmpFaceName)); - Debug(fontcache, 2, "Loaded font '{}' with size {}", this->fontname, pixels); + Debug(fontcache, 2, "Win32FontCache: Loaded font '{}' with size {}", this->fontname, pixels); delete[] (BYTE*)otm; } @@ -245,7 +245,7 @@ void Win32FontCache::ClearFontCache() return this->SetGlyphPtr(key, std::move(new_glyph)).GetSprite(); } -/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(char32_t key, bool allow_fallback) +/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(char32_t key) { assert(IsPrintable(key)); @@ -262,7 +262,7 @@ void Win32FontCache::ClearFontCache() GetGlyphIndicesW(this->dc, chars, key >= 0x010000U ? 2 : 1, glyphs, GGI_MARK_NONEXISTING_GLYPHS); if (glyphs[0] != 0xFFFF) return glyphs[0]; - return allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END ? this->parent->MapCharToGlyph(key) : 0; + return 0; } class Win32FontCacheFactory : FontCacheFactory { @@ -270,32 +270,28 @@ public: Win32FontCacheFactory() : FontCacheFactory("win32", "Win32 font loader") {} /** - * Loads the GDI font. - * If a GDI font description is present, e.g. from the automatic font - * fallback search, use it. Otherwise, try to resolve it by font name. - * @param fs The font size to load. - */ - std::unique_ptr LoadFont(FontSize fs, FontType fonttype) const override + * Loads the GDI font. + * If a GDI font description is present, e.g. from the automatic font + * fallback search, use it. Otherwise, try to resolve it by font name. + * @param fs The font size to load. + */ + std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span os_handle) const override { if (fonttype != FontType::TrueType) return nullptr; - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); - - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return nullptr; - LOGFONT logfont{}; logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH; logfont.lfCharSet = DEFAULT_CHARSET; logfont.lfOutPrecision = OUT_OUTLINE_PRECIS; logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; - if (settings->os_handle != nullptr) { - logfont = *(const LOGFONT *)settings->os_handle; + if (!os_handle.empty()) { + logfont = *reinterpret_cast(os_handle.data()); } else if (font.find('.') != std::string::npos) { /* Might be a font file name, try load it. */ if (!TryLoadFontFromFile(font, logfont)) { ShowInfo("Unable to load file '{}' for {} font, using default windows font selection instead", font, FontSizeToName(fs)); + if (!search) return nullptr; } } @@ -307,7 +303,7 @@ public: return LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); } - bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) const override + bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override { Debug(fontcache, 1, "Trying fallback fonts"); EFCParam langInfo; @@ -317,7 +313,7 @@ public: Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode); return false; } - langInfo.settings = settings; + langInfo.fontsizes = fontsizes; langInfo.callback = callback; LOGFONT font; diff --git a/src/os/windows/font_win32.h b/src/os/windows/font_win32.h index 8aa0a42180..cf4d661f92 100644 --- a/src/os/windows/font_win32.h +++ b/src/os/windows/font_win32.h @@ -37,11 +37,11 @@ public: Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels); ~Win32FontCache(); void ClearFontCache() override; - GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override; + GlyphID MapCharToGlyph(char32_t key) override; std::string GetFontName() override { return this->fontname; } const void *GetOSHandle() override { return &this->logfont; } }; -void LoadWin32Font(FontSize fs); +void LoadWin32Font(FontSize fs, bool search, const std::string &font_name, std::span os_handle); #endif /* FONT_WIN32_H */ diff --git a/src/os/windows/string_uniscribe.cpp b/src/os/windows/string_uniscribe.cpp index 210e7a17f0..9ad373cfa0 100644 --- a/src/os/windows/string_uniscribe.cpp +++ b/src/os/windows/string_uniscribe.cpp @@ -10,6 +10,8 @@ #include "../../stdafx.h" #include "../../debug.h" #include "string_uniscribe.h" +#include "../../gfx_func.h" +#include "../../gfx_layout_fallback.h" #include "../../language.h" #include "../../strings_func.h" #include "../../string_func.h" @@ -29,7 +31,7 @@ /** Uniscribe cache for internal font information, cleared when OTTD changes fonts. */ -static SCRIPT_CACHE _script_cache[FS_END]; +static std::map _script_cache; /** * Contains all information about a run of characters. A run are consecutive @@ -38,7 +40,7 @@ static SCRIPT_CACHE _script_cache[FS_END]; struct UniscribeRun { int pos; int len; - Font *font; + Font font; std::vector ft_glyphs; @@ -51,7 +53,9 @@ struct UniscribeRun { std::vector offsets; int total_advance; - UniscribeRun(int pos, int len, Font *font, SCRIPT_ANALYSIS &sa) : pos(pos), len(len), font(font), sa(sa) {} + UniscribeRun(int pos, int len, const Font &font, SCRIPT_ANALYSIS &sa) : pos(pos), len(len), font(font), sa(sa) {} + + void FallbackShape(const UniscribeParagraphLayoutFactory::CharType *buff); }; /** Break a string into language formatting ranges. */ @@ -81,7 +85,7 @@ public: int start_pos; int total_advance; int num_glyphs; - Font *font; + Font font; mutable std::vector glyph_to_char; @@ -93,8 +97,8 @@ public: std::span GetPositions() const override { return this->positions; } std::span GetGlyphToCharMap() const override; - const Font *GetFont() const override { return this->font; } - int GetLeading() const override { return this->font->fc->GetHeight(); } + const Font &GetFont() const override { return this->font; } + int GetLeading() const override { return GetCharacterHeight(this->font.GetFontCache().GetSize()); } int GetGlyphCount() const override { return this->num_glyphs; } int GetAdvance() const { return this->total_advance; } }; @@ -130,31 +134,71 @@ public: std::unique_ptr NextLine(int max_width) override; }; -void UniscribeResetScriptCache(FontSize size) +void UniscribeResetScriptCache(FontSize) { - if (_script_cache[size] != nullptr) { - ScriptFreeCache(&_script_cache[size]); - _script_cache[size] = nullptr; + for (auto &sc : _script_cache) { + ScriptFreeCache(&sc.second); } + _script_cache.clear(); } /** Load the matching native Windows font. */ -static HFONT HFontFromFont(Font *font) +static HFONT HFontFromFont(const Font &font) { - if (font->fc->GetOSHandle() != nullptr) return CreateFontIndirect(reinterpret_cast(const_cast(font->fc->GetOSHandle()))); + FontCache &fc = font.GetFontCache(); + + if (fc.GetOSHandle() != nullptr) return CreateFontIndirect(reinterpret_cast(const_cast(fc.GetOSHandle()))); LOGFONT logfont{}; - logfont.lfHeight = font->fc->GetHeight(); + logfont.lfHeight = fc.GetHeight(); logfont.lfWeight = FW_NORMAL; logfont.lfCharSet = DEFAULT_CHARSET; - convert_to_fs(font->fc->GetFontName(), logfont.lfFaceName); + convert_to_fs(fc.GetFontName(), logfont.lfFaceName); return CreateFontIndirect(&logfont); } +/** + * Manually shape a run for built-in non-truetype fonts. + * Similar to but not quite the same as \a ICURun::FallbackShape. + * @param buff The complete buffer of the run. + */ +void UniscribeRun::FallbackShape(const UniscribeParagraphLayoutFactory::CharType *buff) +{ + FontCache &fc = this->font.GetFontCache(); + + this->glyphs.reserve(this->len); + + /* Read each UTF-16 character, mapping to an appropriate glyph. */ + for (int i = this->pos; i < this->pos + this->len; i += Utf16IsLeadSurrogate(buff[i]) ? 2 : 1) { + char32_t c = Utf16DecodeChar(reinterpret_cast(buff + i)); + if (this->sa.fRTL) c = SwapRtlPairedCharacters(c); + this->glyphs.emplace_back(fc.MapCharToGlyph(c)); + } + + /* Reverse the sequence if this run is RTL. */ + if (this->sa.fRTL) { + std::reverse(std::begin(this->glyphs), std::end(this->glyphs)); + } + + this->offsets.reserve(this->glyphs.size()); + + /* Set positions of each glyph. */ + int y_offset = fc.GetGlyphYOffset(); + int advance = 0; + for (const GlyphID glyph : this->glyphs) { + this->offsets.emplace_back(advance, y_offset); + int x_advance = fc.GetGlyphWidth(glyph); + this->advances.push_back(x_advance); + advance += x_advance; + } +} + /** Determine the glyph positions for a run. */ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *buff, UniscribeRun &range) { + FontCache &fc = range.font.GetFontCache(); + /* Initial size guess for the number of glyphs recommended by Uniscribe. */ range.glyphs.resize(range.len * 3 / 2 + 16); range.vis_attribs.resize(range.glyphs.size()); @@ -166,10 +210,15 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b HFONT old_font = nullptr; HFONT cur_font = nullptr; + if (fc.IsBuiltInFont()) { + range.FallbackShape(buff); + return true; + } + while (true) { /* Shape the text run by determining the glyphs needed for display. */ int glyphs_used = 0; - HRESULT hr = ScriptShape(temp_dc, &_script_cache[range.font->fc->GetSize()], buff + range.pos, range.len, (int)range.glyphs.size(), &range.sa, &range.glyphs[0], &range.char_to_glyph[0], &range.vis_attribs[0], &glyphs_used); + HRESULT hr = ScriptShape(temp_dc, &_script_cache[fc.GetIndex()], buff + range.pos, range.len, (int)range.glyphs.size(), &range.sa, &range.glyphs[0], &range.char_to_glyph[0], &range.vis_attribs[0], &glyphs_used); if (SUCCEEDED(hr)) { range.glyphs.resize(glyphs_used); @@ -179,7 +228,7 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b ABC abc; range.advances.resize(range.glyphs.size()); range.offsets.resize(range.glyphs.size()); - hr = ScriptPlace(temp_dc, &_script_cache[range.font->fc->GetSize()], &range.glyphs[0], (int)range.glyphs.size(), &range.vis_attribs[0], &range.sa, &range.advances[0], &range.offsets[0], &abc); + hr = ScriptPlace(temp_dc, &_script_cache[fc.GetIndex()], &range.glyphs[0], (int)range.glyphs.size(), &range.vis_attribs[0], &range.sa, &range.advances[0], &range.offsets[0], &abc); if (SUCCEEDED(hr)) { /* We map our special sprite chars to values that don't fit into a WORD. Copy the glyphs * into a new vector and query the real glyph to use for these special chars. */ @@ -187,22 +236,12 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b for (size_t g_id = 0; g_id < range.glyphs.size(); g_id++) { range.ft_glyphs[g_id] = range.glyphs[g_id]; } - for (int i = 0; i < range.len; i++) { - if (buff[range.pos + i] >= SCC_SPRITE_START && buff[range.pos + i] <= SCC_SPRITE_END) { - auto pos = range.char_to_glyph[i]; - if (range.ft_glyphs[pos] == 0) { // Font doesn't have our special glyph, so remap. - range.ft_glyphs[pos] = range.font->fc->MapCharToGlyph(buff[range.pos + i]); - range.offsets[pos].dv = (range.font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(range.font->fc->GetSize()))) / 2; // Align sprite font to centre - range.advances[pos] = range.font->fc->GetGlyphWidth(range.ft_glyphs[pos]); - } - } - } range.total_advance = 0; for (size_t i = 0; i < range.advances.size(); i++) { #ifdef WITH_FREETYPE /* FreeType and GDI/Uniscribe seems to occasionally disagree over the width of a glyph. */ - if (range.advances[i] > 0 && range.ft_glyphs[i] != 0xFFFF) range.advances[i] = range.font->fc->GetGlyphWidth(range.ft_glyphs[i]); + if (range.advances[i] > 0 && range.glyphs[i] != 0xFFFF) range.advances[i] = fc.GetGlyphWidth(range.glyphs[i]); #endif range.total_advance += range.advances[i]; } @@ -280,11 +319,6 @@ static std::vector UniscribeItemizeString(UniscribeParagraphLayoutF /* Can't layout an empty string. */ if (length == 0) return nullptr; - /* Can't layout our in-built sprite fonts. */ - for (auto const &[position, font] : font_mapping) { - if (font->fc->IsBuiltInFont()) return nullptr; - } - /* Itemize text. */ std::vector items = UniscribeItemizeString(buff, length); if (items.empty()) return nullptr; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 806d37b688..68268cd362 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -658,6 +658,9 @@ struct GameOptionsWindow : Window { case WID_GO_GUI_FONT_SPRITE_TEXT: return GetToggleString(STR_GAME_OPTIONS_GUI_FONT_SPRITE, WID_GO_GUI_FONT_SPRITE); + case WID_GO_GUI_FONT_DEFAULT_TEXT: + return GetToggleString(STR_GAME_OPTIONS_GUI_FONT_DEFAULT, WID_GO_GUI_FONT_DEFAULT); + case WID_GO_GUI_FONT_AA_TEXT: return GetToggleString(STR_GAME_OPTIONS_GUI_FONT_AA, WID_GO_GUI_FONT_AA); @@ -928,6 +931,18 @@ struct GameOptionsWindow : Window { } } +#ifdef HAS_TRUETYPE_FONT + static void ReloadFonts() + { + FontCache::LoadFontCaches(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); + CheckForMissingGlyphs(); + SetupWidgetDimensions(); + UpdateAllVirtCoords(); + ReInitAllWindows(true); + } +#endif /* HAS_TRUETYPE_FONT */ + void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override { if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_CONTENT_END) { @@ -1038,13 +1053,15 @@ struct GameOptionsWindow : Window { this->SetWidgetLoweredState(WID_GO_GUI_FONT_SPRITE, _fcsettings.prefer_sprite); this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA, _fcsettings.prefer_sprite); this->SetDirty(); + ReloadFonts(); + break; - FontCache::LoadFontCaches(FONTSIZES_ALL); - FontCache::ClearFontCaches(FONTSIZES_ALL); - CheckForMissingGlyphs(); - SetupWidgetDimensions(); - UpdateAllVirtCoords(); - ReInitAllWindows(true); + case WID_GO_GUI_FONT_DEFAULT: + _fcsettings.prefer_default = !_fcsettings.prefer_default; + + this->SetWidgetLoweredState(WID_GO_GUI_FONT_DEFAULT, _fcsettings.prefer_default); + this->SetDirty(); + ReloadFonts(); break; case WID_GO_GUI_FONT_AA: @@ -1542,6 +1559,7 @@ struct GameOptionsWindow : Window { this->SetWidgetLoweredState(WID_GO_GUI_SCALE_BEVEL_BUTTON, _settings_client.gui.scale_bevels); #ifdef HAS_TRUETYPE_FONT this->SetWidgetLoweredState(WID_GO_GUI_FONT_SPRITE, _fcsettings.prefer_sprite); + this->SetWidgetLoweredState(WID_GO_GUI_FONT_DEFAULT, _fcsettings.prefer_default); this->SetWidgetLoweredState(WID_GO_GUI_FONT_AA, _fcsettings.global_aa); this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA, _fcsettings.prefer_sprite); #endif /* HAS_TRUETYPE_FONT */ @@ -1672,6 +1690,10 @@ static constexpr std::initializer_list _nested_game_options_widgets NWidget(WWT_BOOLBTN, GAME_OPTIONS_BACKGROUND, WID_GO_GUI_FONT_SPRITE), SetAlternateColourTip(GAME_OPTIONS_BUTTON, STR_GAME_OPTIONS_GUI_FONT_SPRITE_TOOLTIP), NWidget(WWT_TEXT, INVALID_COLOUR, WID_GO_GUI_FONT_SPRITE_TEXT), SetFill(1, 0), SetResize(1, 0), SetTextStyle(GAME_OPTIONS_LABEL), EndContainer(), + NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), + NWidget(WWT_BOOLBTN, GAME_OPTIONS_BACKGROUND, WID_GO_GUI_FONT_DEFAULT), SetAlternateColourTip(GAME_OPTIONS_BUTTON, STR_GAME_OPTIONS_GUI_FONT_DEFAULT_TOOLTIP), + NWidget(WWT_TEXT, INVALID_COLOUR, WID_GO_GUI_FONT_DEFAULT_TEXT), SetFill(1, 0), SetResize(1, 0), SetTextStyle(GAME_OPTIONS_LABEL), + EndContainer(), NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), NWidget(WWT_BOOLBTN, GAME_OPTIONS_BACKGROUND, WID_GO_GUI_FONT_AA), SetAlternateColourTip(GAME_OPTIONS_BUTTON, STR_GAME_OPTIONS_GUI_FONT_AA_TOOLTIP), NWidget(WWT_TEXT, INVALID_COLOUR, WID_GO_GUI_FONT_AA_TEXT), SetFill(1, 0), SetResize(1, 0), SetTextStyle(GAME_OPTIONS_LABEL), diff --git a/src/strings.cpp b/src/strings.cpp index 141027f0b7..f99116c97b 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2274,32 +2274,56 @@ std::string_view GetCurrentLanguageIsoCode() /** * Check whether there are glyphs missing in the current language. - * @return If glyphs are missing, return \c true, else return \c false. + * @return Bit mask of font sizes have any missing glyphs. */ -bool MissingGlyphSearcher::FindMissingGlyphs() +FontSizes MissingGlyphSearcher::FindMissingGlyphs() { - FontCache::LoadFontCaches(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(this->fontsizes); + + FontSizes bad_fontsizes{}; + + for (FontSize size : this->fontsizes) { + auto set = this->GetRequiredGlyphs(size); + if (set.empty()) continue; + + Debug(fontcache, 1, "Missing {} glyphs in {} font size", set.size(), FontSizeToName(size)); + bad_fontsizes.Set(size); + } + + return bad_fontsizes; +} + +std::set BaseStringMissingGlyphSearcher::GetRequiredGlyphs(FontSizes fontsizes) +{ + std::set glyphs{}; this->Reset(); for (auto text = this->NextString(); text.has_value(); text = this->NextString()) { FontSize size = this->DefaultSize(); - FontCache *fc = FontCache::Get(size); 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(c), FontSizeToName(size)); - return true; + continue; } + + if (IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END)) continue; + if (!IsPrintable(c) || IsTextDirectionChar(c)) continue; + if (fontsizes.Test(size)) continue; + if (FontCache::GetFontIndexForCharacter(size, c) != INVALID_FONT_INDEX) continue; + + glyphs.insert(c); } } - return false; + + return glyphs; } /** Helper for searching through the language pack. */ -class LanguagePackGlyphSearcher : public MissingGlyphSearcher { +class LanguagePackGlyphSearcher : public BaseStringMissingGlyphSearcher { +public: + LanguagePackGlyphSearcher() : BaseStringMissingGlyphSearcher({FS_NORMAL, FS_SMALL, FS_LARGE}) {} + +private: uint i; ///< Iterator for the primary language tables. uint j; ///< Iterator for the secondary language tables. @@ -2328,26 +2352,9 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher { return ret; } - - bool Monospace() override - { - return false; - } - - void SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data) override - { -#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) - settings->small.font = font_name; - settings->medium.font = font_name; - settings->large.font = font_name; - - settings->small.os_handle = os_data; - settings->medium.os_handle = os_data; - settings->large.os_handle = os_data; -#endif - } }; + /** * Check whether the currently loaded language pack * uses characters that the currently loaded font @@ -2364,20 +2371,21 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher) { static LanguagePackGlyphSearcher pack_searcher; if (searcher == nullptr) searcher = &pack_searcher; - bool bad_font = searcher->FindMissingGlyphs(); + + for (FontSize size : searcher->fontsizes) { + GetFontCacheSubSetting(size)->fallback_fonts.clear(); + } + + FontSizes fontsizes = searcher->FindMissingGlyphs(); + bool bad_font = 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 * a fallback font that can print the characters in the current language. */ bool any_font_configured = !_fcsettings.medium.font.empty(); - FontCacheSettings backup = _fcsettings; - _fcsettings.mono.os_handle = nullptr; - _fcsettings.medium.os_handle = nullptr; - - bad_font = !FontProviderManager::FindFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher); - - _fcsettings = std::move(backup); + bad_font = !FontProviderManager::FindFallbackFont(_langpack.langpack->isocode, fontsizes, searcher); if (!bad_font && any_font_configured) { /* If the user configured a bad font, and we found a better one, @@ -2394,7 +2402,7 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher) /* 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->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(fontsizes); } } #endif @@ -2411,12 +2419,12 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher) ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING); /* Reset the font width */ - LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + LoadStringWidthTable(fontsizes); return; } /* Update the font with cache */ - LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + LoadStringWidthTable(searcher->fontsizes); #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA) /* diff --git a/src/strings_func.h b/src/strings_func.h index e883504b41..e397a69197 100644 --- a/src/strings_func.h +++ b/src/strings_func.h @@ -10,6 +10,7 @@ #ifndef STRINGS_FUNC_H #define STRINGS_FUNC_H +#include "fontcache.h" #include "strings_type.h" #include "string_type.h" #include "gfx_type.h" @@ -158,9 +159,33 @@ EncodedString GetEncodedString(StringID string, const Args&... args) */ class MissingGlyphSearcher { public: + FontSizes fontsizes; ///< Font sizes to search for. + + MissingGlyphSearcher(FontSizes fontsizes) : fontsizes(fontsizes) {} + /** Make sure everything gets destructed right. */ virtual ~MissingGlyphSearcher() = default; + /** + * Test if any glyphs are missing. + * @return Font sizes which have missing glyphs. + */ + FontSizes FindMissingGlyphs(); + + virtual FontLoadReason GetLoadReason() = 0; + + /** + * Get set of glyphs required for the current language. + * @param fontsizes Font sizes to test. + * @return Set of required glyphs. + **/ + virtual std::set GetRequiredGlyphs(FontSizes fontsizes) = 0; +}; + +class BaseStringMissingGlyphSearcher : public MissingGlyphSearcher { +public: + BaseStringMissingGlyphSearcher(FontSizes fontsizes) : MissingGlyphSearcher(fontsizes) {} + /** * Get the next string to search through. * @return The next string or nullopt if there is none. @@ -178,23 +203,11 @@ public: */ virtual void Reset() = 0; - /** - * Whether to search for a monospace font or not. - * @return True if searching for monospace. - */ - virtual bool Monospace() = 0; + FontLoadReason GetLoadReason() override { return FontLoadReason::LanguageFallback; } - /** - * Set the right font names. - * @param settings The settings to modify. - * @param font_name The new font name. - * @param os_data Opaque pointer to OS-specific data. - */ - virtual void SetFontNames(struct FontCacheSettings *settings, std::string_view font_name, const void *os_data = nullptr) = 0; - - bool FindMissingGlyphs(); + std::set GetRequiredGlyphs(FontSizes fontsizes) override; }; -void CheckForMissingGlyphs(MissingGlyphSearcher *search = nullptr); +void CheckForMissingGlyphs(MissingGlyphSearcher *searcher = nullptr); #endif /* STRINGS_FUNC_H */ diff --git a/src/survey.cpp b/src/survey.cpp index 3d648b00e1..2c1a1e2428 100644 --- a/src/survey.cpp +++ b/src/survey.cpp @@ -298,10 +298,16 @@ void SurveyConfiguration(nlohmann::json &survey) */ void SurveyFont(nlohmann::json &survey) { - survey["small"] = FontCache::Get(FS_SMALL)->GetFontName(); - survey["medium"] = FontCache::Get(FS_NORMAL)->GetFontName(); - survey["large"] = FontCache::Get(FS_LARGE)->GetFontName(); - survey["mono"] = FontCache::Get(FS_MONO)->GetFontName(); + for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { + const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); + auto &font = survey[std::string(FontSizeToName(fs))]; + font["configured"]["font"] = setting->font; + font["configured"]["size"] = setting->size; + } + for (const auto &fc : FontCache::Get()) { + auto &font = survey[std::string(FontSizeToName(fc->GetSize()))]; + font["active"].push_back(fc->GetFontName()); + } } /** diff --git a/src/table/settings/misc_settings.ini b/src/table/settings/misc_settings.ini index fd39ff102e..a9ad6d2add 100644 --- a/src/table/settings/misc_settings.ini +++ b/src/table/settings/misc_settings.ini @@ -259,6 +259,12 @@ name = ""prefer_sprite_font"" var = _fcsettings.prefer_sprite def = false +[SDTG_BOOL] +ifdef = HAS_TRUETYPE_FONT +name = ""prefer_default_font"" +var = _fcsettings.prefer_default +def = false + [SDTG_VAR] name = ""sprite_cache_size_px"" type = SLE_UINT diff --git a/src/tests/mock_fontcache.h b/src/tests/mock_fontcache.h index 5be357756d..7cd3a6cd04 100644 --- a/src/tests/mock_fontcache.h +++ b/src/tests/mock_fontcache.h @@ -25,15 +25,16 @@ public: const Sprite *GetGlyph(GlyphID) override { return nullptr; } uint GetGlyphWidth(GlyphID) override { return this->height / 2; } bool GetDrawGlyphShadow() override { return false; } - GlyphID MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback = true) override { return key; } + GlyphID MapCharToGlyph(char32_t key) override { return key; } std::string GetFontName() override { return "mock"; } bool IsBuiltInFont() override { return true; } static void InitializeFontCaches() { + FontCache::caches.clear(); for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { - if (FontCache::Get(fs) != nullptr) continue; - FontCache::Register(std::make_unique(fs)); + FontCache::Register(std::make_unique(fs), FontLoadReason::Default); + FontCache::UpdateCharacterHeight(fs); } } }; diff --git a/src/textfile_gui.cpp b/src/textfile_gui.cpp index 115467f397..91af774e39 100644 --- a/src/textfile_gui.cpp +++ b/src/textfile_gui.cpp @@ -83,7 +83,7 @@ static WindowDesc _textfile_desc( _nested_textfile_widgets ); -TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), 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. */ @@ -754,19 +754,6 @@ bool TextfileWindow::IsTextWrapped() const return this->lines[this->search_iterator++].text; } -/* virtual */ bool TextfileWindow::Monospace() -{ - return true; -} - -/* virtual */ void TextfileWindow::SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data) -{ -#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) - settings->mono.font = font_name; - settings->mono.os_handle = os_data; -#endif -} - #if defined(WITH_ZLIB) /** diff --git a/src/textfile_gui.h b/src/textfile_gui.h index 74034852bf..7a79bba557 100644 --- a/src/textfile_gui.h +++ b/src/textfile_gui.h @@ -19,7 +19,7 @@ std::optional 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. @@ -38,8 +38,6 @@ struct TextfileWindow : public Window, MissingGlyphSearcher { void Reset() override; FontSize DefaultSize() override; std::optional NextString() override; - bool Monospace() override; - void SetFontNames(FontCacheSettings *settings, std::string_view font_name, const void *os_data) override; void ScrollToLine(size_t line); bool IsTextWrapped() const; diff --git a/src/widgets/settings_widget.h b/src/widgets/settings_widget.h index d7a3c069d8..74a68d91a4 100644 --- a/src/widgets/settings_widget.h +++ b/src/widgets/settings_widget.h @@ -32,6 +32,8 @@ enum GameOptionsWidgets : WidgetID { WID_GO_GUI_SCALE_BEVEL_TEXT, ///< Text for chunky bevels. WID_GO_GUI_FONT_SPRITE, ///< Toggle whether to prefer the sprite font over TTF fonts. WID_GO_GUI_FONT_SPRITE_TEXT, ///< Text for sprite font toggle. + WID_GO_GUI_FONT_DEFAULT, ///< Toggle whether to prefer the default font over fallback fonts. + WID_GO_GUI_FONT_DEFAULT_TEXT, ///< Text for default font toggle. WID_GO_GUI_FONT_AA, ///< Toggle whether to anti-alias fonts. WID_GO_GUI_FONT_AA_TEXT, ///< Text for anti-alias toggle. WID_GO_BASE_GRF_DROPDOWN, ///< Use to select a base GRF. diff --git a/src/zoom_func.h b/src/zoom_func.h index 6dfe0f1c51..df12a2382d 100644 --- a/src/zoom_func.h +++ b/src/zoom_func.h @@ -119,4 +119,14 @@ inline int ScaleGUITrad(int value) return value * _gui_scale / 100; } +/** + * Scale traditional pixel dimensions to font zoom level, for drawing sprite fonts. + * @param value Pixel amount at #ZOOM_BASE (traditional "normal" interface size). + * @return Pixel amount at _font_zoom (current interface size). + */ +inline int ScaleFontTrad(int value) +{ + return UnScaleByZoom(value * ZOOM_BASE, _font_zoom); +} + #endif /* ZOOM_FUNC_H */