From b03347f00c741bbd407a762d5989f906254a3c06 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sat, 13 Dec 2025 00:29:06 +0000 Subject: [PATCH] Revert: "Change: Support side-by-side fallback FontCaches instead of hierarchical. (#13303)" This reverts commit 1829f7926d634b9062704bcd502b7ed1b5addee3. --- src/console_cmds.cpp | 35 ++--- src/fontcache.cpp | 183 +++++++-------------------- src/fontcache.h | 137 ++++---------------- 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, 480 insertions(+), 783 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index b9562cd591..230b386093 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2319,18 +2319,6 @@ 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()) { @@ -2379,18 +2367,17 @@ static bool ConFont(std::span argv) SetFont(argfs, font, 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; + 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); } return true; diff --git a/src/fontcache.cpp b/src/fontcache.cpp index ffda4466b4..33757ac3d8 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -8,8 +8,6 @@ /** @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" @@ -19,14 +17,13 @@ #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, 6, 15, 8}; +/* static */ const int FontCache::DEFAULT_FONT_ASCENDER[FS_END] = {8, 5, 15, 8}; FontCacheSettings _fcsettings; @@ -36,10 +33,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, bool search, const std::string &font_name, std::span os_handle) +/* static */ std::unique_ptr FontProviderManager::LoadFont(FontSize fs, FontType fonttype) { for (auto &provider : FontProviderManager::GetProviders()) { - auto fc = provider->LoadFont(fs, fonttype, search, font_name, os_handle); + auto fc = provider->LoadFont(fs, fonttype); if (fc != nullptr) return fc; } @@ -55,10 +52,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(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) +/* static */ bool FontProviderManager::FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) { return std::ranges::any_of(FontProviderManager::GetProviders(), - [&](auto *provider) { return provider->FindFallbackFont(language_isocode, fontsizes, callback); }); + [&](auto *provider) { return provider->FindFallbackFont(settings, language_isocode, callback); }); } int FontCache::GetDefaultFontHeight(FontSize fs) @@ -66,33 +63,21 @@ int FontCache::GetDefaultFontHeight(FontSize fs) return FontCache::DEFAULT_FONT_HEIGHT[fs]; } -/* static */ void FontCache::UpdateCharacterHeight(FontSize 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) { - 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); + FontCache *fc = FontCache::Get(fs); + if (fc != nullptr) { + return fc->GetFontName(); + } else { + return "[NULL]"; } - - 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. @@ -101,22 +86,20 @@ int FontCache::GetGlyphYOffset() */ int GetCharacterHeight(FontSize size) { - uint height = FontCache::GetCharacterHeight(size); - if (height == 0) height = ScaleGUITrad(FontCache::GetDefaultFontHeight(FS_MONO)); - return height; + return FontCache::Get(size)->GetHeight(); } -/* static */ FontCache::FontCaches FontCache::caches; -/* static */ std::array FontCache::metrics{}; -/* static */ std::array FontCache::default_font_index{}; + +/* static */ std::array, FS_END> FontCache::caches{}; /** * Initialise font caches with the base sprite font cache for all sizes. */ /* static */ void FontCache::InitializeFontCaches() { - for (FontSize fs : FONTSIZES_ALL) { - UpdateCharacterHeight(fs); + for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { + if (FontCache::Get(fs) != nullptr) continue; + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::Sprite)); } } @@ -147,13 +130,19 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) if (!changed) return; if (fontsize != FS_MONO) { - /* Check if fallback fonts are needed. */ + /* 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() : ""; + } CheckForMissingGlyphs(); + _fcsettings = std::move(backup); } else { FontCache::LoadFontCaches(fontsize); } - FontCache::UpdateCharacterHeight(fontsize); LoadStringWidthTable(fontsize); UpdateAllVirtCoords(); ReInitAllWindows(true); @@ -167,7 +156,7 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) */ static bool IsDefaultFont(const FontCacheSubSetting &setting) { - return setting.font.empty(); + return setting.font.empty() && setting.os_handle == nullptr; } /** @@ -204,7 +193,7 @@ static std::string GetDefaultTruetypeFont(FontSize fs) * @param fs Font size. * @return Full path of default font file. */ -std::string GetDefaultTruetypeFontFile([[maybe_unused]] FontSize fs) +static std::string GetDefaultTruetypeFontFile([[maybe_unused]] FontSize fs) { #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) /* Find font file. */ @@ -227,54 +216,18 @@ std::string GetFontCacheFontName(FontSize fs) return GetDefaultTruetypeFontFile(fs); } -/* static */ void FontCache::Register(std::unique_ptr &&fc, FontLoadReason load_reason) +/** + * Register a FontCache for its font size. + * @param fc FontCache to register. + */ +/* static */ void FontCache::Register(std::unique_ptr &&fc) { if (fc == nullptr) return; FontSize fs = fc->fs; - /* 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); - } + fc->parent = std::move(FontCache::caches[fs]); + FontCache::caches[fs] = std::move(fc); } /** @@ -283,53 +236,17 @@ std::string GetFontCacheFontName(FontSize fs) */ /* static */ void FontCache::LoadFontCaches(FontSizes fontsizes) { - 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}; + FontCache::InitializeFontCaches(); for (FontSize fs : fontsizes) { Layouter::ResetFontCache(fs); - FontCache::default_font_index[fs] = INVALID_FONT_INDEX; - } - /* 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); + /* Unload everything except the sprite font cache. */ + while (FontCache::Get(fs)->HasParent()) { + FontCache::caches[fs] = std::move(FontCache::caches[fs]->parent); } - /* 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); + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType)); } } @@ -339,14 +256,8 @@ 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::UpdateCharacterHeight(fs); + FontCache::Get(fs)->ClearFontCache(); } } @@ -355,5 +266,7 @@ std::string GetFontCacheFontName(FontSize fs) */ /* static */ void FontCache::UninitializeFontCaches() { - FontCache::caches.clear(); + for (auto &fc : FontCache::caches) { + fc.reset(); + } } diff --git a/src/fontcache.h b/src/fontcache.h index 1f258032e6..4691165662 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -16,42 +16,20 @@ /** Glyphs are characters from a font. */ typedef uint32_t GlyphID; -using FontIndex = uint8_t; - -static const FontIndex INVALID_FONT_INDEX = std::numeric_limits::max(); - -enum class FontLoadReason : uint8_t { - Default, - Configured, - LanguageFallback, - End, -}; +static const GlyphID SPRITE_GLYPH = 1U << 30; /** Font cache for basic fonts. */ class FontCache { protected: - 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; - + static std::array, FS_END> caches; ///< All the font caches. + std::unique_ptr parent; ///< The parent of this font cache. 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, FontLoadReason load_reason); - static void LoadDefaultFonts(FontSize fs); - static void LoadFallbackFonts(FontSize fs); + static void Register(std::unique_ptr &&fc); public: virtual ~FontCache() = default; @@ -68,36 +46,12 @@ 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. @@ -148,9 +102,10 @@ 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) = 0; + virtual GlyphID MapCharToGlyph(char32_t key, bool fallback = true) = 0; /** * Get the native OS font handle, if there is one. @@ -167,57 +122,25 @@ 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(FontIndex font_index) + static inline FontCache *Get(FontSize fs) { - assert(font_index < FontCache::caches.size()); - return FontCache::caches[font_index].get(); + assert(fs < FS_END); + return FontCache::caches[fs].get(); } - static inline int GetCharacterHeight(FontSize fs) - { - return FontCache::metrics[fs].height; - } + static std::string GetName(FontSize fs); - static void UpdateCharacterHeight(FontSize fs); - - static inline FontIndex GetDefaultFontIndex(FontSize fs) + /** + * Check whether the font cache has a parent. + */ + inline bool HasParent() { - 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; + return this->parent != nullptr; } /** @@ -229,33 +152,28 @@ public: /** Get the Sprite for a glyph */ inline const Sprite *GetGlyph(FontSize size, char32_t key) { - 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; + FontCache *fc = FontCache::Get(size); return fc->GetGlyph(fc->MapCharToGlyph(key)); } /** Get the width of a glyph */ inline uint GetGlyphWidth(FontSize size, char32_t key) { - 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; + FontCache *fc = FontCache::Get(size); 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. - struct FontCacheFallback { - FontLoadReason load_reason = FontLoadReason::LanguageFallback; - std::string name; - std::vector os_handle; - }; - - std::vector fallback_fonts; + const void *os_handle = nullptr; ///< Optional native OS font info. Only valid during font search. }; /** Settings for the four different fonts. */ @@ -266,7 +184,6 @@ 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; @@ -312,14 +229,14 @@ public: ProviderManager::Unregister(*this); } - 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; + 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; }; class FontProviderManager : ProviderManager { public: - 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); + static std::unique_ptr LoadFont(FontSize fs, FontType fonttype); + static bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); }; /* Implemented in spritefontcache.cpp */ diff --git a/src/fontcache/freetypefontcache.cpp b/src/fontcache/freetypefontcache.cpp index 33218c243e..e3f85d23da 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) override; + GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) 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,11 +194,17 @@ const Sprite *FreeTypeFontCache::InternalGetGlyph(GlyphID key, bool aa) } -GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key) +GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) { assert(IsPrintable(key)); - return FT_Get_Char_Index(this->face, 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; } FT_Library _ft_library = nullptr; @@ -220,10 +226,15 @@ 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, bool search, const std::string &font, std::span os_handle) const override + std::unique_ptr LoadFont(FontSize fs, FontType fonttype) 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"); @@ -237,9 +248,7 @@ public: /* If font is an absolute path to a ttf, try loading that first. */ int32_t index = 0; - if (os_handle.size() == sizeof(index)) { - index = *reinterpret_cast(os_handle.data()); - } + if (settings->os_handle != nullptr) index = *static_cast(settings->os_handle); FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face); if (error != FT_Err_Ok) { @@ -251,8 +260,8 @@ public: } #ifdef WITH_FONTCONFIG - /* If allowed to search, try loading based on font face name (OS-wide fonts). */ - if (error != FT_Err_Ok && search) error = GetFontByFaceName(font, &face); + /* Try loading based on font face name (OS-wide fonts). */ + if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face); #endif /* WITH_FONTCONFIG */ if (error != FT_Err_Ok) { @@ -263,10 +272,10 @@ public: return LoadFont(fs, face, font, GetFontCacheFontSize(fs)); } - bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override + bool FindFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) const override { #ifdef WITH_FONTCONFIG - if (FontConfigFindFallbackFont(language_isocode, fontsizes, callback)) return true; + if (FontConfigFindFallbackFont(settings, language_isocode, callback)) return true; #endif /* WITH_FONTCONFIG */ return false; @@ -300,7 +309,7 @@ private: if (error != FT_Err_Ok) { FT_Done_Face(face); - ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}", font_name, FontSizeToName(fs), error); + ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), error); return nullptr; } diff --git a/src/fontcache/spritefontcache.cpp b/src/fontcache/spritefontcache.cpp index 8a4e745156..7ac0a1ae59 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -23,6 +23,16 @@ 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. /** @@ -106,48 +116,37 @@ void InitializeUnicodeGlyphMap() */ SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs) { - this->UpdateMetrics(); + this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); + this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2; } void SpriteFontCache::ClearFontCache() { Layouter::ResetFontCache(this->fs); - 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; + this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); + this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2; } const Sprite *SpriteFontCache::GetGlyph(GlyphID key) { - SpriteID sprite = static_cast(key); + SpriteID sprite = static_cast(key & ~SPRITE_GLYPH); if (sprite == 0) sprite = GetUnicodeGlyph(this->fs, '?'); return GetSprite(sprite, SpriteType::Font); } uint SpriteFontCache::GetGlyphWidth(GlyphID key) { - SpriteID sprite = static_cast(key); + SpriteID sprite = static_cast(key & ~SPRITE_GLYPH); 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) +GlyphID SpriteFontCache::MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback) { assert(IsPrintable(key)); SpriteID sprite = GetUnicodeGlyph(this->fs, key); if (sprite == 0) return 0; - return static_cast(sprite); + return SPRITE_GLYPH | sprite; } bool SpriteFontCache::GetDrawGlyphShadow() @@ -159,14 +158,14 @@ class SpriteFontCacheFactory : public FontCacheFactory { public: SpriteFontCacheFactory() : FontCacheFactory("sprite", "Sprite font provider") {} - std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool, const std::string &, std::span) const override + std::unique_ptr LoadFont(FontSize fs, FontType fonttype) const override { if (fonttype != FontType::Sprite) return nullptr; return std::make_unique(fs); } - bool FindFallbackFont(const std::string &, FontSizes, class MissingGlyphSearcher *) const override + bool FindFallbackFont(struct FontCacheSettings *, const std::string &, class MissingGlyphSearcher *) const override { return false; } diff --git a/src/fontcache/spritefontcache.h b/src/fontcache/spritefontcache.h index d1b5ef3e77..f1e986491d 100644 --- a/src/fontcache/spritefontcache.h +++ b/src/fontcache/spritefontcache.h @@ -17,17 +17,12 @@ 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) override; + GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) 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 4a2659e76f..fc5a9bf9a1 100644 --- a/src/fontcache/truetypefontcache.cpp +++ b/src/fontcache/truetypefontcache.cpp @@ -64,6 +64,8 @@ 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); @@ -75,6 +77,8 @@ 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 6ba1fb7bf0..950ef25484 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -536,7 +536,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().GetFontCache().GetSize()); + truncation_layout.emplace(GetEllipsis(), INT32_MAX, line.GetVisualRun(0).GetFont()->fc->GetSize()); truncation_width = truncation_layout->GetBounds().width; /* Is there enough space even for an ellipsis? */ @@ -592,15 +592,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.GetFontCache(); - TextColour colour = f.colour; + FontCache *fc = f->fc; + 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++) { @@ -615,10 +615,13 @@ 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); + 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; + 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 2359b95e77..2c1b80bcc4 100644 --- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -37,6 +37,21 @@ /** 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. * @@ -56,7 +71,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{state.font_index, state.cur_colour}; + Font *f = Layouter::GetFont(state.fontsize, state.cur_colour); font_mapping.clear(); @@ -65,10 +80,7 @@ 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. */ - Utf8View view(str); - for (auto it = view.begin(); it != view.end(); /* nothing */) { - auto cur = it; - uint32_t c = *it++; + for (char32_t c : Utf8View(str)) { if (c == '\0' || c == '\n') { /* Caller should already have filtered out these characters. */ NOT_REACHED(); @@ -83,40 +95,19 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s } else { /* Filter out non printable characters */ if (!IsPrintable(c)) 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; + /* 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 (buff - buff_begin > 0 && (font_mapping.empty() || font_mapping.back().first != buff - buff_begin)) { + if (font_mapping.empty() || font_mapping.back().first != buff - buff_begin) { font_mapping.emplace_back(buff - buff_begin, f); } - f = {state.font_index, state.cur_colour}; + f = Layouter::GetFont(state.fontsize, state.cur_colour); } /* Better safe than sorry. */ @@ -125,14 +116,6 @@ 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; } @@ -145,7 +128,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, FontCache::GetDefaultFontIndex(fontsize)); + FontState state(TC_INVALID, fontsize); while (true) { auto line_length = str.find_first_of('\n'); @@ -355,6 +338,18 @@ 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. */ @@ -367,9 +362,12 @@ void Layouter::Initialize() /** * Reset cached font information. + * @param size Font size to reset. */ -void Layouter::ResetFontCache([[maybe_unused]] FontSize size) +void Layouter::ResetFontCache(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 bdc69707ae..3a63ca13bd 100644 --- a/src/gfx_layout.h +++ b/src/gfx_layout.h @@ -12,6 +12,8 @@ #include "misc/lrucache.hpp" #include "fontcache.h" +#include "gfx_func.h" +#include "core/math_func.hpp" #include @@ -20,13 +22,12 @@ * of the same text, e.g. on line breaks. */ struct FontState { - FontSize fontsize; ///< Current font size. - FontIndex font_index; ///< Current font index. - TextColour cur_colour; ///< Current text colour. + FontSize fontsize; ///< Current font size. + TextColour cur_colour; ///< Current text colour. std::vector colour_stack; ///< Stack of colours to assist with colour switching. - 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) {} + FontState() : fontsize(FS_END), cur_colour(TC_INVALID) {} + FontState(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour) {} auto operator<=>(const FontState &) const = default; @@ -66,7 +67,6 @@ struct FontState { inline void SetFontSize(FontSize f) { this->fontsize = f; - this->font_index = FontCache::GetDefaultFontIndex(this->fontsize); } }; @@ -85,10 +85,9 @@ 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.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); + size_t h2 = std::hash{}(state.cur_colour); + size_t h3 = std::hash>{}(state.colour_stack); + return h1 ^ (h2 << 1) ^ (h3 << 2); } }; @@ -97,14 +96,14 @@ template <> struct std::hash { */ class Font { public: - FontIndex font_index = INVALID_FONT_INDEX; ///< The font we are using. - TextColour colour = TC_INVALID; ///< The colour this font has to be. + FontCache *fc; ///< The font we are using. + TextColour colour; ///< The colour this font has to be. - inline FontCache &GetFontCache() const { return *FontCache::Get(this->font_index); } + Font(FontSize size, TextColour colour); }; /** 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. @@ -130,7 +129,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; @@ -206,14 +205,18 @@ 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 fs); + static void ResetFontCache(FontSize size); static void ResetLineCache(); }; diff --git a/src/gfx_layout_fallback.cpp b/src/gfx_layout_fallback.cpp index 3dc0d64724..15577f3ad6 100644 --- a/src/gfx_layout_fallback.cpp +++ b/src/gfx_layout_fallback.cpp @@ -10,7 +10,6 @@ #include "stdafx.h" #include "gfx_layout_fallback.h" -#include "gfx_func.h" #include "string_func.h" #include "zoom_func.h" @@ -44,15 +43,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(const Font &font, const char32_t *chars, int glyph_count, int char_offset, int x); - const Font &GetFont() const override { return this->font; } + FallbackVisualRun(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 GetCharacterHeight(this->GetFont().GetFontCache().GetSize()); } + int GetLeading() const override { return this->GetFont()->fc->GetHeight(); } std::span GetGlyphToCharMap() const override { return this->glyph_to_char; } }; @@ -110,20 +109,26 @@ 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(const Font &font, const char32_t *chars, int char_count, int char_offset, int x) : +FallbackParagraphLayout::FallbackVisualRun::FallbackVisualRun(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(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. + 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. + } advance += x_advance; this->glyph_to_char.push_back(char_offset + i); } @@ -228,8 +233,7 @@ std::unique_ptr FallbackParagraphLayout::NextLine assert(iter != this->runs.end()); } - const FontCache *fc = &iter->second.GetFontCache(); - assert(fc != nullptr); + const FontCache *fc = iter->second->fc; const char32_t *next_run = this->buffer_begin + iter->first; const char32_t *begin = this->buffer; @@ -247,7 +251,6 @@ 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 598a688aa1..49ec0f8726 100644 --- a/src/gfx_layout_fallback.h +++ b/src/gfx_layout_fallback.h @@ -26,24 +26,5 @@ 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 fa7ba43995..984990324c 100644 --- a/src/gfx_layout_icu.cpp +++ b/src/gfx_layout_icu.cpp @@ -11,9 +11,6 @@ #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" @@ -43,7 +40,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. @@ -51,10 +48,9 @@ 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, const Font &font) : start(start), length(length), level(level), script(script), font(font) {} + ICURun(int start, int length, UBiDiLevel level, UScriptCode script = USCRIPT_UNKNOWN, Font *font = nullptr) : start(start), length(length), level(level), script(script), font(font) {} void Shape(UChar *buff, size_t length); - void FallbackShape(UChar *buff); }; /** @@ -70,7 +66,7 @@ public: std::vector glyph_to_char; int total_advance; - Font font; + const Font *font; public: ICUVisualRun(const ICURun &run, int x); @@ -79,8 +75,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 GetCharacterHeight(this->font.GetFontCache().GetSize()); } + const Font *GetFont() const override { return this->font; } + int GetLeading() const override { return this->font->fc->GetHeight(); } int GetGlyphCount() const override { return this->glyphs.size(); } int GetAdvance() const { return this->total_advance; } }; @@ -145,46 +141,6 @@ 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. * @@ -193,20 +149,7 @@ void ICURun::FallbackShape(UChar *buff) */ void ICURun::Shape(UChar *buff, size_t buff_length) { - 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()))); + auto hbfont = hb_ft_font_create_referenced(*(static_cast(font->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); @@ -227,6 +170,12 @@ 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); @@ -234,12 +183,20 @@ 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 = 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); + 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); + } this->glyph_to_char.push_back(glyph_info[i].cluster); this->advance.push_back(x_advance); @@ -323,7 +280,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, USCRIPT_UNKNOWN, Font{}); + runs.emplace_back(start_pos, logical_pos - start_pos, level); } assert(static_cast(count) == runs.size()); @@ -354,7 +311,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(), Font{}); + runs.emplace_back(cur_pos, stop_pos - cur_pos, cur_run->level, script_itemizer.getScriptCode()); if (stop_pos == cur_run->start + cur_run->length) cur_run++; cur_pos = stop_pos; @@ -402,6 +359,11 @@ 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 7cb7e9f2a8..6e5d5ef499 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1090,8 +1090,6 @@ 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 c022479a83..ed3f35ca99 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) +GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) { assert(IsPrintable(key)); @@ -112,6 +112,10 @@ GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key) return glyph[0]; } + if (allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { + return this->parent->MapCharToGlyph(key); + } + return 0; } @@ -207,19 +211,29 @@ 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, bool search, const std::string &font, std::span) const override + std::unique_ptr LoadFont(FontSize fs, FontType fonttype) 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 (MacOSVersionIsAtLeast(10, 6, 0)) { + 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)) { /* 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 && search) { + if (!font_ref) { CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, font.c_str(), kCFStringEncodingUTF8)); /* Simply creating the font using CTFontCreateWithNameAndSize will *always* return @@ -245,7 +259,7 @@ public: return std::make_unique(fs, std::move(font_ref), GetFontCacheFontSize(fs)); } - bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override + bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, 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. */ @@ -291,7 +305,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) != fontsizes.Test(FS_MONO)) continue; + if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue; /* Get font name. */ char buffer[128]; @@ -309,9 +323,8 @@ public: if (name.starts_with(".") || name.starts_with("LastResort")) continue; /* Save result. */ - FontCache::AddFallback(fontsizes, callback->GetLoadReason(), name); - - if (callback->FindMissingGlyphs().None()) { + callback->SetFontNames(settings, name); + if (!callback->FindMissingGlyphs()) { Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name); result = true; break; @@ -322,8 +335,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. */ - FontCache::AddFallback(fontsizes, callback->GetLoadReason(), "Arial Unicode MS"); - result = callback->FindMissingGlyphs().None(); + callback->SetFontNames(settings, "Arial Unicode MS"); + result = !callback->FindMissingGlyphs(); } callback->FindMissingGlyphs(); diff --git a/src/os/macosx/font_osx.h b/src/os/macosx/font_osx.h index 9739613e16..41024a8525 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) override; + GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) 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 1e7ad14b21..8feab92a9d 100644 --- a/src/os/macosx/string_osx.cpp +++ b/src/os/macosx/string_osx.cpp @@ -9,7 +9,6 @@ #include "../../stdafx.h" #include "string_osx.h" -#include "../../gfx_func.h" #include "../../string_func.h" #include "../../strings_func.h" #include "../../core/utf8.hpp" @@ -53,7 +52,7 @@ extern "C" { /** Cached current locale. */ static CFAutoRelease _osx_locale; /** CoreText cache for font information, cleared when OTTD changes fonts. */ -static std::unordered_map> _font_cache; +static CFAutoRelease _font_cache[FS_END]; /** @@ -61,6 +60,7 @@ static std::unordered_map> _font_cache; */ 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, const Font &font); + CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff); 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 GetCharacterHeight(this->font.GetFontCache().GetSize()); } + const Font *GetFont() const override { return this->font; } + int GetLeading() const override { return this->font->fc->GetHeight(); } 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) + CoreTextLine(CFAutoRelease line, const FontMap &font_mapping, const CoreTextParagraphLayoutFactory::CharType *buff) { 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); - const auto &map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair::first); + auto map = std::ranges::upper_bound(font_mapping, chars.location, std::less{}, &std::pair::first); - this->emplace_back(run, map->second); + this->emplace_back(run, map->second, buff); } } @@ -122,7 +122,7 @@ public: } }; - CoreTextParagraphLayout(CFAutoRelease typesetter, ptrdiff_t len, const FontMap &font_mapping) : length(len), font_map(font_mapping), typesetter(std::move(typesetter)) + 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)) { this->Reflow(); } @@ -137,17 +137,17 @@ public: /** Get the width of an encoded sprite font character. */ -static CGFloat CustomFontGetWidth(void *ref_con) +static CGFloat SpriteFontGetWidth(void *ref_con) { - FontIndex fi = static_cast(reinterpret_cast(ref_con) >> 24); - char32_t c = static_cast(reinterpret_cast(ref_con) & 0xFFFFFF); + FontSize fs = (FontSize)((size_t)ref_con >> 24); + char32_t c = (char32_t)((size_t)ref_con & 0xFFFFFF); - return FontCache::Get(fi)->GetGlyphWidth(c); + return GetGlyphWidth(fs, c); } static const CTRunDelegateCallbacks _sprite_font_callback = { kCTRunDelegateCurrentVersion, nullptr, nullptr, nullptr, - &CustomFontGetWidth + &SpriteFontGetWidth }; /* static */ std::unique_ptr CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &font_mapping) @@ -158,6 +158,11 @@ 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()); @@ -174,26 +179,25 @@ static const CTRunDelegateCallbacks _sprite_font_callback = { for (const auto &[position, font] : font_mapping) { if (position - last == 0) continue; - FontCache &fc = font.GetFontCache(); - CTFontRef font_handle = static_cast(fc.GetOSHandle()); + CTFontRef font_handle = static_cast(font->fc->GetOSHandle()); if (font_handle == nullptr) { - if (!_font_cache[fc.GetIndex()]) { + if (!_font_cache[font->fc->GetSize()]) { /* Cache font information. */ - CFAutoRelease font_name(CFStringCreateWithCString(kCFAllocatorDefault, fc.GetFontName().c_str(), kCFStringEncodingUTF8)); - _font_cache[fc.GetIndex()].reset(CTFontCreateWithName(font_name.get(), fc.GetFontSize(), nullptr)); + 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)); } - font_handle = _font_cache[fc.GetIndex()].get(); + font_handle = _font_cache[font->fc->GetSize()].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 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))))); + /* 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)))); /* 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()); @@ -207,7 +211,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), length, font_mapping) : nullptr; + return typesetter ? std::make_unique(std::move(typesetter), buff, length, font_mapping) : nullptr; } /* virtual */ std::unique_ptr CoreTextParagraphLayout::NextLine(int max_width) @@ -223,10 +227,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); + return std::make_unique(std::move(line), this->font_map, this->text_buffer); } -CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, const Font &font) : font(font) +CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font *font, const CoreTextParagraphLayoutFactory::CharType *buff) : font(font) { this->glyphs.resize(CTRunGetGlyphCount(run)); @@ -243,15 +247,19 @@ CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, cons 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++) { - this->glyphs[i] = gl[i]; - this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y + y_offset); + 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->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 5db3313fe4..ff7f2f7028 100644 --- a/src/os/unix/font_unix.cpp +++ b/src/os/unix/font_unix.cpp @@ -120,10 +120,6 @@ 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; } @@ -143,7 +139,7 @@ static int GetPreferredWeightDistance(int weight) return 0; } -bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) +bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) { bool ret = false; @@ -152,25 +148,22 @@ bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes f 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 = language_isocode.empty() ? "" : fmt::format(":lang={}", language_isocode.substr(0, language_isocode.find('_'))); + std::string lang = 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, FC_CHARSET, nullptr)); + auto os = AutoRelease(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, 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; - std::string best_font; + const char *best_font = nullptr; int best_index = 0; for (FcPattern *font : std::span(fs->fonts, fs->nfont)) { @@ -181,7 +174,7 @@ bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes f /* Get a font with the right spacing .*/ int value = 0; FcPatternGetInteger(font, FC_SPACING, 0, &value); - if (fontsizes.Test(FS_MONO) != (value == FC_MONO) && value != FC_DUAL) continue; + if (callback->Monospace() != (value == FC_MONO) && value != FC_DUAL) continue; /* Do not use those that explicitly say they're slanted. */ FcPatternGetInteger(font, FC_SLANT, 0, &value); @@ -192,32 +185,26 @@ bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes f 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; - best_weight = weight; - best_font = FromFcString(file); - best_index = index; + 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; + } } - if (best_font.empty()) return false; - - FontCache::AddFallbackWithHandle(fontsizes, callback->GetLoadReason(), best_font, best_index); - FontCache::LoadFontCaches(fontsizes); + if (best_font == nullptr) return false; + 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 b2044fe6e9..d47f28150e 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(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback); +bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); #endif /* WITH_FONTCONFIG */ diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index a18be2da38..9f252eb266 100644 --- a/src/os/windows/font_win32.cpp +++ b/src/os/windows/font_win32.cpp @@ -31,8 +31,8 @@ #include "../../safeguards.h" struct EFCParam { - LOCALESIGNATURE locale; - FontSizes fontsizes; + FontCacheSettings *settings; + LOCALESIGNATURE locale; 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->fontsizes.Test(FS_MONO) && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1; + if (info->callback->Monospace() && (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); - FontCache::AddFallbackWithHandle(info->fontsizes, info->callback->GetLoadReason(), font_name, logfont->elfLogFont); - if (info->callback->FindMissingGlyphs().None()) return 1; + info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont); + if (info->callback->FindMissingGlyphs()) 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, "Win32FontCache: Loaded font '{}' with size {}", this->fontname, pixels); + Debug(fontcache, 2, "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) +/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(char32_t key, bool allow_fallback) { 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 0; + return allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END ? this->parent->MapCharToGlyph(key) : 0; } class Win32FontCacheFactory : FontCacheFactory { @@ -270,28 +270,32 @@ 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, bool search, const std::string &font, std::span os_handle) 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) 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 (!os_handle.empty()) { - logfont = *reinterpret_cast(os_handle.data()); + if (settings->os_handle != nullptr) { + logfont = *(const LOGFONT *)settings->os_handle; } 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; } } @@ -303,7 +307,7 @@ public: return LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); } - bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) const override + bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) const override { Debug(fontcache, 1, "Trying fallback fonts"); EFCParam langInfo; @@ -313,7 +317,7 @@ public: Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode); return false; } - langInfo.fontsizes = fontsizes; + langInfo.settings = settings; langInfo.callback = callback; LOGFONT font; diff --git a/src/os/windows/font_win32.h b/src/os/windows/font_win32.h index 46cf022e37..264efe00e5 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) override; + GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override; std::string GetFontName() override { return this->fontname; } const void *GetOSHandle() override { return &this->logfont; } }; -void LoadWin32Font(FontSize fs, bool search, const std::string &font_name, std::span os_handle); +void LoadWin32Font(FontSize fs); #endif /* FONT_WIN32_H */ diff --git a/src/os/windows/string_uniscribe.cpp b/src/os/windows/string_uniscribe.cpp index 89e9045e22..5c45e881f6 100644 --- a/src/os/windows/string_uniscribe.cpp +++ b/src/os/windows/string_uniscribe.cpp @@ -10,8 +10,6 @@ #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" @@ -31,7 +29,7 @@ /** Uniscribe cache for internal font information, cleared when OTTD changes fonts. */ -static std::map _script_cache; +static SCRIPT_CACHE _script_cache[FS_END]; /** * Contains all information about a run of characters. A run are consecutive @@ -40,7 +38,7 @@ static std::map _script_cache; struct UniscribeRun { int pos; int len; - Font font; + Font *font; std::vector ft_glyphs; @@ -53,9 +51,7 @@ struct UniscribeRun { std::vector offsets; int total_advance; - 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); + UniscribeRun(int pos, int len, Font *font, SCRIPT_ANALYSIS &sa) : pos(pos), len(len), font(font), sa(sa) {} }; /** Break a string into language formatting ranges. */ @@ -85,7 +81,7 @@ public: int start_pos; int total_advance; int num_glyphs; - Font font; + Font *font; mutable std::vector glyph_to_char; @@ -97,8 +93,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 GetCharacterHeight(this->font.GetFontCache().GetSize()); } + const Font *GetFont() const override { return this->font; } + int GetLeading() const override { return this->font->fc->GetHeight(); } int GetGlyphCount() const override { return this->num_glyphs; } int GetAdvance() const { return this->total_advance; } }; @@ -134,71 +130,31 @@ public: std::unique_ptr NextLine(int max_width) override; }; -void UniscribeResetScriptCache(FontSize) +void UniscribeResetScriptCache(FontSize size) { - for (auto &sc : _script_cache) { - ScriptFreeCache(&sc.second); + if (_script_cache[size] != nullptr) { + ScriptFreeCache(&_script_cache[size]); + _script_cache[size] = nullptr; } - _script_cache.clear(); } /** Load the matching native Windows font. */ -static HFONT HFontFromFont(const Font &font) +static HFONT HFontFromFont(Font *font) { - FontCache &fc = font.GetFontCache(); - - if (fc.GetOSHandle() != nullptr) return CreateFontIndirect(reinterpret_cast(const_cast(fc.GetOSHandle()))); + if (font->fc->GetOSHandle() != nullptr) return CreateFontIndirect(reinterpret_cast(const_cast(font->fc->GetOSHandle()))); LOGFONT logfont{}; - logfont.lfHeight = fc.GetHeight(); + logfont.lfHeight = font->fc->GetHeight(); logfont.lfWeight = FW_NORMAL; logfont.lfCharSet = DEFAULT_CHARSET; - convert_to_fs(fc.GetFontName(), logfont.lfFaceName); + convert_to_fs(font->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()); @@ -210,15 +166,10 @@ 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[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); + 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); if (SUCCEEDED(hr)) { range.glyphs.resize(glyphs_used); @@ -228,7 +179,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[fc.GetIndex()], &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[range.font->fc->GetSize()], &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. */ @@ -236,12 +187,22 @@ 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.glyphs[i] != 0xFFFF) range.advances[i] = fc.GetGlyphWidth(range.glyphs[i]); + if (range.advances[i] > 0 && range.ft_glyphs[i] != 0xFFFF) range.advances[i] = range.font->fc->GetGlyphWidth(range.ft_glyphs[i]); #endif range.total_advance += range.advances[i]; } @@ -319,6 +280,11 @@ 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 64997fd80b..70e54fc340 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -658,9 +658,6 @@ 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); @@ -931,18 +928,6 @@ 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) { @@ -1053,15 +1038,13 @@ 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; - 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(); + FontCache::LoadFontCaches(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); + CheckForMissingGlyphs(); + SetupWidgetDimensions(); + UpdateAllVirtCoords(); + ReInitAllWindows(true); break; case WID_GO_GUI_FONT_AA: @@ -1559,7 +1542,6 @@ 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 */ @@ -1690,10 +1672,6 @@ 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 0ab1354b30..47c8cc3203 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2274,56 +2274,32 @@ std::string_view GetCurrentLanguageIsoCode() /** * Check whether there are glyphs missing in the current language. - * @return Bit mask of font sizes have any missing glyphs. + * @return If glyphs are missing, return \c true, else return \c false. */ -FontSizes MissingGlyphSearcher::FindMissingGlyphs() +bool MissingGlyphSearcher::FindMissingGlyphs() { - 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{}; + FontCache::LoadFontCaches(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); 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); - continue; + 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; } - - 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 glyphs; + return false; } /** Helper for searching through the language pack. */ -class LanguagePackGlyphSearcher : public BaseStringMissingGlyphSearcher { -public: - LanguagePackGlyphSearcher() : BaseStringMissingGlyphSearcher({FS_NORMAL, FS_SMALL, FS_LARGE}) {} - -private: +class LanguagePackGlyphSearcher : public MissingGlyphSearcher { uint i; ///< Iterator for the primary language tables. uint j; ///< Iterator for the secondary language tables. @@ -2352,8 +2328,25 @@ private: 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 @@ -2371,21 +2364,20 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher) { static LanguagePackGlyphSearcher pack_searcher; if (searcher == nullptr) searcher = &pack_searcher; - - for (FontSize size : searcher->fontsizes) { - GetFontCacheSubSetting(size)->fallback_fonts.clear(); - } - - FontSizes fontsizes = searcher->FindMissingGlyphs(); - bool bad_font = fontsizes.Any(); - + bool bad_font = searcher->FindMissingGlyphs(); #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; - bad_font = !FontProviderManager::FindFallbackFont(_langpack.langpack->isocode, fontsizes, searcher); + _fcsettings.mono.os_handle = nullptr; + _fcsettings.medium.os_handle = nullptr; + + bad_font = !FontProviderManager::FindFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher); + + _fcsettings = std::move(backup); if (!bad_font && any_font_configured) { /* If the user configured a bad font, and we found a better one, @@ -2402,7 +2394,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(fontsizes); + FontCache::LoadFontCaches(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); } } #endif @@ -2419,12 +2411,12 @@ void CheckForMissingGlyphs(MissingGlyphSearcher *searcher) ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING); /* Reset the font width */ - LoadStringWidthTable(fontsizes); + LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); return; } /* Update the font with cache */ - LoadStringWidthTable(searcher->fontsizes); + LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); #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 1436e2eb26..5b8fcaafd1 100644 --- a/src/strings_func.h +++ b/src/strings_func.h @@ -10,7 +10,6 @@ #ifndef STRINGS_FUNC_H #define STRINGS_FUNC_H -#include "fontcache.h" #include "strings_type.h" #include "gfx_type.h" #include "vehicle_type.h" @@ -156,33 +155,9 @@ 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. @@ -200,11 +175,23 @@ public: */ virtual void Reset() = 0; - FontLoadReason GetLoadReason() override { return FontLoadReason::LanguageFallback; } + /** + * Whether to search for a monospace font or not. + * @return True if searching for monospace. + */ + virtual bool Monospace() = 0; - std::set GetRequiredGlyphs(FontSizes fontsizes) override; + /** + * 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(); }; -void CheckForMissingGlyphs(MissingGlyphSearcher *searcher = nullptr); +void CheckForMissingGlyphs(MissingGlyphSearcher *search = nullptr); #endif /* STRINGS_FUNC_H */ diff --git a/src/survey.cpp b/src/survey.cpp index 7f84ff556d..ccc30c1ea3 100644 --- a/src/survey.cpp +++ b/src/survey.cpp @@ -298,16 +298,10 @@ void SurveyConfiguration(nlohmann::json &survey) */ void SurveyFont(nlohmann::json &survey) { - 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()); - } + 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(); } /** diff --git a/src/table/settings/misc_settings.ini b/src/table/settings/misc_settings.ini index 48f42704ea..86b15c378a 100644 --- a/src/table/settings/misc_settings.ini +++ b/src/table/settings/misc_settings.ini @@ -259,12 +259,6 @@ 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 d08456bd1d..03482cee05 100644 --- a/src/tests/mock_fontcache.h +++ b/src/tests/mock_fontcache.h @@ -25,16 +25,15 @@ 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) override { return key; } + GlyphID MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback = true) 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++) { - FontCache::Register(std::make_unique(fs), FontLoadReason::Default); - FontCache::UpdateCharacterHeight(fs); + if (FontCache::Get(fs) != nullptr) continue; + FontCache::Register(std::make_unique(fs)); } } }; diff --git a/src/textfile_gui.cpp b/src/textfile_gui.cpp index 61c41e58f6..9a00bf1e6a 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), BaseStringMissingGlyphSearcher(FS_MONO), file_type(file_type) +TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), file_type(file_type) { /* Init of nested tree is deferred. * TextfileWindow::ConstructWindow must be called by the inheriting window. */ @@ -754,6 +754,19 @@ 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 49b640641d..a8211f0988 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, BaseStringMissingGlyphSearcher { +struct TextfileWindow : public Window, MissingGlyphSearcher { TextfileType file_type{}; ///< Type of textfile to view. Scrollbar *vscroll = nullptr; ///< Vertical scrollbar. Scrollbar *hscroll = nullptr; ///< Horizontal scrollbar. @@ -38,6 +38,8 @@ struct TextfileWindow : public Window, BaseStringMissingGlyphSearcher { 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 c5692fd721..5ea226a00a 100644 --- a/src/widgets/settings_widget.h +++ b/src/widgets/settings_widget.h @@ -32,8 +32,6 @@ 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 1ce7f36e50..e04273cfbd 100644 --- a/src/zoom_func.h +++ b/src/zoom_func.h @@ -119,14 +119,4 @@ 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 */