From 97073e359178992aef166f23b0c73f2c7cbce0a1 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 15 Jul 2025 19:46:07 +0100 Subject: [PATCH 1/4] Codechange: Initialise/reset font cache with FontSizes bitset. Instead of choosing either "Normal/Small/Large" or "Monospace", use an EnumBitSet to allow any combination. --- src/console_cmds.cpp | 2 +- src/fontcache.cpp | 12 +++++------- src/fontcache.h | 6 +++--- src/gfx.cpp | 13 +++++++------ src/gfx_func.h | 2 +- src/gfx_type.h | 7 +++++++ src/gfxinit.cpp | 6 +++--- src/openttd.cpp | 2 +- src/os/unix/font_unix.cpp | 2 +- src/settings_gui.cpp | 7 +++---- src/strings.cpp | 8 ++++---- 11 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 3293c4704f..52dfc9cffa 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2369,7 +2369,7 @@ static bool ConFont(std::span argv) FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); /* Make sure all non sprite fonts are loaded. */ if (!setting->font.empty() && !fc->HasParent()) { - InitFontCache(fs == FS_MONO); + InitFontCache(fs); fc = FontCache::Get(fs); } IConsolePrint(CC_DEFAULT, "{} font:", FontSizeToName(fs)); diff --git a/src/fontcache.cpp b/src/fontcache.cpp index dcdc1804c9..d1be5e6ac2 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -126,10 +126,10 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) CheckForMissingGlyphs(); _fcsettings = std::move(backup); } else { - InitFontCache(true); + InitFontCache(fontsize); } - LoadStringWidthTable(fontsize == FS_MONO); + LoadStringWidthTable(fontsize); UpdateAllVirtCoords(); ReInitAllWindows(true); @@ -213,15 +213,13 @@ std::string GetFontCacheFontName(FontSize fs) /** * (Re)initialize the font cache related things, i.e. load the non-sprite fonts. - * @param monospace Whether to initialise the monospace or regular fonts. + * @param fontsizes Font sizes to be initialised. */ -void InitFontCache(bool monospace) +void InitFontCache(FontSizes fontsizes) { FontCache::InitializeFontCaches(); - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { - if (monospace != (fs == FS_MONO)) continue; - + for (FontSize fs : fontsizes) { FontCache *fc = FontCache::Get(fs); if (fc->HasParent()) delete fc; diff --git a/src/fontcache.h b/src/fontcache.h index 5026694f58..20dbfff074 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -167,9 +167,9 @@ inline void InitializeUnicodeGlyphMap() } } -inline void ClearFontCache() +inline void ClearFontCache(FontSizes fontsizes) { - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { + for (FontSize fs : fontsizes) { FontCache::Get(fs)->ClearFontCache(); } } @@ -231,7 +231,7 @@ inline FontCacheSubSetting *GetFontCacheSubSetting(FontSize fs) uint GetFontCacheFontSize(FontSize fs); std::string GetFontCacheFontName(FontSize fs); -void InitFontCache(bool monospace); +void InitFontCache(FontSizes fontsizes); void UninitFontCache(); bool GetFontAAState(); diff --git a/src/gfx.cpp b/src/gfx.cpp index 1add357877..9ba985e971 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -8,6 +8,7 @@ /** @file gfx.cpp Handling of drawing text and other gfx related stuff. */ #include "stdafx.h" +#include "gfx_func.h" #include "gfx_layout.h" #include "progress.h" #include "zoom_func.h" @@ -1240,14 +1241,14 @@ static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, } /** - * Initialize _stringwidth_table cache - * @param monospace Whether to load the monospace cache or the normal fonts. + * Initialize _stringwidth_table cache for the specified font sizes. + * @param fontsizes Font sizes to initialise. */ -void LoadStringWidthTable(bool monospace) +void LoadStringWidthTable(FontSizes fontsizes) { - ClearFontCache(); + ClearFontCache(fontsizes); - for (FontSize fs = monospace ? FS_MONO : FS_BEGIN; fs < (monospace ? FS_END : FS_MONO); fs++) { + for (FontSize fs : fontsizes) { for (uint i = 0; i != 224; i++) { _stringwidth_table[fs][i] = GetGlyphWidth(fs, i + 32); } @@ -1812,7 +1813,7 @@ bool AdjustGUIZoom(bool automatic) if (old_font_zoom != _font_zoom) { GfxClearFontSpriteCache(); } - ClearFontCache(); + ClearFontCache(FONTSIZES_ALL); LoadStringWidthTable(); SetupWidgetDimensions(); diff --git a/src/gfx_func.h b/src/gfx_func.h index 655b167c37..edd5b03d82 100644 --- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -149,7 +149,7 @@ int GetStringHeight(StringID str, int maxw); int GetStringLineCount(std::string_view str, int maxw); Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestion); Dimension GetStringMultiLineBoundingBox(std::string_view str, const Dimension &suggestion, FontSize fontsize = FS_NORMAL); -void LoadStringWidthTable(bool monospace = false); +void LoadStringWidthTable(FontSizes fontsizes = FONTSIZES_REQUIRED); void DrawDirtyBlocks(); void AddDirtyBlock(int left, int top, int right, int bottom); diff --git a/src/gfx_type.h b/src/gfx_type.h index 664aecaed9..718f7a455d 100644 --- a/src/gfx_type.h +++ b/src/gfx_type.h @@ -258,6 +258,13 @@ enum FontSize : uint8_t { }; DECLARE_INCREMENT_DECREMENT_OPERATORS(FontSize) +using FontSizes = EnumBitSet; + +/** Mask of all possible font sizes. */ +constexpr FontSizes FONTSIZES_ALL{FS_NORMAL, FS_SMALL, FS_LARGE, FS_MONO}; +/** Mask of font sizes required to be present. */ +constexpr FontSizes FONTSIZES_REQUIRED{FS_NORMAL, FS_SMALL, FS_LARGE}; + inline std::string_view FontSizeToName(FontSize fs) { static const std::string_view SIZE_TO_NAME[] = { "medium", "small", "large", "mono" }; diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp index deaaf405b2..20c5e69f11 100644 --- a/src/gfxinit.cpp +++ b/src/gfxinit.cpp @@ -245,7 +245,7 @@ static void RealChangeBlitter(std::string_view repl_blitter) /* Clear caches that might have sprites for another blitter. */ VideoDriver::GetInstance()->ClearSystemSprites(); - ClearFontCache(); + ClearFontCache(FONTSIZES_ALL); GfxClearSpriteCache(); ReInitAllWindows(false); } @@ -326,7 +326,7 @@ void CheckBlitter() { if (!SwitchNewGRFBlitter()) return; - ClearFontCache(); + ClearFontCache(FONTSIZES_ALL); GfxClearSpriteCache(); ReInitAllWindows(false); } @@ -338,7 +338,7 @@ void GfxLoadSprites() SwitchNewGRFBlitter(); VideoDriver::GetInstance()->ClearSystemSprites(); - ClearFontCache(); + ClearFontCache(FONTSIZES_ALL); GfxInitSpriteMem(); LoadSpriteTables(); GfxInitPalettes(); diff --git a/src/openttd.cpp b/src/openttd.cpp index 10ea0951e0..533ee0446a 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -700,7 +700,7 @@ int openttd_main(std::span arguments) InitializeLanguagePacks(); /* Initialize the font cache */ - InitFontCache(false); + InitFontCache(FONTSIZES_REQUIRED); /* This must be done early, since functions use the SetWindowDirty* calls */ InitWindowSystem(); diff --git a/src/os/unix/font_unix.cpp b/src/os/unix/font_unix.cpp index 37567537e1..feb96713e5 100644 --- a/src/os/unix/font_unix.cpp +++ b/src/os/unix/font_unix.cpp @@ -182,6 +182,6 @@ bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_is if (best_font == nullptr) return false; callback->SetFontNames(settings, best_font, &best_index); - InitFontCache(callback->Monospace()); + InitFontCache(callback->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); return true; } diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index d5f8d50e19..3e9353d488 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1036,9 +1036,8 @@ struct GameOptionsWindow : Window { this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA, _fcsettings.prefer_sprite); this->SetDirty(); - InitFontCache(false); - InitFontCache(true); - ClearFontCache(); + InitFontCache(FONTSIZES_ALL); + ClearFontCache(FONTSIZES_ALL); CheckForMissingGlyphs(); SetupWidgetDimensions(); UpdateAllVirtCoords(); @@ -1051,7 +1050,7 @@ struct GameOptionsWindow : Window { this->SetWidgetLoweredState(WID_GO_GUI_FONT_AA, _fcsettings.global_aa); MarkWholeScreenDirty(); - ClearFontCache(); + ClearFontCache(FONTSIZES_ALL); break; #endif /* HAS_TRUETYPE_FONT */ diff --git a/src/strings.cpp b/src/strings.cpp index d33535ba92..69d605e86d 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2278,7 +2278,7 @@ std::string_view GetCurrentLanguageIsoCode() */ bool MissingGlyphSearcher::FindMissingGlyphs() { - InitFontCache(this->Monospace()); + InitFontCache(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); this->Reset(); for (auto text = this->NextString(); text.has_value(); text = this->NextString()) { @@ -2395,7 +2395,7 @@ void CheckForMissingGlyphs(bool base_font, 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 */ - InitFontCache(searcher->Monospace()); + InitFontCache(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); } } #endif @@ -2412,12 +2412,12 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher) ShowErrorMessage(GetEncodedString(STR_JUST_RAW_STRING, std::move(err_str)), {}, WL_WARNING); /* Reset the font width */ - LoadStringWidthTable(searcher->Monospace()); + LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); return; } /* Update the font with cache */ - LoadStringWidthTable(searcher->Monospace()); + LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA) /* From bb6ef04d7a9c95400304904b68f201ab70ed8b02 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 15 Jul 2025 19:47:07 +0100 Subject: [PATCH 2/4] Codechange: Decouple glyph map from SpriteFontCache instances. This makes the map independent from the SpriteFontCache instances. --- src/fontcache.h | 28 ++---------- src/fontcache/spritefontcache.cpp | 76 ++++++++++++++++++++----------- src/fontcache/spritefontcache.h | 7 --- src/fontcache/truetypefontcache.h | 2 - src/tests/mock_fontcache.h | 2 - 5 files changed, 54 insertions(+), 61 deletions(-) diff --git a/src/fontcache.h b/src/fontcache.h index 20dbfff074..2ee0d240af 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -70,16 +70,6 @@ public: */ virtual int GetFontSize() const { return this->height; } - /** - * Map a SpriteID to the key - * @param key The key to map to. - * @param sprite The sprite that is being mapped. - */ - virtual void SetUnicodeGlyph(char32_t key, SpriteID sprite) = 0; - - /** Initialize the glyph map */ - virtual void InitializeUnicodeGlyphMap() = 0; - /** Clear the font cache. */ virtual void ClearFontCache() = 0; @@ -153,20 +143,6 @@ public: virtual bool IsBuiltInFont() = 0; }; -/** Map a SpriteID to the font size and key */ -inline void SetUnicodeGlyph(FontSize size, char32_t key, SpriteID sprite) -{ - FontCache::Get(size)->SetUnicodeGlyph(key, sprite); -} - -/** Initialize the glyph map */ -inline void InitializeUnicodeGlyphMap() -{ - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { - FontCache::Get(fs)->InitializeUnicodeGlyphMap(); - } -} - inline void ClearFontCache(FontSizes fontsizes) { for (FontSize fs : fontsizes) { @@ -237,4 +213,8 @@ void UninitFontCache(); bool GetFontAAState(); void SetFont(FontSize fontsize, const std::string &font, uint size); +/* Implemented in spritefontcache.cpp */ +void InitializeUnicodeGlyphMap(); +void SetUnicodeGlyph(FontSize size, char32_t key, SpriteID sprite); + #endif /* FONTCACHE_H */ diff --git a/src/fontcache/spritefontcache.cpp b/src/fontcache/spritefontcache.cpp index cb49ad2817..e2d93189c2 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -10,6 +10,7 @@ #include "../stdafx.h" #include "../fontcache.h" #include "../gfx_layout.h" +#include "../string_func.h" #include "../zoom_func.h" #include "spritefontcache.h" @@ -31,41 +32,43 @@ static int ScaleFontTrad(int value) return UnScaleByZoom(value * ZOOM_BASE, _font_zoom); } -/** - * Create a new sprite font cache. - * @param fs The font size to create the cache for. - */ -SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs) -{ - this->InitializeUnicodeGlyphMap(); - this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); - this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2; -} +static std::array, FS_END> _char_maps{}; ///< Glyph map for each font size. /** * Get SpriteID associated with a character. * @param key Character to find. * @return SpriteID for character, or 0 if not present. */ -SpriteID SpriteFontCache::GetUnicodeGlyph(char32_t key) +static SpriteID GetUnicodeGlyph(FontSize fs, char32_t key) { - const auto found = this->char_map.find(key); - if (found == std::end(this->char_map)) return 0; - return found->second; + auto found = _char_maps[fs].find(key); + if (found != std::end(_char_maps[fs])) return found->second; + return 0; } -void SpriteFontCache::SetUnicodeGlyph(char32_t key, SpriteID sprite) +/** + * Set the SpriteID for a unicode character. + * @param fs Font size to set. + * @param key Unicode character to set. + * @param sprite SpriteID of character. + */ +void SetUnicodeGlyph(FontSize fs, char32_t key, SpriteID sprite) { - this->char_map[key] = sprite; + _char_maps[fs][key] = sprite; } -void SpriteFontCache::InitializeUnicodeGlyphMap() +/** + * Initialize the glyph map for a font size. + * This populates the glyph map with the baseset font sprites. + * @param fs Font size to initialize. + */ +void InitializeUnicodeGlyphMap(FontSize fs) { /* Clear out existing glyph map if it exists */ - this->char_map.clear(); + _char_maps[fs].clear(); SpriteID base; - switch (this->fs) { + switch (fs) { default: NOT_REACHED(); case FS_MONO: // Use normal as default for mono spaced font case FS_NORMAL: base = SPR_ASCII_SPACE; break; @@ -76,24 +79,45 @@ void SpriteFontCache::InitializeUnicodeGlyphMap() for (uint i = ASCII_LETTERSTART; i < 256; i++) { SpriteID sprite = base + i - ASCII_LETTERSTART; if (!SpriteExists(sprite)) continue; - this->SetUnicodeGlyph(i, sprite); - this->SetUnicodeGlyph(i + SCC_SPRITE_START, sprite); + SetUnicodeGlyph(fs, i, sprite); + SetUnicodeGlyph(fs, i + SCC_SPRITE_START, sprite); } + /* Modify map to move non-standard glyphs to a better unicode codepoint. */ for (const auto &unicode_map : _default_unicode_map) { uint8_t key = unicode_map.key; if (key == CLRA) { /* Clear the glyph. This happens if the glyph at this code point * is non-standard and should be accessed by an SCC_xxx enum * entry only. */ - this->SetUnicodeGlyph(unicode_map.code, 0); + SetUnicodeGlyph(fs, unicode_map.code, 0); } else { SpriteID sprite = base + key - ASCII_LETTERSTART; - this->SetUnicodeGlyph(unicode_map.code, sprite); + SetUnicodeGlyph(fs, unicode_map.code, sprite); } } } +/** + * Initialize the glyph map. + */ +void InitializeUnicodeGlyphMap() +{ + for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { + InitializeUnicodeGlyphMap(fs); + } +} + +/** + * Create a new sprite font cache. + * @param fs The font size to create the cache for. + */ +SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs) +{ + this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); + this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2; +} + void SpriteFontCache::ClearFontCache() { Layouter::ResetFontCache(this->fs); @@ -104,21 +128,21 @@ void SpriteFontCache::ClearFontCache() const Sprite *SpriteFontCache::GetGlyph(GlyphID key) { SpriteID sprite = static_cast(key & ~SPRITE_GLYPH); - if (sprite == 0) sprite = this->GetUnicodeGlyph('?'); + if (sprite == 0) sprite = GetUnicodeGlyph(this->fs, '?'); return GetSprite(sprite, SpriteType::Font); } uint SpriteFontCache::GetGlyphWidth(GlyphID key) { SpriteID sprite = static_cast(key & ~SPRITE_GLYPH); - if (sprite == 0) sprite = this->GetUnicodeGlyph('?'); + if (sprite == 0) sprite = GetUnicodeGlyph(this->fs, '?'); return SpriteExists(sprite) ? GetSprite(sprite, SpriteType::Font)->width + ScaleFontTrad(this->fs != FS_NORMAL ? 1 : 0) : 0; } GlyphID SpriteFontCache::MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback) { assert(IsPrintable(key)); - SpriteID sprite = this->GetUnicodeGlyph(key); + SpriteID sprite = GetUnicodeGlyph(this->fs, key); if (sprite == 0) return 0; return SPRITE_GLYPH | sprite; } diff --git a/src/fontcache/spritefontcache.h b/src/fontcache/spritefontcache.h index 1f2a09a9c6..5403348cbe 100644 --- a/src/fontcache/spritefontcache.h +++ b/src/fontcache/spritefontcache.h @@ -10,15 +10,12 @@ #ifndef SPRITEFONTCACHE_H #define SPRITEFONTCACHE_H -#include "../string_func.h" #include "../fontcache.h" /** Font cache for fonts that are based on a freetype font. */ class SpriteFontCache : public FontCache { public: SpriteFontCache(FontSize fs); - void SetUnicodeGlyph(char32_t key, SpriteID sprite) override; - void InitializeUnicodeGlyphMap() override; void ClearFontCache() override; const Sprite *GetGlyph(GlyphID key) override; uint GetGlyphWidth(GlyphID key) override; @@ -26,10 +23,6 @@ public: GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override; std::string GetFontName() override { return "sprite"; } bool IsBuiltInFont() override { return true; } - -private: - std::unordered_map char_map{}; ///< Mapping of characters to sprite IDs. - SpriteID GetUnicodeGlyph(char32_t key); }; #endif /* SPRITEFONTCACHE_H */ diff --git a/src/fontcache/truetypefontcache.h b/src/fontcache/truetypefontcache.h index 2ce94c5d35..7714823305 100644 --- a/src/fontcache/truetypefontcache.h +++ b/src/fontcache/truetypefontcache.h @@ -46,8 +46,6 @@ public: TrueTypeFontCache(FontSize fs, int pixels); virtual ~TrueTypeFontCache(); int GetFontSize() const override { return this->used_size; } - void SetUnicodeGlyph(char32_t key, SpriteID sprite) override { this->parent->SetUnicodeGlyph(key, sprite); } - void InitializeUnicodeGlyphMap() override { this->parent->InitializeUnicodeGlyphMap(); } const Sprite *GetGlyph(GlyphID key) override; void ClearFontCache() override; uint GetGlyphWidth(GlyphID key) override; diff --git a/src/tests/mock_fontcache.h b/src/tests/mock_fontcache.h index 5ae4103f65..c159742a31 100644 --- a/src/tests/mock_fontcache.h +++ b/src/tests/mock_fontcache.h @@ -21,8 +21,6 @@ public: this->height = FontCache::GetDefaultFontHeight(this->fs); } - void SetUnicodeGlyph(char32_t, SpriteID) override {} - void InitializeUnicodeGlyphMap() override {} void ClearFontCache() override {} const Sprite *GetGlyph(GlyphID) override { return nullptr; } uint GetGlyphWidth(GlyphID) override { return this->height / 2; } From 89f4e5da4e9e1dcf61f75bd4949ec3909253a4a5 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 15 Jul 2025 19:47:08 +0100 Subject: [PATCH 3/4] Codechange: Use ProviderManager interface to register FontCache factories. This removes use of #ifdefs to select the appropriate loader, and also replaces FontCache self-registration. --- src/console_cmds.cpp | 2 +- src/fontcache.cpp | 96 +++++++------- src/fontcache.h | 54 +++++--- src/fontcache/freetypefontcache.cpp | 198 ++++++++++++++-------------- src/fontcache/spritefontcache.cpp | 14 ++ src/gfx.cpp | 4 +- src/gfxinit.cpp | 6 +- src/openttd.cpp | 4 +- src/os/macosx/font_osx.cpp | 156 ++++++++++++---------- src/os/unix/font_unix.cpp | 2 +- src/os/windows/font_win32.cpp | 178 +++++++++++++------------ src/settings_gui.cpp | 6 +- src/strings.cpp | 4 +- src/tests/mock_fontcache.h | 3 +- 14 files changed, 398 insertions(+), 329 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 52dfc9cffa..919f22684b 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2369,7 +2369,7 @@ static bool ConFont(std::span argv) FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); /* Make sure all non sprite fonts are loaded. */ if (!setting->font.empty() && !fc->HasParent()) { - InitFontCache(fs); + FontCache::LoadFontCaches(fs); fc = FontCache::Get(fs); } IConsolePrint(CC_DEFAULT, "{} font:", FontSizeToName(fs)); diff --git a/src/fontcache.cpp b/src/fontcache.cpp index d1be5e6ac2..0fe53a7350 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -12,7 +12,6 @@ #include "fontdetection.h" #include "blitter/factory.hpp" #include "gfx_layout.h" -#include "fontcache/spritefontcache.h" #include "openttd.h" #include "settings_func.h" #include "strings_func.h" @@ -30,22 +29,19 @@ FontCacheSettings _fcsettings; /** - * Create a new font cache. - * @param fs The size of the font. + * Try loading a font with any fontcache factory. + * @param fs Font size to load. + * @param fonttype Font type requested. + * @return FontCache of the font if loaded, or nullptr. */ -FontCache::FontCache(FontSize fs) : parent(FontCache::Get(fs)), fs(fs) +/* static */ std::unique_ptr FontProviderManager::LoadFont(FontSize fs, FontType fonttype) { - assert(this->parent == nullptr || this->fs == this->parent->fs); - FontCache::caches[this->fs] = this; - Layouter::ResetFontCache(this->fs); -} + for (auto &provider : FontProviderManager::GetProviders()) { + auto fc = provider->LoadFont(fs, fonttype); + if (fc != nullptr) return fc; + } -/** Clean everything up. */ -FontCache::~FontCache() -{ - assert(this->parent == nullptr || this->fs == this->parent->fs); - FontCache::caches[this->fs] = this->parent; - Layouter::ResetFontCache(this->fs); + return nullptr; } int FontCache::GetDefaultFontHeight(FontSize fs) @@ -80,12 +76,16 @@ int GetCharacterHeight(FontSize size) } -/* static */ FontCache *FontCache::caches[FS_END]; +/* 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 = FS_BEGIN; fs != FS_END; fs++) { - if (FontCache::caches[fs] == nullptr) new SpriteFontCache(fs); /* FontCache inserts itself into to the cache. */ + if (FontCache::Get(fs) != nullptr) continue; + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::Sprite)); } } @@ -126,7 +126,7 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) CheckForMissingGlyphs(); _fcsettings = std::move(backup); } else { - InitFontCache(fontsize); + FontCache::LoadFontCaches(fontsize); } LoadStringWidthTable(fontsize); @@ -136,15 +136,6 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) if (_save_config) SaveToConfig(); } -#ifdef WITH_FREETYPE -extern void LoadFreeTypeFont(FontSize fs); -extern void UninitFreeType(); -#elif defined(_WIN32) -extern void LoadWin32Font(FontSize fs); -#elif defined(WITH_COCOA) -extern void LoadCoreTextFont(FontSize fs); -#endif - /** * Test if a font setting uses the default font. * @return true iff the font is not configured and no fallback font data is present. @@ -211,40 +202,57 @@ std::string GetFontCacheFontName(FontSize fs) return GetDefaultTruetypeFontFile(fs); } +/** + * Register a FontCache for its font size. + * @param fc FontCache to register. + */ +/* static */ void FontCache::Register(std::unique_ptr &&fc) +{ + if (fc == nullptr) return; + + FontSize fs = fc->fs; + + fc->parent = std::move(FontCache::caches[fs]); + FontCache::caches[fs] = std::move(fc); +} + /** * (Re)initialize the font cache related things, i.e. load the non-sprite fonts. * @param fontsizes Font sizes to be initialised. */ -void InitFontCache(FontSizes fontsizes) +/* static */ void FontCache::LoadFontCaches(FontSizes fontsizes) { FontCache::InitializeFontCaches(); for (FontSize fs : fontsizes) { - FontCache *fc = FontCache::Get(fs); - if (fc->HasParent()) delete fc; + Layouter::ResetFontCache(fs); -#ifdef WITH_FREETYPE - LoadFreeTypeFont(fs); -#elif defined(_WIN32) - LoadWin32Font(fs); -#elif defined(WITH_COCOA) - LoadCoreTextFont(fs); -#endif + /* Unload everything except the sprite font cache. */ + while (FontCache::Get(fs)->HasParent()) { + FontCache::caches[fs] = std::move(FontCache::caches[fs]->parent); + } + + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType)); + } +} + +/** + * Clear cached information for the specified font caches. + * @param fontsizes Font sizes to clear. + */ +/* static */ void FontCache::ClearFontCaches(FontSizes fontsizes) +{ + for (FontSize fs : fontsizes) { + FontCache::Get(fs)->ClearFontCache(); } } /** * Free everything allocated w.r.t. fonts. */ -void UninitFontCache() +/* static */ void FontCache::UninitializeFontCaches() { - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { - while (FontCache::Get(fs) != nullptr) delete FontCache::Get(fs); - } - -#ifdef WITH_FREETYPE - UninitFreeType(); -#endif /* WITH_FREETYPE */ + std::ranges::generate(FontCache::caches, []() { return nullptr; }); } #if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) diff --git a/src/fontcache.h b/src/fontcache.h index 2ee0d240af..566dc86ccc 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -10,6 +10,7 @@ #ifndef FONTCACHE_H #define FONTCACHE_H +#include "provider_manager.h" #include "string_type.h" #include "spritecache.h" @@ -20,18 +21,23 @@ static const GlyphID SPRITE_GLYPH = 1U << 30; /** Font cache for basic fonts. */ class FontCache { protected: - static FontCache *caches[FS_END]; ///< All the font caches. - FontCache *parent; ///< The parent of this font cache. - const FontSize fs; ///< The size of the font. + static std::array, FS_END> caches; ///< All the font caches. + std::unique_ptrparent; ///< The parent of this font cache. + const FontSize fs; ///< The size of the font. 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); + public: - FontCache(FontSize fs); - virtual ~FontCache(); + virtual ~FontCache() {} static void InitializeFontCaches(); + static void UninitializeFontCaches(); + static void LoadFontCaches(FontSizes fontsizes); + static void ClearFontCaches(FontSizes fontsizes); /** Default unscaled font heights. */ static const int DEFAULT_FONT_HEIGHT[FS_END]; @@ -124,7 +130,7 @@ public: static inline FontCache *Get(FontSize fs) { assert(fs < FS_END); - return FontCache::caches[fs]; + return FontCache::caches[fs].get(); } static std::string GetName(FontSize fs); @@ -143,13 +149,6 @@ public: virtual bool IsBuiltInFont() = 0; }; -inline void ClearFontCache(FontSizes fontsizes) -{ - for (FontSize fs : fontsizes) { - FontCache::Get(fs)->ClearFontCache(); - } -} - /** Get the Sprite for a glyph */ inline const Sprite *GetGlyph(FontSize size, char32_t key) { @@ -207,12 +206,37 @@ inline FontCacheSubSetting *GetFontCacheSubSetting(FontSize fs) uint GetFontCacheFontSize(FontSize fs); std::string GetFontCacheFontName(FontSize fs); -void InitFontCache(FontSizes fontsizes); -void UninitFontCache(); bool GetFontAAState(); void SetFont(FontSize fontsize, const std::string &font, uint size); +/** Different types of font that can be loaded. */ +enum class FontType : uint8_t { + Sprite, ///< Bitmap sprites from GRF files. + TrueType, ///< Scalable TrueType fonts. +}; + +/** Factory for FontCaches. */ +class FontCacheFactory : public BaseProvider { +public: + FontCacheFactory(std::string_view name, std::string_view description) : BaseProvider(name, description) + { + ProviderManager::Register(*this); + } + + virtual ~FontCacheFactory() + { + ProviderManager::Unregister(*this); + } + + virtual std::unique_ptr LoadFont(FontSize fs, FontType fonttype) = 0; +}; + +class FontProviderManager : ProviderManager { +public: + static std::unique_ptr LoadFont(FontSize fs, FontType fonttype); +}; + /* Implemented in spritefontcache.cpp */ void InitializeUnicodeGlyphMap(); void SetUnicodeGlyph(FontSize size, char32_t key, SpriteID sprite); diff --git a/src/fontcache/freetypefontcache.cpp b/src/fontcache/freetypefontcache.cpp index 80bb2cda6d..34c628b651 100644 --- a/src/fontcache/freetypefontcache.cpp +++ b/src/fontcache/freetypefontcache.cpp @@ -46,9 +46,6 @@ public: const void *GetOSHandle() override { return &face; } }; -FT_Library _ft_library = nullptr; - - /** * Create a new FreeTypeFontCache. * @param fs The font size that is going to be cached. @@ -113,93 +110,6 @@ void FreeTypeFontCache::SetFontSize(int pixels) } } -static FT_Error LoadFont(FontSize fs, FT_Face face, std::string_view font_name, uint size) -{ - Debug(fontcache, 2, "Requested '{}', using '{} {}'", font_name, face->family_name, face->style_name); - - /* Attempt to select the unicode character map */ - FT_Error error = FT_Select_Charmap(face, ft_encoding_unicode); - if (error == FT_Err_Ok) goto found_face; // Success - - if (error == FT_Err_Invalid_CharMap_Handle) { - /* Try to pick a different character map instead. We default to - * the first map, but platform_id 0 encoding_id 0 should also - * be unicode (strange system...) */ - FT_CharMap found = face->charmaps[0]; - int i; - - for (i = 0; i < face->num_charmaps; i++) { - FT_CharMap charmap = face->charmaps[i]; - if (charmap->platform_id == 0 && charmap->encoding_id == 0) { - found = charmap; - } - } - - if (found != nullptr) { - error = FT_Set_Charmap(face, found); - if (error == FT_Err_Ok) goto found_face; - } - } - - FT_Done_Face(face); - return error; - -found_face: - new FreeTypeFontCache(fs, face, size); - return FT_Err_Ok; -} - -/** - * Loads the freetype font. - * First type to load the fontname as if it were a path. If that fails, - * try to resolve the filename of the font using fontconfig, where the - * format is 'font family name' or 'font family name, font style'. - * @param fs The font size to load. - */ -void LoadFreeTypeFont(FontSize fs) -{ - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); - - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return; - - if (_ft_library == nullptr) { - if (FT_Init_FreeType(&_ft_library) != FT_Err_Ok) { - ShowInfo("Unable to initialize FreeType, using sprite fonts instead"); - return; - } - - Debug(fontcache, 2, "Initialized"); - } - - FT_Face face = nullptr; - - /* If font is an absolute path to a ttf, try loading that first. */ - int32_t index = 0; - if (settings->os_handle != nullptr) index = *static_cast(settings->os_handle); - FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face); - - if (error != FT_Err_Ok) { - /* Check if font is a relative filename in one of our search-paths. */ - std::string full_font = FioFindFullPath(BASE_DIR, font); - if (!full_font.empty()) { - error = FT_New_Face(_ft_library, full_font.c_str(), 0, &face); - } - } - - /* Try loading based on font face name (OS-wide fonts). */ - if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face); - - if (error == FT_Err_Ok) { - error = LoadFont(fs, face, font, GetFontCacheFontSize(fs)); - if (error != FT_Err_Ok) { - ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}, using sprite font instead", font, FontSizeToName(fs), error); - } - } else { - FT_Done_Face(face); - } -} - /** * Free everything that was allocated for this font cache. */ @@ -296,14 +206,106 @@ GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) return glyph; } -/** - * Free everything allocated w.r.t. freetype. - */ -void UninitFreeType() -{ - FT_Done_FreeType(_ft_library); - _ft_library = nullptr; -} +FT_Library _ft_library = nullptr; + +class FreeTypeFontCacheFactory : public FontCacheFactory { +public: + FreeTypeFontCacheFactory() : FontCacheFactory("freetype", "FreeType font provider") {} + + virtual ~FreeTypeFontCacheFactory() + { + FT_Done_FreeType(_ft_library); + _ft_library = nullptr; + } + + /** + * Loads the freetype font. + * First type to load the fontname as if it were a path. If that fails, + * try to resolve the filename of the font using fontconfig, where the + * 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) 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"); + return nullptr; + } + + Debug(fontcache, 2, "Initialized"); + } + + FT_Face face = nullptr; + + /* If font is an absolute path to a ttf, try loading that first. */ + int32_t index = 0; + if (settings->os_handle != nullptr) index = *static_cast(settings->os_handle); + FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face); + + if (error != FT_Err_Ok) { + /* Check if font is a relative filename in one of our search-paths. */ + std::string full_font = FioFindFullPath(BASE_DIR, font); + if (!full_font.empty()) { + error = FT_New_Face(_ft_library, full_font.c_str(), 0, &face); + } + } + + /* Try loading based on font face name (OS-wide fonts). */ + if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face); + + if (error != FT_Err_Ok) { + FT_Done_Face(face); + return nullptr; + } + + return LoadFont(fs, face, font, GetFontCacheFontSize(fs)); + } + +private: + static std::unique_ptr LoadFont(FontSize fs, FT_Face face, std::string_view font_name, uint size) + { + Debug(fontcache, 2, "Requested '{}', using '{} {}'", font_name, face->family_name, face->style_name); + + /* Attempt to select the unicode character map */ + FT_Error error = FT_Select_Charmap(face, ft_encoding_unicode); + if (error == FT_Err_Invalid_CharMap_Handle) { + /* Try to pick a different character map instead. We default to + * the first map, but platform_id 0 encoding_id 0 should also + * be unicode (strange system...) */ + FT_CharMap found = face->charmaps[0]; + + for (int i = 0; i < face->num_charmaps; ++i) { + FT_CharMap charmap = face->charmaps[i]; + if (charmap->platform_id == 0 && charmap->encoding_id == 0) { + found = charmap; + } + } + + if (found != nullptr) { + error = FT_Set_Charmap(face, found); + } + } + + if (error != FT_Err_Ok) { + FT_Done_Face(face); + + ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), error); + return nullptr; + } + + return std::make_unique(fs, face, size); + } +}; + +static FreeTypeFontCacheFactory s_freetype_fontcache_factory; #if !defined(WITH_FONTCONFIG) diff --git a/src/fontcache/spritefontcache.cpp b/src/fontcache/spritefontcache.cpp index e2d93189c2..37bb5a9a6f 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -151,3 +151,17 @@ bool SpriteFontCache::GetDrawGlyphShadow() { return false; } + +class SpriteFontCacheFactory : public FontCacheFactory { +public: + SpriteFontCacheFactory() : FontCacheFactory("sprite", "Sprite font provider") {} + + std::unique_ptr LoadFont(FontSize fs, FontType fonttype) override + { + if (fonttype != FontType::Sprite) return nullptr; + + return std::make_unique(fs); + } +}; + +static SpriteFontCacheFactory s_sprite_fontcache_factory; diff --git a/src/gfx.cpp b/src/gfx.cpp index 9ba985e971..d04ed6733c 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -1246,7 +1246,7 @@ static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, */ void LoadStringWidthTable(FontSizes fontsizes) { - ClearFontCache(fontsizes); + FontCache::ClearFontCaches(fontsizes); for (FontSize fs : fontsizes) { for (uint i = 0; i != 224; i++) { @@ -1813,7 +1813,7 @@ bool AdjustGUIZoom(bool automatic) if (old_font_zoom != _font_zoom) { GfxClearFontSpriteCache(); } - ClearFontCache(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); LoadStringWidthTable(); SetupWidgetDimensions(); diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp index 20c5e69f11..6620876e4d 100644 --- a/src/gfxinit.cpp +++ b/src/gfxinit.cpp @@ -245,7 +245,7 @@ static void RealChangeBlitter(std::string_view repl_blitter) /* Clear caches that might have sprites for another blitter. */ VideoDriver::GetInstance()->ClearSystemSprites(); - ClearFontCache(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); GfxClearSpriteCache(); ReInitAllWindows(false); } @@ -326,7 +326,7 @@ void CheckBlitter() { if (!SwitchNewGRFBlitter()) return; - ClearFontCache(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); GfxClearSpriteCache(); ReInitAllWindows(false); } @@ -338,7 +338,7 @@ void GfxLoadSprites() SwitchNewGRFBlitter(); VideoDriver::GetInstance()->ClearSystemSprites(); - ClearFontCache(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); GfxInitSpriteMem(); LoadSpriteTables(); GfxInitPalettes(); diff --git a/src/openttd.cpp b/src/openttd.cpp index 533ee0446a..e05daac567 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -315,7 +315,7 @@ static void ShutdownGame() /* No NewGRFs were loaded when it was still bootstrapping. */ if (_game_mode != GM_BOOTSTRAP) ResetNewGRFData(); - UninitFontCache(); + FontCache::UninitializeFontCaches(); } /** @@ -700,7 +700,7 @@ int openttd_main(std::span arguments) InitializeLanguagePacks(); /* Initialize the font cache */ - InitFontCache(FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(FONTSIZES_REQUIRED); /* This must be done early, since functions use the SetWindowDirty* calls */ InitWindowSystem(); diff --git a/src/os/macosx/font_osx.cpp b/src/os/macosx/font_osx.cpp index 6588eb7507..c969f9f1db 100644 --- a/src/os/macosx/font_osx.cpp +++ b/src/os/macosx/font_osx.cpp @@ -287,88 +287,98 @@ const Sprite *CoreTextFontCache::InternalGetGlyph(GlyphID key, bool use_aa) return this->SetGlyphPtr(key, std::move(new_glyph)).GetSprite(); } -static CTFontDescriptorRef LoadFontFromFile(const std::string &font_name) -{ - if (!MacOSVersionIsAtLeast(10, 6, 0)) return nullptr; +class CoreTextFontCacheFactory : public FontCacheFactory { +public: + CoreTextFontCacheFactory() : FontCacheFactory("coretext", "CoreText font loader") {} - /* Might be a font file name, try load it. Direct font loading is - * only supported starting on OSX 10.6. */ - CFAutoRelease path; + /** + * Loads the TrueType font. + * If a CoreText 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) override + { + if (fonttype != FontType::TrueType) return nullptr; - /* See if this is an absolute path. */ - if (FileExists(font_name)) { - path.reset(CFStringCreateWithCString(kCFAllocatorDefault, font_name.c_str(), kCFStringEncodingUTF8)); - } else { - /* Scan the search-paths to see if it can be found. */ - std::string full_font = FioFindFullPath(BASE_DIR, font_name); - if (!full_font.empty()) { - path.reset(CFStringCreateWithCString(kCFAllocatorDefault, full_font.c_str(), kCFStringEncodingUTF8)); + FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); + + std::string font = GetFontCacheFontName(fs); + if (font.empty()) return nullptr; + + CFAutoRelease font_ref; + + if (settings->os_handle != nullptr) { + font_ref.reset(static_cast(const_cast(settings->os_handle))); + CFRetain(font_ref.get()); // Increase ref count to match a later release. } - } - if (path) { - /* Try getting a font descriptor to see if the system can use it. */ - CFAutoRelease url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle, false)); - CFAutoRelease descs(CTFontManagerCreateFontDescriptorsFromURL(url.get())); - - if (descs && CFArrayGetCount(descs.get()) > 0) { - CTFontDescriptorRef font_ref = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0); - CFRetain(font_ref); - return font_ref; + 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)); } - } - return nullptr; -} + if (!font_ref) { + CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, font.c_str(), kCFStringEncodingUTF8)); -/** - * Loads the TrueType font. - * If a CoreText 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. - */ -void LoadCoreTextFont(FontSize fs) -{ - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); + /* Simply creating the font using CTFontCreateWithNameAndSize will *always* return + * something, no matter the name. As such, we can't use it to check for existence. + * We instead query the list of all font descriptors that match the given name which + * does not do this stupid name fallback. */ + CFAutoRelease name_desc(CTFontDescriptorCreateWithNameAndSize(name.get(), 0.0)); + CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontNameAttribute)), 1, &kCFTypeSetCallBacks)); + CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(name_desc.get(), mandatory_attribs.get())); - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return; - - CFAutoRelease font_ref; - - if (settings->os_handle != nullptr) { - font_ref.reset(static_cast(const_cast(settings->os_handle))); - CFRetain(font_ref.get()); // Increase ref count to match a later release. - } - - if (!font_ref && MacOSVersionIsAtLeast(10, 6, 0)) { - /* 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) { - CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, font.c_str(), kCFStringEncodingUTF8)); - - /* Simply creating the font using CTFontCreateWithNameAndSize will *always* return - * something, no matter the name. As such, we can't use it to check for existence. - * We instead query the list of all font descriptors that match the given name which - * does not do this stupid name fallback. */ - CFAutoRelease name_desc(CTFontDescriptorCreateWithNameAndSize(name.get(), 0.0)); - CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontNameAttribute)), 1, &kCFTypeSetCallBacks)); - CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(name_desc.get(), mandatory_attribs.get())); - - /* Assume the first result is the one we want. */ - if (descs && CFArrayGetCount(descs.get()) > 0) { - font_ref.reset((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0)); - CFRetain(font_ref.get()); + /* Assume the first result is the one we want. */ + if (descs && CFArrayGetCount(descs.get()) > 0) { + font_ref.reset((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0)); + CFRetain(font_ref.get()); + } } + + if (!font_ref) { + ShowInfo("Unable to use '{}' for {} font, using sprite font instead", font, FontSizeToName(fs)); + return nullptr; + } + + return std::make_unique(fs, std::move(font_ref), GetFontCacheFontSize(fs)); } - if (!font_ref) { - ShowInfo("Unable to use '{}' for {} font, using sprite font instead", font, FontSizeToName(fs)); - return; - } +private: + static CTFontDescriptorRef LoadFontFromFile(const std::string &font_name) + { + if (!MacOSVersionIsAtLeast(10, 6, 0)) return nullptr; - new CoreTextFontCache(fs, std::move(font_ref), GetFontCacheFontSize(fs)); -} + /* Might be a font file name, try load it. Direct font loading is + * only supported starting on OSX 10.6. */ + CFAutoRelease path; + + /* See if this is an absolute path. */ + if (FileExists(font_name)) { + path.reset(CFStringCreateWithCString(kCFAllocatorDefault, font_name.c_str(), kCFStringEncodingUTF8)); + } else { + /* Scan the search-paths to see if it can be found. */ + std::string full_font = FioFindFullPath(BASE_DIR, font_name); + if (!full_font.empty()) { + path.reset(CFStringCreateWithCString(kCFAllocatorDefault, full_font.c_str(), kCFStringEncodingUTF8)); + } + } + + if (path) { + /* Try getting a font descriptor to see if the system can use it. */ + CFAutoRelease url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle, false)); + CFAutoRelease descs(CTFontManagerCreateFontDescriptorsFromURL(url.get())); + + if (descs && CFArrayGetCount(descs.get()) > 0) { + CTFontDescriptorRef font_ref = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0); + CFRetain(font_ref); + return font_ref; + } + } + + return nullptr; + } +}; + +static CoreTextFontCacheFactory s_coretext_fontcache_Factory; diff --git a/src/os/unix/font_unix.cpp b/src/os/unix/font_unix.cpp index feb96713e5..645386066f 100644 --- a/src/os/unix/font_unix.cpp +++ b/src/os/unix/font_unix.cpp @@ -182,6 +182,6 @@ bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_is if (best_font == nullptr) return false; callback->SetFontNames(settings, best_font, &best_index); - InitFontCache(callback->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(callback->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); return true; } diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index 6358ca4d3d..d8074991ce 100644 --- a/src/os/windows/font_win32.cpp +++ b/src/os/windows/font_win32.cpp @@ -293,99 +293,109 @@ void Win32FontCache::ClearFontCache() return allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END ? this->parent->MapCharToGlyph(key) : 0; } +class Win32FontCacheFactory : FontCacheFactory { +public: + Win32FontCacheFactory() : FontCacheFactory("win32", "Win32 font loader") {} -static bool TryLoadFontFromFile(const std::string &font_name, LOGFONT &logfont) -{ - wchar_t fontPath[MAX_PATH] = {}; + /** + * 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) override + { + if (fonttype != FontType::TrueType) return nullptr; - /* See if this is an absolute path. */ - if (FileExists(font_name)) { - convert_to_fs(font_name, fontPath); - } else { - /* Scan the search-paths to see if it can be found. */ - std::string full_font = FioFindFullPath(BASE_DIR, font_name); - if (!full_font.empty()) { - convert_to_fs(font_name, fontPath); + FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); + + std::string font = GetFontCacheFontName(fs); + if (font.empty()) return nullptr; + + LOGFONT logfont{}; + logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH; + logfont.lfCharSet = DEFAULT_CHARSET; + logfont.lfOutPrecision = OUT_OUTLINE_PRECIS; + logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + + if (settings->os_handle != nullptr) { + logfont = *(const LOGFONT *)settings->os_handle; + } 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 (logfont.lfFaceName[0] == 0) { + logfont.lfWeight = StrContainsIgnoreCase(font, " bold") ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. + convert_to_fs(font, logfont.lfFaceName); + } + + return LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); } - if (fontPath[0] != 0) { - if (AddFontResourceEx(fontPath, FR_PRIVATE, 0) != 0) { - /* Try a nice little undocumented function first for getting the internal font name. - * Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */ - static LibraryLoader _gdi32("gdi32.dll"); - typedef BOOL(WINAPI *PFNGETFONTRESOURCEINFO)(LPCTSTR, LPDWORD, LPVOID, DWORD); - static PFNGETFONTRESOURCEINFO GetFontResourceInfo = _gdi32.GetFunction("GetFontResourceInfoW"); +private: + static std::unique_ptr LoadWin32Font(FontSize fs, const LOGFONT &logfont, uint size, std::string_view font_name) + { + HFONT font = CreateFontIndirect(&logfont); + if (font == nullptr) { + ShowInfo("Unable to use '{}' for {} font, Win32 reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), GetLastError()); + return nullptr; + } + DeleteObject(font); - if (GetFontResourceInfo != nullptr) { - /* Try to query an array of LOGFONTs that describe the file. */ - DWORD len = 0; - if (GetFontResourceInfo(fontPath, &len, nullptr, 2) && len >= sizeof(LOGFONT)) { - LOGFONT *buf = (LOGFONT *)new uint8_t[len]; - if (GetFontResourceInfo(fontPath, &len, buf, 2)) { - logfont = *buf; // Just use first entry. + return std::make_unique(fs, logfont, size); + } + + static bool TryLoadFontFromFile(const std::string &font_name, LOGFONT &logfont) + { + wchar_t fontPath[MAX_PATH] = {}; + + /* See if this is an absolute path. */ + if (FileExists(font_name)) { + convert_to_fs(font_name, fontPath); + } else { + /* Scan the search-paths to see if it can be found. */ + std::string full_font = FioFindFullPath(BASE_DIR, font_name); + if (!full_font.empty()) { + convert_to_fs(font_name, fontPath); + } + } + + if (fontPath[0] != 0) { + if (AddFontResourceEx(fontPath, FR_PRIVATE, 0) != 0) { + /* Try a nice little undocumented function first for getting the internal font name. + * Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */ + static LibraryLoader _gdi32("gdi32.dll"); + typedef BOOL(WINAPI *PFNGETFONTRESOURCEINFO)(LPCTSTR, LPDWORD, LPVOID, DWORD); + static PFNGETFONTRESOURCEINFO GetFontResourceInfo = _gdi32.GetFunction("GetFontResourceInfoW"); + + if (GetFontResourceInfo != nullptr) { + /* Try to query an array of LOGFONTs that describe the file. */ + DWORD len = 0; + if (GetFontResourceInfo(fontPath, &len, nullptr, 2) && len >= sizeof(LOGFONT)) { + LOGFONT *buf = (LOGFONT *)new uint8_t[len]; + if (GetFontResourceInfo(fontPath, &len, buf, 2)) { + logfont = *buf; // Just use first entry. + } + delete[](uint8_t *)buf; } - delete[](uint8_t *)buf; + } + + /* No dice yet. Use the file name as the font face name, hoping it matches. */ + if (logfont.lfFaceName[0] == 0) { + wchar_t fname[_MAX_FNAME]; + _wsplitpath(fontPath, nullptr, nullptr, fname, nullptr); + + wcsncpy_s(logfont.lfFaceName, lengthof(logfont.lfFaceName), fname, _TRUNCATE); + logfont.lfWeight = StrContainsIgnoreCase(font_name, " bold") || StrContainsIgnoreCase(font_name, "-bold") ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. } } - - /* No dice yet. Use the file name as the font face name, hoping it matches. */ - if (logfont.lfFaceName[0] == 0) { - wchar_t fname[_MAX_FNAME]; - _wsplitpath(fontPath, nullptr, nullptr, fname, nullptr); - - wcsncpy_s(logfont.lfFaceName, lengthof(logfont.lfFaceName), fname, _TRUNCATE); - logfont.lfWeight = StrContainsIgnoreCase(font_name, " bold") || StrContainsIgnoreCase(font_name, "-bold") ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. - } } + + return logfont.lfFaceName[0] != 0; } +}; - return logfont.lfFaceName[0] != 0; -} - -static void LoadWin32Font(FontSize fs, const LOGFONT &logfont, uint size, std::string_view font_name) -{ - HFONT font = CreateFontIndirect(&logfont); - if (font == nullptr) { - ShowInfo("Unable to use '{}' for {} font, Win32 reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), GetLastError()); - return; - } - DeleteObject(font); - - new Win32FontCache(fs, logfont, size); -} -/** - * 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. - */ -void LoadWin32Font(FontSize fs) -{ - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); - - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return; - - LOGFONT logfont{}; - logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH; - logfont.lfCharSet = DEFAULT_CHARSET; - logfont.lfOutPrecision = OUT_OUTLINE_PRECIS; - logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; - - if (settings->os_handle != nullptr) { - logfont = *(const LOGFONT *)settings->os_handle; - } 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 (logfont.lfFaceName[0] == 0) { - logfont.lfWeight = StrContainsIgnoreCase(font, " bold") ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. - convert_to_fs(font, logfont.lfFaceName); - } - - LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); -} +static Win32FontCacheFactory s_win32_fontcache_factory; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 3e9353d488..38c5a6de9f 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1036,8 +1036,8 @@ struct GameOptionsWindow : Window { this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA, _fcsettings.prefer_sprite); this->SetDirty(); - InitFontCache(FONTSIZES_ALL); - ClearFontCache(FONTSIZES_ALL); + FontCache::LoadFontCaches(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); CheckForMissingGlyphs(); SetupWidgetDimensions(); UpdateAllVirtCoords(); @@ -1050,7 +1050,7 @@ struct GameOptionsWindow : Window { this->SetWidgetLoweredState(WID_GO_GUI_FONT_AA, _fcsettings.global_aa); MarkWholeScreenDirty(); - ClearFontCache(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); break; #endif /* HAS_TRUETYPE_FONT */ diff --git a/src/strings.cpp b/src/strings.cpp index 69d605e86d..a8ac71841d 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2278,7 +2278,7 @@ std::string_view GetCurrentLanguageIsoCode() */ bool MissingGlyphSearcher::FindMissingGlyphs() { - InitFontCache(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); this->Reset(); for (auto text = this->NextString(); text.has_value(); text = this->NextString()) { @@ -2395,7 +2395,7 @@ void CheckForMissingGlyphs(bool base_font, 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 */ - InitFontCache(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); } } #endif diff --git a/src/tests/mock_fontcache.h b/src/tests/mock_fontcache.h index c159742a31..b009cfc33e 100644 --- a/src/tests/mock_fontcache.h +++ b/src/tests/mock_fontcache.h @@ -32,7 +32,8 @@ public: static void InitializeFontCaches() { for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { - if (FontCache::caches[fs] == nullptr) new MockFontCache(fs); /* FontCache inserts itself into to the cache. */ + if (FontCache::Get(fs) != nullptr) continue; + FontCache::caches[fs] = std::make_unique(fs); } } }; From 497d18d9878631bee7f94e6afa97cb3a85d9a389 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 15 Jul 2025 19:47:08 +0100 Subject: [PATCH 4/4] Codechange: Move fallback font detection to FontCacheFactory. Provides a standard interface instead of relying on defines. --- src/CMakeLists.txt | 1 - src/fontcache.cpp | 25 +++- src/fontcache.h | 2 + src/fontcache/freetypefontcache.cpp | 21 ++-- src/fontcache/spritefontcache.cpp | 5 + src/fontdetection.h | 41 ------- src/os/macosx/font_osx.cpp | 170 ++++++++++++++-------------- src/os/unix/CMakeLists.txt | 1 + src/os/unix/font_unix.cpp | 16 ++- src/os/unix/font_unix.h | 26 +++++ src/os/windows/font_win32.cpp | 52 ++++----- src/strings.cpp | 3 +- src/tests/mock_fontcache.h | 2 +- 13 files changed, 188 insertions(+), 177 deletions(-) delete mode 100644 src/fontdetection.h create mode 100644 src/os/unix/font_unix.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 266e4e577c..fdec332a07 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -187,7 +187,6 @@ add_files( fios_gui.cpp fontcache.cpp fontcache.h - fontdetection.h framerate_gui.cpp framerate_type.h gamelog.cpp diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 0fe53a7350..7b44c32e24 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -9,7 +9,6 @@ #include "stdafx.h" #include "fontcache.h" -#include "fontdetection.h" #include "blitter/factory.hpp" #include "gfx_layout.h" #include "openttd.h" @@ -44,6 +43,25 @@ FontCacheSettings _fcsettings; return nullptr; } +/** + * We would like to have a fallback font as the current one + * doesn't contain all characters we need. + * This function must set all fonts of settings. + * @param settings the settings to overwrite the fontname of. + * @param language_isocode the language, e.g. en_GB. + * @param callback The function to call to check for missing glyphs. + * @return true if a font has been set, false otherwise. + */ +/* static */ bool FontProviderManager::FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) +{ + for (auto &provider : FontProviderManager::GetProviders()) { + if (provider->FindFallbackFont(settings, language_isocode, callback)) { + return true; + } + } + return false; +} + int FontCache::GetDefaultFontHeight(FontSize fs) { return FontCache::DEFAULT_FONT_HEIGHT[fs]; @@ -254,8 +272,3 @@ std::string GetFontCacheFontName(FontSize fs) { std::ranges::generate(FontCache::caches, []() { return nullptr; }); } - -#if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) - -bool SetFallbackFont(FontCacheSettings *, const std::string &, MissingGlyphSearcher *) { return false; } -#endif /* !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) */ diff --git a/src/fontcache.h b/src/fontcache.h index 566dc86ccc..f918562b86 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -230,11 +230,13 @@ public: } virtual std::unique_ptr LoadFont(FontSize fs, FontType fonttype) = 0; + virtual bool FindFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) = 0; }; class FontProviderManager : ProviderManager { public: static std::unique_ptr LoadFont(FontSize fs, FontType fonttype); + static bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); }; /* Implemented in spritefontcache.cpp */ diff --git a/src/fontcache/freetypefontcache.cpp b/src/fontcache/freetypefontcache.cpp index 34c628b651..1a687ff280 100644 --- a/src/fontcache/freetypefontcache.cpp +++ b/src/fontcache/freetypefontcache.cpp @@ -8,14 +8,14 @@ /** @file freetypefontcache.cpp FreeType font cache implementation. */ #include "../stdafx.h" + #include "../debug.h" #include "../fontcache.h" -#include "../fontdetection.h" #include "../blitter/factory.hpp" -#include "../core/math_func.hpp" #include "../zoom_func.h" #include "../fileio_func.h" #include "../error_func.h" +#include "../../os/unix/font_unix.h" #include "truetypefontcache.h" #include "../table/control_codes.h" @@ -258,8 +258,10 @@ public: } } +#ifdef WITH_FONTCONFIG /* 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) { FT_Done_Face(face); @@ -269,6 +271,15 @@ public: return LoadFont(fs, face, font, GetFontCacheFontSize(fs)); } + bool FindFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) override + { +#ifdef WITH_FONTCONFIG + if (FontConfigFindFallbackFont(settings, language_isocode, callback)) return true; +#endif /* WITH_FONTCONFIG */ + + return false; + } + private: static std::unique_ptr LoadFont(FontSize fs, FT_Face face, std::string_view font_name, uint size) { @@ -307,10 +318,4 @@ private: static FreeTypeFontCacheFactory s_freetype_fontcache_factory; -#if !defined(WITH_FONTCONFIG) - -FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face) { return FT_Err_Cannot_Open_Resource; } - -#endif /* !defined(WITH_FONTCONFIG) */ - #endif /* WITH_FREETYPE */ diff --git a/src/fontcache/spritefontcache.cpp b/src/fontcache/spritefontcache.cpp index 37bb5a9a6f..e602abbba4 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -162,6 +162,11 @@ public: return std::make_unique(fs); } + + bool FindFallbackFont(struct FontCacheSettings *, const std::string &, class MissingGlyphSearcher *) override + { + return false; + } }; static SpriteFontCacheFactory s_sprite_fontcache_factory; diff --git a/src/fontdetection.h b/src/fontdetection.h deleted file mode 100644 index 55a2ab7eba..0000000000 --- a/src/fontdetection.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This file is part of OpenTTD. - * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. - * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . - */ - -/** @file fontdetection.h Functions related to detecting/finding the right font. */ - -#ifndef FONTDETECTION_H -#define FONTDETECTION_H - -#include "fontcache.h" - -#ifdef WITH_FREETYPE - -#include -#include FT_FREETYPE_H - -/** - * Load a freetype font face with the given font name. - * @param font_name The name of the font to load. - * @param face The face that has been found. - * @return The error we encountered. - */ -FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face); - -#endif /* WITH_FREETYPE */ - -/** - * We would like to have a fallback font as the current one - * doesn't contain all characters we need. - * This function must set all fonts of settings. - * @param settings the settings to overwrite the fontname of. - * @param language_isocode the language, e.g. en_GB. - * @param callback The function to call to check for missing glyphs. - * @return true if a font has been set, false otherwise. - */ -bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback); - -#endif diff --git a/src/os/macosx/font_osx.cpp b/src/os/macosx/font_osx.cpp index c969f9f1db..894eb99ec7 100644 --- a/src/os/macosx/font_osx.cpp +++ b/src/os/macosx/font_osx.cpp @@ -14,7 +14,6 @@ #include "../../blitter/factory.hpp" #include "../../error_func.h" #include "../../fileio_func.h" -#include "../../fontdetection.h" #include "../../string_func.h" #include "../../strings_func.h" #include "../../zoom_func.h" @@ -24,91 +23,6 @@ #include "../../safeguards.h" -bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) -{ - /* Determine fallback font using CoreText. This uses the language isocode - * to find a suitable font. CoreText is available from 10.5 onwards. */ - std::string lang; - if (language_isocode == "zh_TW") { - /* Traditional Chinese */ - lang = "zh-Hant"; - } else if (language_isocode == "zh_CN") { - /* Simplified Chinese */ - lang = "zh-Hans"; - } else { - /* Just copy the first part of the isocode. */ - lang = language_isocode.substr(0, language_isocode.find('_')); - } - - /* Create a font descriptor matching the wanted language and latin (english) glyphs. - * Can't use CFAutoRelease here for everything due to the way the dictionary has to be created. */ - CFStringRef lang_codes[2]; - lang_codes[0] = CFStringCreateWithCString(kCFAllocatorDefault, lang.c_str(), kCFStringEncodingUTF8); - lang_codes[1] = CFSTR("en"); - CFArrayRef lang_arr = CFArrayCreate(kCFAllocatorDefault, (const void **)lang_codes, lengthof(lang_codes), &kCFTypeArrayCallBacks); - CFAutoRelease lang_attribs(CFDictionaryCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontLanguagesAttribute)), (const void **)&lang_arr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); - CFAutoRelease lang_desc(CTFontDescriptorCreateWithAttributes(lang_attribs.get())); - CFRelease(lang_arr); - CFRelease(lang_codes[0]); - - /* Get array of all font descriptors for the wanted language. */ - CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontLanguagesAttribute)), 1, &kCFTypeSetCallBacks)); - CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(lang_desc.get(), mandatory_attribs.get())); - - bool result = false; - for (int tries = 0; tries < 2; tries++) { - for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()); i++) { - CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i); - - /* Get font traits. */ - CFAutoRelease traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute)); - CTFontSymbolicTraits symbolic_traits; - CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait), kCFNumberIntType, &symbolic_traits); - - /* Skip symbol fonts and vertical fonts. */ - if ((symbolic_traits & kCTFontClassMaskTrait) == (CTFontStylisticClass)kCTFontSymbolicClass || (symbolic_traits & kCTFontVerticalTrait)) continue; - /* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */ - if (symbolic_traits & kCTFontBoldTrait) continue; - /* Select monospaced fonts if asked for. */ - if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue; - - /* Get font name. */ - char buffer[128]; - CFAutoRelease font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute)); - CFStringGetCString(font_name.get(), buffer, std::size(buffer), kCFStringEncodingUTF8); - - /* Serif fonts usually look worse on-screen with only small - * font sizes. As such, we try for a sans-serif font first. - * If we can't find one in the first try, try all fonts. */ - if (tries == 0 && (symbolic_traits & kCTFontClassMaskTrait) != (CTFontStylisticClass)kCTFontSansSerifClass) continue; - - /* There are some special fonts starting with an '.' and the last - * resort font that aren't usable. Skip them. */ - std::string_view name{buffer}; - if (name.starts_with(".") || name.starts_with("LastResort")) continue; - - /* Save result. */ - callback->SetFontNames(settings, name); - if (!callback->FindMissingGlyphs()) { - Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name); - result = true; - break; - } - } - } - - if (!result) { - /* For some OS versions, the font 'Arial Unicode MS' does not report all languages it - * supports. If we didn't find any other font, just try it, maybe we get lucky. */ - callback->SetFontNames(settings, "Arial Unicode MS"); - result = !callback->FindMissingGlyphs(); - } - - callback->FindMissingGlyphs(); - return result; -} - - CoreTextFontCache::CoreTextFontCache(FontSize fs, CFAutoRelease &&font, int pixels) : TrueTypeFontCache(fs, pixels), font_desc(std::move(font)) { this->SetFontSize(pixels); @@ -345,6 +259,90 @@ public: return std::make_unique(fs, std::move(font_ref), GetFontCacheFontSize(fs)); } + bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) override + { + /* Determine fallback font using CoreText. This uses the language isocode + * to find a suitable font. CoreText is available from 10.5 onwards. */ + std::string lang; + if (language_isocode == "zh_TW") { + /* Traditional Chinese */ + lang = "zh-Hant"; + } else if (language_isocode == "zh_CN") { + /* Simplified Chinese */ + lang = "zh-Hans"; + } else { + /* Just copy the first part of the isocode. */ + lang = language_isocode.substr(0, language_isocode.find('_')); + } + + /* Create a font descriptor matching the wanted language and latin (english) glyphs. + * Can't use CFAutoRelease here for everything due to the way the dictionary has to be created. */ + CFStringRef lang_codes[2]; + lang_codes[0] = CFStringCreateWithCString(kCFAllocatorDefault, lang.c_str(), kCFStringEncodingUTF8); + lang_codes[1] = CFSTR("en"); + CFArrayRef lang_arr = CFArrayCreate(kCFAllocatorDefault, (const void **)lang_codes, lengthof(lang_codes), &kCFTypeArrayCallBacks); + CFAutoRelease lang_attribs(CFDictionaryCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontLanguagesAttribute)), (const void **)&lang_arr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + CFAutoRelease lang_desc(CTFontDescriptorCreateWithAttributes(lang_attribs.get())); + CFRelease(lang_arr); + CFRelease(lang_codes[0]); + + /* Get array of all font descriptors for the wanted language. */ + CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontLanguagesAttribute)), 1, &kCFTypeSetCallBacks)); + CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(lang_desc.get(), mandatory_attribs.get())); + + bool result = false; + for (int tries = 0; tries < 2; tries++) { + for (CFIndex i = 0; descs.get() != nullptr && i < CFArrayGetCount(descs.get()); i++) { + CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), i); + + /* Get font traits. */ + CFAutoRelease traits((CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute)); + CTFontSymbolicTraits symbolic_traits; + CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait), kCFNumberIntType, &symbolic_traits); + + /* Skip symbol fonts and vertical fonts. */ + if ((symbolic_traits & kCTFontClassMaskTrait) == (CTFontStylisticClass)kCTFontSymbolicClass || (symbolic_traits & kCTFontVerticalTrait)) continue; + /* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */ + if (symbolic_traits & kCTFontBoldTrait) continue; + /* Select monospaced fonts if asked for. */ + if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue; + + /* Get font name. */ + char buffer[128]; + CFAutoRelease font_name((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontDisplayNameAttribute)); + CFStringGetCString(font_name.get(), buffer, std::size(buffer), kCFStringEncodingUTF8); + + /* Serif fonts usually look worse on-screen with only small + * font sizes. As such, we try for a sans-serif font first. + * If we can't find one in the first try, try all fonts. */ + if (tries == 0 && (symbolic_traits & kCTFontClassMaskTrait) != (CTFontStylisticClass)kCTFontSansSerifClass) continue; + + /* There are some special fonts starting with an '.' and the last + * resort font that aren't usable. Skip them. */ + std::string_view name{buffer}; + if (name.starts_with(".") || name.starts_with("LastResort")) continue; + + /* Save result. */ + callback->SetFontNames(settings, name); + if (!callback->FindMissingGlyphs()) { + Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name); + result = true; + break; + } + } + } + + if (!result) { + /* For some OS versions, the font 'Arial Unicode MS' does not report all languages it + * supports. If we didn't find any other font, just try it, maybe we get lucky. */ + callback->SetFontNames(settings, "Arial Unicode MS"); + result = !callback->FindMissingGlyphs(); + } + + callback->FindMissingGlyphs(); + return result; + } + private: static CTFontDescriptorRef LoadFontFromFile(const std::string &font_name) { diff --git a/src/os/unix/CMakeLists.txt b/src/os/unix/CMakeLists.txt index f6c40d015f..d5cdcfdf19 100644 --- a/src/os/unix/CMakeLists.txt +++ b/src/os/unix/CMakeLists.txt @@ -12,6 +12,7 @@ add_files( add_files( font_unix.cpp + font_unix.h CONDITION Fontconfig_FOUND ) diff --git a/src/os/unix/font_unix.cpp b/src/os/unix/font_unix.cpp index 645386066f..dd8f2f1d2a 100644 --- a/src/os/unix/font_unix.cpp +++ b/src/os/unix/font_unix.cpp @@ -11,17 +11,17 @@ #include "../../misc/autorelease.hpp" #include "../../debug.h" -#include "../../fontdetection.h" +#include "../../fontcache.h" #include "../../string_func.h" #include "../../strings_func.h" +#include "font_unix.h" #include - -#include "../../safeguards.h" - #include #include FT_FREETYPE_H +#include "../../safeguards.h" + extern FT_Library _ft_library; /** @@ -61,6 +61,12 @@ static std::tuple SplitFontFamilyAndStyle(std::string_ return { std::string(font_name.substr(0, separator)), std::string(font_name.substr(begin)) }; } +/** + * Load a freetype font face with the given font name. + * @param font_name The name of the font to load. + * @param face The face that has been found. + * @return The error we encountered. + */ FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face) { FT_Error err = FT_Err_Cannot_Open_Resource; @@ -117,7 +123,7 @@ FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face) return err; } -bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) +bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) { bool ret = false; diff --git a/src/os/unix/font_unix.h b/src/os/unix/font_unix.h new file mode 100644 index 0000000000..bd1eea4416 --- /dev/null +++ b/src/os/unix/font_unix.h @@ -0,0 +1,26 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file font_unix.h Functions related to detecting/finding the right font. */ + +#ifndef FONT_UNIX_H +#define FONT_UNIX_H + +#ifdef WITH_FONTCONFIG + +#include "../../fontcache.h" + +#include +#include FT_FREETYPE_H + +FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face); + +bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); + +#endif /* WITH_FONTCONFIG */ + +#endif /* FONT_UNIX_H */ diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index d8074991ce..f292a6f805 100644 --- a/src/os/windows/font_win32.cpp +++ b/src/os/windows/font_win32.cpp @@ -15,7 +15,6 @@ #include "../../fileio_func.h" #include "../../fontcache.h" #include "../../fontcache/truetypefontcache.h" -#include "../../fontdetection.h" #include "../../library_loader.h" #include "../../string_func.h" #include "../../strings_func.h" @@ -85,32 +84,6 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT return 0; // stop enumerating } -bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) -{ - Debug(fontcache, 1, "Trying fallback fonts"); - EFCParam langInfo; - std::wstring lang = OTTD2FS(language_isocode.substr(0, language_isocode.find('_'))); - if (GetLocaleInfoEx(lang.c_str(), LOCALE_FONTSIGNATURE, reinterpret_cast(&langInfo.locale), sizeof(langInfo.locale) / sizeof(wchar_t)) == 0) { - /* Invalid isocode or some other mysterious error, can't determine fallback font. */ - Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode); - return false; - } - langInfo.settings = settings; - langInfo.callback = callback; - - LOGFONT font; - /* Enumerate all fonts. */ - font.lfCharSet = DEFAULT_CHARSET; - font.lfFaceName[0] = '\0'; - font.lfPitchAndFamily = 0; - - HDC dc = GetDC(nullptr); - int ret = EnumFontFamiliesEx(dc, &font, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&langInfo, 0); - ReleaseDC(nullptr, dc); - return ret == 0; -} - - /** * Create a new Win32FontCache. * @param fs The font size that is going to be cached. @@ -335,6 +308,31 @@ public: return LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); } + bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) override + { + Debug(fontcache, 1, "Trying fallback fonts"); + EFCParam langInfo; + std::wstring lang = OTTD2FS(language_isocode.substr(0, language_isocode.find('_'))); + if (GetLocaleInfoEx(lang.c_str(), LOCALE_FONTSIGNATURE, reinterpret_cast(&langInfo.locale), sizeof(langInfo.locale) / sizeof(wchar_t)) == 0) { + /* Invalid isocode or some other mysterious error, can't determine fallback font. */ + Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode); + return false; + } + langInfo.settings = settings; + langInfo.callback = callback; + + LOGFONT font; + /* Enumerate all fonts. */ + font.lfCharSet = DEFAULT_CHARSET; + font.lfFaceName[0] = '\0'; + font.lfPitchAndFamily = 0; + + HDC dc = GetDC(nullptr); + int ret = EnumFontFamiliesEx(dc, &font, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&langInfo, 0); + ReleaseDC(nullptr, dc); + return ret == 0; + } + private: static std::unique_ptr LoadWin32Font(FontSize fs, const LOGFONT &logfont, uint size, std::string_view font_name) { diff --git a/src/strings.cpp b/src/strings.cpp index a8ac71841d..f4ff461bc7 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -17,7 +17,6 @@ #include "newgrf_text.h" #include "fileio_func.h" #include "signs_base.h" -#include "fontdetection.h" #include "error.h" #include "error_func.h" #include "strings_func.h" @@ -2376,7 +2375,7 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher) _fcsettings.mono.os_handle = nullptr; _fcsettings.medium.os_handle = nullptr; - bad_font = !SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher); + bad_font = !FontProviderManager::FindFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher); _fcsettings = std::move(backup); diff --git a/src/tests/mock_fontcache.h b/src/tests/mock_fontcache.h index b009cfc33e..5be357756d 100644 --- a/src/tests/mock_fontcache.h +++ b/src/tests/mock_fontcache.h @@ -33,7 +33,7 @@ public: { for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { if (FontCache::Get(fs) != nullptr) continue; - FontCache::caches[fs] = std::make_unique(fs); + FontCache::Register(std::make_unique(fs)); } } };