From 97073e359178992aef166f23b0c73f2c7cbce0a1 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 15 Jul 2025 19:46:07 +0100 Subject: [PATCH 1/5] 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/5] 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/5] 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/5] 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)); } } }; From 9094cf6b229ff1b56231cbe120f87353522f1572 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Mon, 14 Jul 2025 17:48:53 +0100 Subject: [PATCH 5/5] Change: Support side-by-side fallback FontCaches instead of hierarchical. The text layouter system can now support using different fonts for different glyphs, including mixing scalable and sprite glyphs. --- src/console_cmds.cpp | 23 ++-- src/fontcache.cpp | 165 ++++++++++++++++++++-------- src/fontcache.h | 126 ++++++++++++++++----- src/fontcache/freetypefontcache.cpp | 33 ++---- src/fontcache/spritefontcache.cpp | 35 +++--- src/fontcache/spritefontcache.h | 5 +- src/fontcache/truetypefontcache.cpp | 4 - src/gfx.cpp | 15 +-- src/gfx_layout.cpp | 89 +++++++++------ src/gfx_layout.h | 31 +++--- src/gfx_layout_fallback.cpp | 23 ++-- src/gfx_layout_fallback.h | 19 ++++ src/gfx_layout_icu.cpp | 89 ++++++++++----- src/os/macosx/font_osx.cpp | 35 ++---- src/os/macosx/font_osx.h | 2 +- src/os/macosx/string_osx.cpp | 50 ++++----- 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 | 89 ++++++++++----- src/strings.cpp | 87 ++++++++------- src/strings_func.h | 40 ++++--- src/survey.cpp | 14 ++- src/tests/mock_fontcache.h | 5 +- src/textfile_gui.cpp | 15 +-- src/textfile_gui.h | 4 +- src/zoom_func.h | 10 ++ 28 files changed, 681 insertions(+), 424 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 919f22684b..f9a339b0e3 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2364,17 +2364,18 @@ static bool ConFont(std::span argv) SetFont(argfs, font, size); } - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { - FontCache *fc = FontCache::Get(fs); - FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); - /* Make sure all non sprite fonts are loaded. */ - if (!setting->font.empty() && !fc->HasParent()) { - FontCache::LoadFontCaches(fs); - fc = FontCache::Get(fs); - } - IConsolePrint(CC_DEFAULT, "{} font:", FontSizeToName(fs)); - IConsolePrint(CC_DEFAULT, "Currently active: \"{}\", size {}", fc->GetFontName(), fc->GetFontSize()); - IConsolePrint(CC_DEFAULT, "Requested: \"{}\", size {}", setting->font, setting->size); + IConsolePrint(CC_INFO, "Configured fonts:"); + for (uint i = 0; FontSize fs : FONTSIZES_ALL) { + const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); + IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\", size {}", i, FontSizeToName(fs), setting->font, setting->size); + ++i; + } + + IConsolePrint(CC_INFO, "Currently active fonts:"); + for (uint i = 0; const auto &fc : FontCache::Get()) { + if (fc == nullptr) continue; + IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\" size {}", i, FontSizeToName(fc->GetSize()), fc->GetFontName(), fc->GetFontSize()); + ++i; } return true; diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 7b44c32e24..754aee644b 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -8,6 +8,8 @@ /** @file fontcache.cpp Cache for characters from fonts. */ #include "stdafx.h" + +#include "core/string_consumer.hpp" #include "fontcache.h" #include "blitter/factory.hpp" #include "gfx_layout.h" @@ -17,6 +19,7 @@ #include "viewport_func.h" #include "window_func.h" #include "fileio_func.h" +#include "zoom_func.h" #include "safeguards.h" @@ -33,10 +36,10 @@ FontCacheSettings _fcsettings; * @param fonttype Font type requested. * @return FontCache of the font if loaded, or nullptr. */ -/* static */ std::unique_ptr FontProviderManager::LoadFont(FontSize fs, FontType fonttype) +/* static */ std::unique_ptr FontProviderManager::LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span os_handle) { for (auto &provider : FontProviderManager::GetProviders()) { - auto fc = provider->LoadFont(fs, fonttype); + auto fc = provider->LoadFont(fs, fonttype, search, font_name, os_handle); if (fc != nullptr) return fc; } @@ -52,10 +55,10 @@ FontCacheSettings _fcsettings; * @param callback The function to call to check for missing glyphs. * @return true if a font has been set, false otherwise. */ -/* static */ bool FontProviderManager::FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) +/* static */ bool FontProviderManager::FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, class MissingGlyphSearcher *callback) { for (auto &provider : FontProviderManager::GetProviders()) { - if (provider->FindFallbackFont(settings, language_isocode, callback)) { + if (provider->FindFallbackFont(language_isocode, fontsizes, callback)) { return true; } } @@ -67,21 +70,37 @@ int FontCache::GetDefaultFontHeight(FontSize fs) return FontCache::DEFAULT_FONT_HEIGHT[fs]; } -/** - * Get the font name of a given font size. - * @param fs The font size to look up. - * @return The font name. - */ -std::string FontCache::GetName(FontSize fs) +/* static */ void FontCache::UpdateCharacterHeight(FontSize fs) { - FontCache *fc = FontCache::Get(fs); - if (fc != nullptr) { - return fc->GetFontName(); + FontMetrics &metrics = FontCache::metrics[fs]; + const FontCache *defaultfc = GetDefaultFontCache(fs); + + metrics = {}; + + for (const auto &fc : FontCache::caches) { + if (fc == nullptr || fc->fs != fs) continue; + metrics.ascender = std::max(metrics.ascender, fc->ascender); + metrics.descender = std::min(metrics.descender, fc->descender); + } + + if (metrics.ascender == 0 && metrics.descender == 0) { + /* No font loaded yet? */ + metrics.ascender = ScaleGUITrad(DEFAULT_FONT_ASCENDER[fs]); + metrics.descender = ScaleGUITrad(DEFAULT_FONT_ASCENDER[fs] - DEFAULT_FONT_HEIGHT[fs]); + } + + if (defaultfc != nullptr) { + metrics.y_offset = (metrics.Height() - (defaultfc->ascender - defaultfc->descender)) / 2 + defaultfc->ascender; } else { - return "[NULL]"; + metrics.y_offset = ScaleGUITrad(DEFAULT_FONT_ASCENDER[fs]); } } +int FontCache::GetGlyphYOffset() +{ + const FontMetrics &metrics = FontCache::metrics[this->fs]; + return metrics.y_offset - this->ascender; +} /** * Get height of a character for a given font size. @@ -90,20 +109,22 @@ std::string FontCache::GetName(FontSize fs) */ int GetCharacterHeight(FontSize size) { - return FontCache::Get(size)->GetHeight(); + uint height = FontCache::GetCharacterHeight(size); + if (height == 0) height = ScaleGUITrad(FontCache::GetDefaultFontHeight(FS_MONO)); + return height; } - -/* static */ std::array, FS_END> FontCache::caches{}; +/* static */ FontCache::FontCaches FontCache::caches; +/* static */ std::array FontCache::metrics{}; +/* static */ std::array FontCache::default_font_index{}; /** * Initialise font caches with the base sprite font cache for all sizes. */ /* static */ void FontCache::InitializeFontCaches() { - for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { - if (FontCache::Get(fs) != nullptr) continue; - FontCache::Register(FontProviderManager::LoadFont(fs, FontType::Sprite)); + for (FontSize fs : FONTSIZES_ALL) { + UpdateCharacterHeight(fs); } } @@ -134,19 +155,13 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) if (!changed) return; if (fontsize != FS_MONO) { - /* Try to reload only the modified font. */ - FontCacheSettings backup = _fcsettings; - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { - if (fs == fontsize) continue; - FontCache *fc = FontCache::Get(fs); - GetFontCacheSubSetting(fs)->font = fc->HasParent() ? fc->GetFontName() : ""; - } + /* Check if fallback fonts are needed. */ CheckForMissingGlyphs(); - _fcsettings = std::move(backup); } else { FontCache::LoadFontCaches(fontsize); } + FontCache::UpdateCharacterHeight(fontsize); LoadStringWidthTable(fontsize); UpdateAllVirtCoords(); ReInitAllWindows(true); @@ -160,7 +175,7 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) */ static bool IsDefaultFont(const FontCacheSubSetting &setting) { - return setting.font.empty() && setting.os_handle == nullptr; + return setting.font.empty(); } /** @@ -197,7 +212,7 @@ static std::string GetDefaultTruetypeFont(FontSize fs) * @param fs Font size. * @return Full path of default font file. */ -static std::string GetDefaultTruetypeFontFile([[maybe_unused]] FontSize fs) +std::string GetDefaultTruetypeFontFile([[maybe_unused]] FontSize fs) { #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) /* Find font file. */ @@ -220,18 +235,35 @@ 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); + /* 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)); + 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, std::string_view name, std::span os_data) +{ + for (FontSize fs : fontsizes) { + GetFontCacheSubSetting(fs)->fallback_fonts.emplace_back(std::string{name}, std::vector{os_data.begin(), os_data.end()}); + } } /** @@ -240,17 +272,58 @@ std::string GetFontCacheFontName(FontSize fs) */ /* static */ void FontCache::LoadFontCaches(FontSizes fontsizes) { - FontCache::InitializeFontCaches(); + static constexpr std::string_view DEFAULT_FONT = "default"; for (FontSize fs : fontsizes) { Layouter::ResetFontCache(fs); + FontCache::default_font_index[fs] = INVALID_FONT_INDEX; + } - /* Unload everything except the sprite font cache. */ - while (FontCache::Get(fs)->HasParent()) { - FontCache::caches[fs] = std::move(FontCache::caches[fs]->parent); + /* Remove all existing FontCaches. */ + if (fontsizes == FONTSIZES_ALL) { + FontCache::caches.clear(); + } else { + for (auto it = std::begin(FontCache::caches); it != std::end(FontCache::caches); ++it) { + if (*it == nullptr) continue; + if (!fontsizes.Test((*it)->fs)) continue; + it->reset(); + } + } + + for (FontSize fs : fontsizes) { + const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); + + /* Add all detected fallback fonts. */ + for (auto it = setting->fallback_fonts.rbegin(); it != setting->fallback_fonts.rend(); ++it) { + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, /*fallback.dynamic ? "missing-fallback" : "language-fallback", */ false, it->name, it->os_handle)); } - FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType)); + /* Parse configured fonts, separated by ';' into a list. */ + std::vector fontnames; + StringConsumer consumer(setting->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 font as lowest priority if not manually specified. */ + if (std::ranges::find(fontnames, DEFAULT_FONT) == std::end(fontnames)) fontnames.push_back(DEFAULT_FONT); + + /* 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) { + /* Load the sprite font, even if it's not preferred. */ + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::Sprite, /*"default"*/ false, {}, {})); + if (!_fcsettings.prefer_sprite) { + /* Load the default truetype font if sprite not isn't preferred. */ + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, /*"default",*/ false, GetDefaultTruetypeFontFile(fs), {})); + } + } else { + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType, /*"configured",*/ true, std::string{*it}, {})); + } + } + + FontCache::UpdateCharacterHeight(fs); } } @@ -260,8 +333,14 @@ std::string GetFontCacheFontName(FontSize fs) */ /* static */ void FontCache::ClearFontCaches(FontSizes fontsizes) { + for (const auto &fc : FontCache::caches) { + if (fc == nullptr) continue; + if (!fontsizes.Test(fc->GetSize())) continue; + fc->ClearFontCache(); + } + for (FontSize fs : fontsizes) { - FontCache::Get(fs)->ClearFontCache(); + FontCache::UpdateCharacterHeight(fs); } } @@ -270,5 +349,5 @@ std::string GetFontCacheFontName(FontSize fs) */ /* static */ void FontCache::UninitializeFontCaches() { - std::ranges::generate(FontCache::caches, []() { return nullptr; }); + FontCache::caches.clear(); } diff --git a/src/fontcache.h b/src/fontcache.h index f918562b86..ae1b58c1a8 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -16,14 +16,33 @@ /** Glyphs are characters from a font. */ typedef uint32_t GlyphID; -static const GlyphID SPRITE_GLYPH = 1U << 30; +using FontIndex = uint8_t; + +static const FontIndex INVALID_FONT_INDEX = std::numeric_limits::max(); + +/** Default heights for the different sizes of fonts. */ +static constexpr int DEFAULT_FONT_HEIGHT[FS_END] = {10, 6, 18, 10}; +static constexpr int DEFAULT_FONT_ASCENDER[FS_END] = { 8, 5, 15, 8}; /** Font cache for basic fonts. */ class FontCache { protected: - static std::array, FS_END> caches; ///< All the font caches. - std::unique_ptrparent; ///< The parent of this font cache. + using FontCaches = std::vector>; + static FontCaches caches; + + struct FontMetrics { + int ascender; + int descender; + int y_offset; + + inline int Height() const { return this->ascender - this->descender; } + }; + + static std::array metrics; + static std::array default_font_index; + const FontSize fs; ///< The size of the font. + FontIndex font_index; ///< The index of the font. 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. @@ -46,12 +65,29 @@ public: static int GetDefaultFontHeight(FontSize fs); + static void AddFallback(FontSizes fontsizes, 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, std::string_view name, T &handle) + { + auto os_data = std::as_bytes(std::span(&handle, 1)); + FontCache::AddFallback(fontsizes, 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; } + /** * Get the height of the font. * @return The height of the font. @@ -102,10 +138,9 @@ public: /** * Map a character into a glyph. * @param key The character. - * @param fallback Allow fallback to the parent font. * @return The glyph ID used to draw the character. */ - virtual GlyphID MapCharToGlyph(char32_t key, bool fallback = true) = 0; + virtual GlyphID MapCharToGlyph(char32_t key) = 0; /** * Get the native OS font handle, if there is one. @@ -122,25 +157,57 @@ public: */ virtual std::string GetFontName() = 0; + virtual int GetGlyphYOffset(); + + /** + * Get span of all FontCaches. + * @return Span of all FontCaches. + */ + static inline std::span> Get() + { + return FontCache::caches; + } + /** * Get the font cache of a given font size. * @param fs The font size to look up. * @return The font cache. */ - static inline FontCache *Get(FontSize fs) + static inline FontCache *Get(FontIndex font_index) { - assert(fs < FS_END); - return FontCache::caches[fs].get(); + assert(font_index < FontCache::caches.size()); + return FontCache::caches[font_index].get(); } - static std::string GetName(FontSize fs); - - /** - * Check whether the font cache has a parent. - */ - inline bool HasParent() + static inline int GetCharacterHeight(FontSize fs) { - return this->parent != nullptr; + return FontCache::metrics[fs].Height(); + } + + static void UpdateCharacterHeight(FontSize fs); + + static inline FontIndex GetDefaultFontIndex(FontSize fs) + { + return FontCache::default_font_index[fs]; + } + + static inline class FontCache *GetDefaultFontCache(FontSize fs) + { + FontIndex index = FontCache::GetDefaultFontIndex(fs); + if (index != INVALID_FONT_INDEX) return FontCache::Get(index); + NOT_REACHED(); + } + + static inline FontIndex GetFontIndexForCharacter(FontSize fs, char32_t c) + { + for (auto it = std::rbegin(FontCache::caches); it != std::rend(FontCache::caches); ++it) { + FontCache *fc = it->get(); + if (fc == nullptr) continue; + if (fc->GetSize() != fs) continue; + if (fc->MapCharToGlyph(c) == 0) continue; + return std::distance(std::begin(FontCache::caches), std::next(it).base()); + } + return INVALID_FONT_INDEX; } /** @@ -152,28 +219,33 @@ public: /** Get the Sprite for a glyph */ inline const Sprite *GetGlyph(FontSize size, char32_t key) { - FontCache *fc = FontCache::Get(size); + FontIndex font_index = FontCache::GetFontIndexForCharacter(size, key); + FontCache *fc = font_index != INVALID_FONT_INDEX ? FontCache::Get(font_index) : FontCache::GetDefaultFontCache(size); + if (fc == nullptr) return nullptr; return fc->GetGlyph(fc->MapCharToGlyph(key)); } /** Get the width of a glyph */ inline uint GetGlyphWidth(FontSize size, char32_t key) { - FontCache *fc = FontCache::Get(size); + FontIndex font_index = FontCache::GetFontIndexForCharacter(size, key); + FontCache *fc = font_index != INVALID_FONT_INDEX ? FontCache::Get(font_index) : FontCache::GetDefaultFontCache(size); + if (fc == nullptr) return 0; return fc->GetGlyphWidth(fc->MapCharToGlyph(key)); } -inline bool GetDrawGlyphShadow(FontSize size) -{ - return FontCache::Get(size)->GetDrawGlyphShadow(); -} - /** Settings for a single font. */ struct FontCacheSubSetting { std::string font; ///< The name of the font, or path to the font. uint size; ///< The (requested) size of the font. - const void *os_handle = nullptr; ///< Optional native OS font info. Only valid during font search. + struct FontCacheFallback { + std::string name; + std::vector os_handle; + bool dynamic; + }; + + std::vector fallback_fonts; }; /** Settings for the four different fonts. */ @@ -229,14 +301,14 @@ public: ProviderManager::Unregister(*this); } - 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; + virtual std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span os_handle) = 0; + virtual bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, 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); + static std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span os_handle); + static bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, class MissingGlyphSearcher *callback); }; /* Implemented in spritefontcache.cpp */ diff --git a/src/fontcache/freetypefontcache.cpp b/src/fontcache/freetypefontcache.cpp index 1a687ff280..08a25375b2 100644 --- a/src/fontcache/freetypefontcache.cpp +++ b/src/fontcache/freetypefontcache.cpp @@ -40,7 +40,7 @@ public: FreeTypeFontCache(FontSize fs, FT_Face face, int pixels); ~FreeTypeFontCache(); void ClearFontCache() override; - GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override; + GlyphID MapCharToGlyph(char32_t key) override; std::string GetFontName() override { return fmt::format("{}, {}", face->family_name, face->style_name); } bool IsBuiltInFont() override { return false; } const void *GetOSHandle() override { return &face; } @@ -193,17 +193,11 @@ const Sprite *FreeTypeFontCache::InternalGetGlyph(GlyphID key, bool aa) } -GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) +GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key) { assert(IsPrintable(key)); - FT_UInt glyph = FT_Get_Char_Index(this->face, key); - - if (glyph == 0 && allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { - return this->parent->MapCharToGlyph(key); - } - - return glyph; + return FT_Get_Char_Index(this->face, key); } FT_Library _ft_library = nullptr; @@ -225,15 +219,10 @@ public: * format is 'font family name' or 'font family name, font style'. * @param fs The font size to load. */ - std::unique_ptr LoadFont(FontSize fs, FontType fonttype) override + std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span os_handle) 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"); @@ -247,7 +236,9 @@ public: /* If font is an absolute path to a ttf, try loading that first. */ int32_t index = 0; - if (settings->os_handle != nullptr) index = *static_cast(settings->os_handle); + if (os_handle.size() == sizeof(index)) { + index = *reinterpret_cast(os_handle.data()); + } FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face); if (error != FT_Err_Ok) { @@ -259,8 +250,8 @@ public: } #ifdef WITH_FONTCONFIG - /* Try loading based on font face name (OS-wide fonts). */ - if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face); + /* If allowed to search, try loading based on font face name (OS-wide fonts). */ + if (error != FT_Err_Ok && search) error = GetFontByFaceName(font, &face); #endif /* WITH_FONTCONFIG */ if (error != FT_Err_Ok) { @@ -271,10 +262,10 @@ public: return LoadFont(fs, face, font, GetFontCacheFontSize(fs)); } - bool FindFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) override + bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) override { #ifdef WITH_FONTCONFIG - if (FontConfigFindFallbackFont(settings, language_isocode, callback)) return true; + if (FontConfigFindFallbackFont(language_isocode, fontsizes, callback)) return true; #endif /* WITH_FONTCONFIG */ return false; @@ -308,7 +299,7 @@ private: if (error != FT_Err_Ok) { FT_Done_Face(face); - ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), error); + ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}", font_name, FontSizeToName(fs), error); return nullptr; } diff --git a/src/fontcache/spritefontcache.cpp b/src/fontcache/spritefontcache.cpp index e602abbba4..77e12ade6a 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -22,16 +22,6 @@ static const int ASCII_LETTERSTART = 32; ///< First printable ASCII letter. -/** - * Scale traditional pixel dimensions to font zoom level, for drawing sprite fonts. - * @param value Pixel amount at #ZOOM_BASE (traditional "normal" interface size). - * @return Pixel amount at _font_zoom (current interface size). - */ -static int ScaleFontTrad(int value) -{ - return UnScaleByZoom(value * ZOOM_BASE, _font_zoom); -} - static std::array, FS_END> _char_maps{}; ///< Glyph map for each font size. /** @@ -114,37 +104,42 @@ void InitializeUnicodeGlyphMap() */ SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs) { - this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); - this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2; + this->UpdateMetrics(); } void SpriteFontCache::ClearFontCache() { Layouter::ResetFontCache(this->fs); - this->height = ScaleGUITrad(FontCache::GetDefaultFontHeight(this->fs)); - this->ascender = (this->height - ScaleFontTrad(FontCache::GetDefaultFontHeight(this->fs))) / 2; + this->UpdateMetrics(); +} + +void SpriteFontCache::UpdateMetrics() +{ + this->height = ScaleGUITrad(DEFAULT_FONT_HEIGHT[this->fs]); + this->ascender = ScaleFontTrad(DEFAULT_FONT_ASCENDER[fs]); + this->descender = ScaleFontTrad(DEFAULT_FONT_ASCENDER[fs] - DEFAULT_FONT_HEIGHT[fs]); } const Sprite *SpriteFontCache::GetGlyph(GlyphID key) { - SpriteID sprite = static_cast(key & ~SPRITE_GLYPH); + SpriteID sprite = static_cast(key); if (sprite == 0) sprite = GetUnicodeGlyph(this->fs, '?'); return GetSprite(sprite, SpriteType::Font); } uint SpriteFontCache::GetGlyphWidth(GlyphID key) { - SpriteID sprite = static_cast(key & ~SPRITE_GLYPH); + SpriteID sprite = static_cast(key); if (sprite == 0) sprite = GetUnicodeGlyph(this->fs, '?'); return SpriteExists(sprite) ? GetSprite(sprite, SpriteType::Font)->width + ScaleFontTrad(this->fs != FS_NORMAL ? 1 : 0) : 0; } -GlyphID SpriteFontCache::MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback) +GlyphID SpriteFontCache::MapCharToGlyph(char32_t key) { assert(IsPrintable(key)); SpriteID sprite = GetUnicodeGlyph(this->fs, key); if (sprite == 0) return 0; - return SPRITE_GLYPH | sprite; + return static_cast(sprite); } bool SpriteFontCache::GetDrawGlyphShadow() @@ -156,14 +151,14 @@ class SpriteFontCacheFactory : public FontCacheFactory { public: SpriteFontCacheFactory() : FontCacheFactory("sprite", "Sprite font provider") {} - std::unique_ptr LoadFont(FontSize fs, FontType fonttype) override + std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool, const std::string &, std::span) override { if (fonttype != FontType::Sprite) return nullptr; return std::make_unique(fs); } - bool FindFallbackFont(struct FontCacheSettings *, const std::string &, class MissingGlyphSearcher *) override + bool FindFallbackFont(const std::string &, FontSizes, class MissingGlyphSearcher *) override { return false; } diff --git a/src/fontcache/spritefontcache.h b/src/fontcache/spritefontcache.h index 5403348cbe..b2839626bc 100644 --- a/src/fontcache/spritefontcache.h +++ b/src/fontcache/spritefontcache.h @@ -20,9 +20,12 @@ public: const Sprite *GetGlyph(GlyphID key) override; uint GetGlyphWidth(GlyphID key) override; bool GetDrawGlyphShadow() override; - GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override; + GlyphID MapCharToGlyph(char32_t key) override; std::string GetFontName() override { return "sprite"; } bool IsBuiltInFont() override { return true; } + +private: + void UpdateMetrics(); }; #endif /* SPRITEFONTCACHE_H */ diff --git a/src/fontcache/truetypefontcache.cpp b/src/fontcache/truetypefontcache.cpp index 890464e039..fe0a358c69 100644 --- a/src/fontcache/truetypefontcache.cpp +++ b/src/fontcache/truetypefontcache.cpp @@ -64,8 +64,6 @@ bool TrueTypeFontCache::GetDrawGlyphShadow() uint TrueTypeFontCache::GetGlyphWidth(GlyphID key) { - if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyphWidth(key); - GlyphEntry *glyph = this->GetGlyphPtr(key); if (glyph == nullptr || glyph->data == nullptr) { this->GetGlyph(key); @@ -77,8 +75,6 @@ uint TrueTypeFontCache::GetGlyphWidth(GlyphID key) const Sprite *TrueTypeFontCache::GetGlyph(GlyphID key) { - if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyph(key); - /* Check for the glyph in our cache */ GlyphEntry *glyph = this->GetGlyphPtr(key); if (glyph != nullptr && glyph->data != nullptr) return glyph->GetSprite(); diff --git a/src/gfx.cpp b/src/gfx.cpp index d04ed6733c..4d3bb478a0 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -534,7 +534,7 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, * another size would be chosen it won't have truncated too little for * the truncation dots. */ - truncation_layout.emplace(GetEllipsis(), INT32_MAX, line.GetVisualRun(0).GetFont()->fc->GetSize()); + truncation_layout.emplace(GetEllipsis(), INT32_MAX, line.GetVisualRun(0).GetFont()->GetFontCache().GetSize()); truncation_width = truncation_layout->GetBounds().width; /* Is there enough space even for an ellipsis? */ @@ -591,12 +591,12 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, const auto &positions = run.GetPositions(); const Font *f = run.GetFont(); - FontCache *fc = f->fc; + FontCache &fc = f->GetFontCache(); TextColour colour = f->colour; if (colour == TC_INVALID || HasFlag(default_colour, TC_FORCED)) colour = default_colour; bool colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK; SetColourRemap(do_shadow ? TC_BLACK : colour); // the last run also sets the colour for the truncation dots - if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue; + if (do_shadow && (!fc.GetDrawGlyphShadow() || !colour_has_shadow)) continue; for (int i = 0; i < run.GetGlyphCount(); i++) { GlyphID glyph = glyphs[i]; @@ -610,13 +610,10 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, /* Truncated away. */ if (truncation && (begin_x < min_x || end_x > max_x)) continue; + /* Outside the clipping area. */ + if (begin_x > dpi_right || end_x < dpi_left) continue; - const Sprite *sprite = fc->GetGlyph(glyph); - /* Check clipping (the "+ 1" is for the shadow). */ - if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue; - - if (do_shadow && (glyph & SPRITE_GLYPH) != 0) continue; - + const Sprite *sprite = fc.GetGlyph(glyph); GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap); } } diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp index 8c43f7b2c0..4ca6394a23 100644 --- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -38,19 +38,7 @@ 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); -} +std::unordered_map Layouter::fonts; /** * Helper for getting a ParagraphLayouter of the given type. @@ -71,7 +59,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s const typename T::CharType *buffer_last = buff_begin + str.size() + 1; typename T::CharType *buff = buff_begin; FontMap &font_mapping = line.runs; - Font *f = Layouter::GetFont(state.fontsize, state.cur_colour); + Font *f = Layouter::GetFont(state.font_index, state.cur_colour); font_mapping.clear(); @@ -80,7 +68,10 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s * whenever the font changes, and convert the wide characters into a format * usable by ParagraphLayout. */ - for (char32_t c : Utf8View(str)) { + Utf8View view(str); + for (auto it = view.begin(); it != view.end(); /* nothing */) { + auto cur = it; + uint32_t c = *it++; if (c == '\0' || c == '\n') { /* Caller should already have filtered out these characters. */ NOT_REACHED(); @@ -95,19 +86,40 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s } else { /* Filter out non printable characters */ if (!IsPrintable(c)) continue; - /* Filter out text direction characters that shouldn't be drawn, and - * will not be handled in the fallback case because they are mostly - * needed for RTL languages which need more proper shaping support. */ - if (!T::SUPPORTS_RTL && IsTextDirectionChar(c)) continue; - buff += T::AppendToBuffer(buff, buffer_last, c); - if (buff >= buffer_last) break; - continue; + + if (IsTextDirectionChar(c)) { + /* Filter out text direction characters that shouldn't be drawn, and + * will not be handled in the fallback case because they are mostly + * needed for RTL languages which need more proper shaping support. */ + if constexpr (!T::SUPPORTS_RTL) continue; + + buff += T::AppendToBuffer(buff, buffer_last, c); + if (buff >= buffer_last) break; + continue; + } + + FontIndex font_index = FontCache::GetFontIndexForCharacter(state.fontsize, c); + + if (font_index == INVALID_FONT_INDEX) { + font_index = FontCache::GetDefaultFontIndex(state.fontsize); + } + + if (state.font_index == font_index) { + buff += T::AppendToBuffer(buff, buffer_last, c); + if (buff >= buffer_last) break; + continue; + } + + /* This character goes in the next run so don't advance. */ + state.font_index = font_index; + + it = cur; } - if (font_mapping.empty() || font_mapping.back().first != buff - buff_begin) { + if (buff - buff_begin > 0 && (font_mapping.empty() || font_mapping.back().first != buff - buff_begin)) { font_mapping.emplace_back(buff - buff_begin, f); } - f = Layouter::GetFont(state.fontsize, state.cur_colour); + f = Layouter::GetFont(state.font_index, state.cur_colour); } /* Better safe than sorry. */ @@ -116,6 +128,14 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s if (font_mapping.empty() || font_mapping.back().first != buff - buff_begin) { font_mapping.emplace_back(buff - buff_begin, f); } + + if constexpr (!std::is_same_v) { + /* Don't layout if all runs use a built-in font and we're not using the fallback layouter. */ + if (std::ranges::all_of(font_mapping, [](const auto &i) { return i.second->GetFontCache().IsBuiltInFont(); })) { + return; + } + } + line.layout = T::GetParagraphLayout(buff_begin, buff, font_mapping); line.state_after = state; } @@ -128,7 +148,7 @@ static inline void GetLayouter(Layouter::LineCacheItem &line, std::string_view s */ Layouter::Layouter(std::string_view str, int maxw, FontSize fontsize) : string(str) { - FontState state(TC_INVALID, fontsize); + FontState state(TC_INVALID, fontsize, FontCache::GetDefaultFontIndex(fontsize)); while (true) { auto line_length = str.find_first_of('\n'); @@ -341,13 +361,17 @@ ptrdiff_t Layouter::GetCharAtPosition(int x, size_t line_index) const /** * Get a static font instance. */ -Font *Layouter::GetFont(FontSize size, TextColour colour) +Font *Layouter::GetFont(FontIndex font_index, TextColour colour) { - FontColourMap::iterator it = fonts[size].find(colour); - if (it != fonts[size].end()) return it->second.get(); + if (font_index == INVALID_FONT_INDEX) return nullptr; + assert(font_index < FontCache::Get().size()); - fonts[size][colour] = std::make_unique(size, colour); - return fonts[size][colour].get(); + FontColourMap &fcm = Layouter::fonts[font_index]; + auto it = fcm.find(colour); + if (it != fcm.end()) return it->second.get(); + + fcm[colour] = std::make_unique(font_index, colour); + return fcm[colour].get(); } /** @@ -362,11 +386,10 @@ void Layouter::Initialize() /** * Reset cached font information. - * @param size Font size to reset. */ -void Layouter::ResetFontCache(FontSize size) +void Layouter::ResetFontCache([[maybe_unused]] FontSize size) { - fonts[size].clear(); + Layouter::fonts.clear(); /* We must reset the linecache since it references the just freed fonts */ ResetLineCache(); diff --git a/src/gfx_layout.h b/src/gfx_layout.h index b37741a59b..2a28f8364f 100644 --- a/src/gfx_layout.h +++ b/src/gfx_layout.h @@ -12,8 +12,6 @@ #include "misc/lrucache.hpp" #include "fontcache.h" -#include "gfx_func.h" -#include "core/math_func.hpp" #include @@ -22,12 +20,13 @@ * of the same text, e.g. on line breaks. */ struct FontState { - FontSize fontsize; ///< Current font size. - TextColour cur_colour; ///< Current text colour. + FontSize fontsize; ///< Current font size. + FontIndex font_index; ///< Current font index. + TextColour cur_colour; ///< Current text colour. std::vector colour_stack; ///< Stack of colours to assist with colour switching. - FontState() : fontsize(FS_END), cur_colour(TC_INVALID) {} - FontState(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour) {} + FontState() : fontsize(FS_END), font_index(INVALID_FONT_INDEX), cur_colour(TC_INVALID) {} + FontState(TextColour colour, FontSize fontsize, FontIndex font_index) : fontsize(fontsize), font_index(font_index), cur_colour(colour) {} auto operator<=>(const FontState &) const = default; @@ -67,6 +66,7 @@ struct FontState { inline void SetFontSize(FontSize f) { this->fontsize = f; + this->font_index = FontCache::GetDefaultFontIndex(this->fontsize); } }; @@ -85,9 +85,10 @@ template <> struct std::hash { std::size_t operator()(const FontState &state) const noexcept { size_t h1 = std::hash{}(state.fontsize); - size_t h2 = std::hash{}(state.cur_colour); - size_t h3 = std::hash>{}(state.colour_stack); - return h1 ^ (h2 << 1) ^ (h3 << 2); + size_t h2 = std::hash{}(state.font_index); + size_t h3 = std::hash{}(state.cur_colour); + size_t h4 = std::hash>{}(state.colour_stack); + return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3); } }; @@ -96,10 +97,12 @@ template <> struct std::hash { */ class Font { public: - FontCache *fc; ///< The font we are using. + FontIndex font_index; ///< The font we are using. TextColour colour; ///< The colour this font has to be. - Font(FontSize size, TextColour colour); + constexpr Font(FontIndex font_index, TextColour colour) : font_index(font_index), colour(colour) {} + + inline FontCache &GetFontCache() const { return *FontCache::Get(this->font_index); } }; /** Mapping from index to font. The pointer is owned by FontColourMap. */ @@ -206,9 +209,9 @@ private: static LineCacheItem &GetCachedParagraphLayout(std::string_view str, const FontState &state); using FontColourMap = std::map>; - static FontColourMap fonts[FS_END]; + static std::unordered_map fonts; public: - static Font *GetFont(FontSize size, TextColour colour); + static Font *GetFont(FontIndex font_index, TextColour colour); Layouter(std::string_view str, int maxw = INT32_MAX, FontSize fontsize = FS_NORMAL); Dimension GetBounds(); @@ -216,7 +219,7 @@ public: ptrdiff_t GetCharAtPosition(int x, size_t line_index) const; static void Initialize(); - static void ResetFontCache(FontSize size); + static void ResetFontCache(FontSize fs); static void ResetLineCache(); }; diff --git a/src/gfx_layout_fallback.cpp b/src/gfx_layout_fallback.cpp index d2a43fb602..566518a040 100644 --- a/src/gfx_layout_fallback.cpp +++ b/src/gfx_layout_fallback.cpp @@ -10,6 +10,7 @@ #include "stdafx.h" #include "gfx_layout_fallback.h" +#include "gfx_func.h" #include "string_func.h" #include "zoom_func.h" @@ -51,7 +52,7 @@ public: int GetGlyphCount() const override { return static_cast(this->glyphs.size()); } std::span GetGlyphs() const override { return this->glyphs; } std::span GetPositions() const override { return this->positions; } - int GetLeading() const override { return this->GetFont()->fc->GetHeight(); } + int GetLeading() const override { return GetCharacterHeight(this->GetFont()->GetFontCache().GetSize()); } std::span GetGlyphToCharMap() const override { return this->glyph_to_char; } }; @@ -112,23 +113,17 @@ public: 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(font->fc->MapCharToGlyph(chars[i])); - int x_advance = font->fc->GetGlyphWidth(glyph_id); - if (isbuiltin) { - this->positions.emplace_back(advance, advance + x_advance - 1, font->fc->GetAscender()); // Apply sprite font's ascender. - } else if (chars[i] >= SCC_SPRITE_START && chars[i] <= SCC_SPRITE_END) { - this->positions.emplace_back(advance, advance + x_advance - 1, (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2); // Align sprite font to centre - } else { - this->positions.emplace_back(advance, advance + x_advance - 1, 0); // No ascender adjustment. - } + const GlyphID &glyph_id = this->glyphs.emplace_back(fc.MapCharToGlyph(chars[i])); + int x_advance = fc.GetGlyphWidth(glyph_id); + this->positions.emplace_back(advance, advance + x_advance - 1, y_offset); // No ascender adjustment. advance += x_advance; this->glyph_to_char.push_back(char_offset + i); } @@ -233,7 +228,8 @@ std::unique_ptr FallbackParagraphLayout::NextLine assert(iter != this->runs.end()); } - const FontCache *fc = iter->second->fc; + const FontCache *fc = &iter->second->GetFontCache(); + assert(fc != nullptr); const char32_t *next_run = this->buffer_begin + iter->first; const char32_t *begin = this->buffer; @@ -251,6 +247,7 @@ std::unique_ptr FallbackParagraphLayout::NextLine if (this->buffer == next_run) { int w = l->GetWidth(); + assert(iter->second->font_index != INVALID_FONT_INDEX); l->emplace_back(iter->second, begin, this->buffer - begin, begin - this->buffer_begin, w); ++iter; assert(iter != this->runs.end()); diff --git a/src/gfx_layout_fallback.h b/src/gfx_layout_fallback.h index bd93ac0ab7..ad1858062c 100644 --- a/src/gfx_layout_fallback.h +++ b/src/gfx_layout_fallback.h @@ -26,5 +26,24 @@ public: static size_t AppendToBuffer(char32_t *buff, const char32_t *buffer_last, char32_t c); }; +/** + * Swap paired brackets for fallback RTL layouting. + * @param c Character to swap. + * @return Swapped character, or original character if it is not a paired bracket. + */ +inline char32_t SwapRtlPairedCharacters(char32_t c) +{ + /* There are many more paired brackets, but for fallback purposes we only handle ASCII brackets. */ + /* https://www.unicode.org/Public/UCD/latest/ucd/BidiBrackets.txt */ + switch (c) { + case U'(': return U')'; + case U')': return U'('; + case U'[': return U']'; + case U']': return U'['; + case U'{': return U'}'; + case U'}': return U'{'; + default: return c; + } +} #endif /* GFX_LAYOUT_FALLBACK_H */ diff --git a/src/gfx_layout_icu.cpp b/src/gfx_layout_icu.cpp index d7ee97e7ce..971cbfd1c8 100644 --- a/src/gfx_layout_icu.cpp +++ b/src/gfx_layout_icu.cpp @@ -11,6 +11,9 @@ #include "gfx_layout_icu.h" #include "debug.h" +#include "gfx_func.h" +#include "gfx_layout_fallback.h" +#include "string_func.h" #include "strings_func.h" #include "language.h" #include "table/control_codes.h" @@ -51,6 +54,7 @@ public: 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); }; /** @@ -76,7 +80,7 @@ public: std::span GetGlyphToCharMap() const override { return this->glyph_to_char; } const Font *GetFont() const override { return this->font; } - int GetLeading() const override { return this->font->fc->GetHeight(); } + int GetLeading() const override { return GetCharacterHeight(this->font->GetFontCache().GetSize()); } int GetGlyphCount() const override { return this->glyphs.size(); } int GetAdvance() const { return this->total_advance; } }; @@ -141,6 +145,47 @@ 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) { + 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); + if (Utf16IsLeadSurrogate(*(buff + i))) ++i; + } + + /* Reverse the sequence if this run is RTL. */ + if (this->level & 1) { + std::reverse(std::begin(this->glyphs), std::end(this->glyphs)); + std::reverse(std::begin(this->glyph_to_char), std::end(this->glyph_to_char)); + } + + this->positions.reserve(this->glyphs.size()); + + /* Set positions of each glyph. */ + int y_offset = fc.GetGlyphYOffset(); + int advance = 0; + for (const GlyphID glyph : this->glyphs) { + int x_advance = fc.GetGlyphWidth(glyph); + this->positions.emplace_back(advance, advance + x_advance - 1, y_offset); + this->advance.push_back(x_advance); + advance += x_advance; + } + this->total_advance = advance; +} + /** * Shape a single run. * @@ -149,7 +194,20 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) : */ void ICURun::Shape(UChar *buff, size_t buff_length) { - auto hbfont = hb_ft_font_create_referenced(*(static_cast(font->fc->GetOSHandle()))); + FontCache &fc = this->font->GetFontCache(); + + /* Make sure any former run is lost. */ + this->glyphs.clear(); + this->glyph_to_char.clear(); + this->positions.clear(); + this->advance.clear(); + + if (fc.IsBuiltInFont()) { + this->FallbackShape(buff); + return; + } + + auto hbfont = hb_ft_font_create_referenced(*(static_cast(fc.GetOSHandle()))); /* Match the flags with how we render the glyphs. */ hb_ft_font_set_load_flags(hbfont, GetFontAAState() ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO); @@ -170,12 +228,6 @@ void ICURun::Shape(UChar *buff, size_t buff_length) auto glyph_info = hb_buffer_get_glyph_infos(hbbuf, &glyph_count); auto glyph_pos = hb_buffer_get_glyph_positions(hbbuf, &glyph_count); - /* Make sure any former run is lost. */ - this->glyphs.clear(); - this->glyph_to_char.clear(); - this->positions.clear(); - this->advance.clear(); - /* Reserve space, as we already know the size. */ this->glyphs.reserve(glyph_count); this->glyph_to_char.reserve(glyph_count); @@ -183,20 +235,12 @@ void ICURun::Shape(UChar *buff, size_t buff_length) this->advance.reserve(glyph_count); /* Prepare the glyphs/position. ICUVisualRun will give the position an offset if needed. */ + int y_offset = fc.GetGlyphYOffset(); hb_position_t advance = 0; for (unsigned int i = 0; i < glyph_count; i++) { - int x_advance; - - if (buff[glyph_info[i].cluster] >= SCC_SPRITE_START && buff[glyph_info[i].cluster] <= SCC_SPRITE_END && glyph_info[i].codepoint == 0) { - auto glyph = this->font->fc->MapCharToGlyph(buff[glyph_info[i].cluster]); - x_advance = this->font->fc->GetGlyphWidth(glyph); - this->glyphs.push_back(glyph); - this->positions.emplace_back(advance, advance + x_advance - 1, (this->font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(this->font->fc->GetSize()))) / 2); // Align sprite font to centre - } else { - x_advance = glyph_pos[i].x_advance / FONT_SCALE; - this->glyphs.push_back(glyph_info[i].codepoint); - this->positions.emplace_back(glyph_pos[i].x_offset / FONT_SCALE + advance, glyph_pos[i].x_offset / FONT_SCALE + advance + x_advance - 1, glyph_pos[i].y_offset / FONT_SCALE); - } + int x_advance = glyph_pos[i].x_advance / FONT_SCALE; + this->glyphs.push_back(glyph_info[i].codepoint); + this->positions.emplace_back(glyph_pos[i].x_offset / FONT_SCALE + advance, glyph_pos[i].x_offset / FONT_SCALE + advance + x_advance - 1, glyph_pos[i].y_offset / FONT_SCALE + y_offset); this->glyph_to_char.push_back(glyph_info[i].cluster); this->advance.push_back(x_advance); @@ -359,11 +403,6 @@ std::vector ItemizeStyle(std::vector &runs_current, FontMap &fon /* Can't layout an empty string. */ if (length == 0) return nullptr; - /* Can't layout our in-built sprite fonts. */ - for (auto const &[position, font] : font_mapping) { - if (font->fc->IsBuiltInFont()) return nullptr; - } - auto runs = ItemizeBidi(buff, length); runs = ItemizeScript(buff, length, runs); runs = ItemizeStyle(runs, font_mapping); diff --git a/src/os/macosx/font_osx.cpp b/src/os/macosx/font_osx.cpp index 894eb99ec7..2796728842 100644 --- a/src/os/macosx/font_osx.cpp +++ b/src/os/macosx/font_osx.cpp @@ -94,7 +94,7 @@ void CoreTextFontCache::SetFontSize(int pixels) Debug(fontcache, 2, "Loaded font '{}' with size {}", this->font_name, pixels); } -GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) +GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key) { assert(IsPrintable(key)); @@ -112,10 +112,6 @@ GlyphID CoreTextFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) return glyph[0]; } - if (allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { - return this->parent->MapCharToGlyph(key); - } - return 0; } @@ -211,29 +207,19 @@ public: * fallback search, use it. Otherwise, try to resolve it by font name. * @param fs The font size to load. */ - std::unique_ptr LoadFont(FontSize fs, FontType fonttype) override + std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span) override { if (fonttype != FontType::TrueType) return nullptr; - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); - - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return nullptr; - CFAutoRelease font_ref; - if (settings->os_handle != nullptr) { - font_ref.reset(static_cast(const_cast(settings->os_handle))); - CFRetain(font_ref.get()); // Increase ref count to match a later release. - } - - if (!font_ref && MacOSVersionIsAtLeast(10, 6, 0)) { + if (MacOSVersionIsAtLeast(10, 6, 0)) { /* Might be a font file name, try load it. */ font_ref.reset(LoadFontFromFile(font)); if (!font_ref) ShowInfo("Unable to load file '{}' for {} font, using default OS font selection instead", font, FontSizeToName(fs)); } - if (!font_ref) { + if (!font_ref && search) { CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, font.c_str(), kCFStringEncodingUTF8)); /* Simply creating the font using CTFontCreateWithNameAndSize will *always* return @@ -259,7 +245,7 @@ public: return std::make_unique(fs, std::move(font_ref), GetFontCacheFontSize(fs)); } - bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) override + bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, 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. */ @@ -305,7 +291,7 @@ public: /* Skip bold fonts (especially Arial Bold, which looks worse than regular Arial). */ if (symbolic_traits & kCTFontBoldTrait) continue; /* Select monospaced fonts if asked for. */ - if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != callback->Monospace()) continue; + if (((symbolic_traits & kCTFontMonoSpaceTrait) == kCTFontMonoSpaceTrait) != fontsizes.Test(FS_MONO)) continue; /* Get font name. */ char buffer[128]; @@ -323,8 +309,9 @@ public: if (name.starts_with(".") || name.starts_with("LastResort")) continue; /* Save result. */ - callback->SetFontNames(settings, name); - if (!callback->FindMissingGlyphs()) { + FontCache::AddFallback(fontsizes, name); + + if (callback->FindMissingGlyphs().None()) { Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name); result = true; break; @@ -335,8 +322,8 @@ public: if (!result) { /* For some OS versions, the font 'Arial Unicode MS' does not report all languages it * supports. If we didn't find any other font, just try it, maybe we get lucky. */ - callback->SetFontNames(settings, "Arial Unicode MS"); - result = !callback->FindMissingGlyphs(); + FontCache::AddFallback(fontsizes, "Arial Unicode MS"); + result = callback->FindMissingGlyphs().None(); } callback->FindMissingGlyphs(); diff --git a/src/os/macosx/font_osx.h b/src/os/macosx/font_osx.h index 2ba91d485d..48de29d62e 100644 --- a/src/os/macosx/font_osx.h +++ b/src/os/macosx/font_osx.h @@ -28,7 +28,7 @@ public: ~CoreTextFontCache() {} void ClearFontCache() override; - GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override; + GlyphID MapCharToGlyph(char32_t key) override; std::string GetFontName() override { return font_name; } bool IsBuiltInFont() override { return false; } const void *GetOSHandle() override { return font.get(); } diff --git a/src/os/macosx/string_osx.cpp b/src/os/macosx/string_osx.cpp index 25cf921af7..be22de8355 100644 --- a/src/os/macosx/string_osx.cpp +++ b/src/os/macosx/string_osx.cpp @@ -9,6 +9,7 @@ #include "../../stdafx.h" #include "string_osx.h" +#include "../../gfx_func.h" #include "../../string_func.h" #include "../../strings_func.h" #include "../../core/utf8.hpp" @@ -52,7 +53,7 @@ extern "C" { /** Cached current locale. */ static CFAutoRelease _osx_locale; /** CoreText cache for font information, cleared when OTTD changes fonts. */ -static CFAutoRelease _font_cache[FS_END]; +static std::unordered_map> _font_cache; /** @@ -88,7 +89,7 @@ public: std::span GetGlyphToCharMap() const override { return this->glyph_to_char; } const Font *GetFont() const override { return this->font; } - int GetLeading() const override { return this->font->fc->GetHeight(); } + int GetLeading() const override { return GetCharacterHeight(this->font->GetFontCache().GetSize()); } int GetGlyphCount() const override { return (int)this->glyphs.size(); } int GetAdvance() const { return this->total_advance; } }; @@ -137,17 +138,17 @@ public: /** Get the width of an encoded sprite font character. */ -static CGFloat SpriteFontGetWidth(void *ref_con) +static CGFloat CustomFontGetWidth(void *ref_con) { - FontSize fs = (FontSize)((size_t)ref_con >> 24); - char32_t c = (char32_t)((size_t)ref_con & 0xFFFFFF); + FontIndex fi = static_cast(reinterpret_cast(ref_con) >> 24); + char32_t c = static_cast(reinterpret_cast(ref_con) & 0xFFFFFF); - return GetGlyphWidth(fs, c); + return FontCache::Get(fi)->GetGlyphWidth(c); } static const CTRunDelegateCallbacks _sprite_font_callback = { kCTRunDelegateCurrentVersion, nullptr, nullptr, nullptr, - &SpriteFontGetWidth + &CustomFontGetWidth }; /* static */ std::unique_ptr CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &font_mapping) @@ -158,10 +159,7 @@ 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; - } + FontCache &fc = this->font->GetFontCache(); /* Make attributed string with embedded font information. */ CFAutoRelease str(CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)); @@ -179,14 +177,14 @@ static const CTRunDelegateCallbacks _sprite_font_callback = { for (const auto &[position, font] : font_mapping) { if (position - last == 0) continue; - CTFontRef font_handle = static_cast(font->fc->GetOSHandle()); + CTFontRef font_handle = static_cast(fc.GetOSHandle()); if (font_handle == nullptr) { - if (!_font_cache[font->fc->GetSize()]) { + if (!_font_cache[fc.GetIndex()]) { /* Cache font information. */ - CFAutoRelease font_name(CFStringCreateWithCString(kCFAllocatorDefault, font->fc->GetFontName().c_str(), kCFStringEncodingUTF8)); - _font_cache[font->fc->GetSize()].reset(CTFontCreateWithName(font_name.get(), font->fc->GetFontSize(), nullptr)); + CFAutoRelease font_name(CFStringCreateWithCString(kCFAllocatorDefault, fc.GetFontName().c_str(), kCFStringEncodingUTF8)); + _font_cache[fc.GetIndex()].reset(CTFontCreateWithName(font_name.get(), fc.GetFontSize(), nullptr)); } - font_handle = _font_cache[font->fc->GetSize()].get(); + font_handle = _font_cache[fc.GetIndex()].get(); } CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle); @@ -194,10 +192,10 @@ static const CTRunDelegateCallbacks _sprite_font_callback = { CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTForegroundColorAttributeName, color); CGColorRelease(color); - /* Install a size callback for our special private-use sprite glyphs in case the font does not provide them. */ - for (ssize_t c = last; c < position; c++) { - if (buff[c] >= SCC_SPRITE_START && buff[c] <= SCC_SPRITE_END && font->fc->MapCharToGlyph(buff[c], false) == 0) { - CFAutoRelease del(CTRunDelegateCreate(&_sprite_font_callback, (void *)(size_t)(buff[c] | (font->fc->GetSize() << 24)))); + /* Install a size callback for our custom fonts. */ + if (fc.IsBuiltInFont()) { + for (ssize_t c = last; c < i.first; c++) { + CFAutoRelease del(CTRunDelegateCreate(&_custom_font_callback, static_cast(reinterpret_cast(buff[c] | (fc.GetIndex() << 24))))); /* According to the official documentation, if a run delegate is used, the char should always be 0xFFFC. */ CFAttributedStringReplaceString(str.get(), CFRangeMake(c, 1), replacment_str.get()); CFAttributedStringSetAttribute(str.get(), CFRangeMake(c, 1), kCTRunDelegateAttributeName, del.get()); @@ -247,19 +245,15 @@ CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font CTRunGetAdvances(run, CFRangeMake(0, 0), advs); 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. */ CGGlyph gl[this->glyphs.size()]; CTRunGetGlyphs(run, CFRangeMake(0, 0), gl); for (size_t i = 0; i < this->glyphs.size(); i++) { - if (buff[this->glyph_to_char[i]] >= SCC_SPRITE_START && buff[this->glyph_to_char[i]] <= SCC_SPRITE_END && (gl[i] == 0 || gl[i] == 3)) { - /* A glyph of 0 indidicates not found, while apparently 3 is what char 0xFFFC maps to. */ - this->glyphs[i] = font->fc->MapCharToGlyph(buff[this->glyph_to_char[i]]); - this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, (font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(font->fc->GetSize()))) / 2); // Align sprite font to centre - } else { - this->glyphs[i] = gl[i]; - this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y); - } + this->glyphs[i] = gl[i]; + this->positions.emplace_back(pts[i].x, pts[i].x + advs[i].width - 1, pts[i].y + y_offset); } this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nullptr, nullptr, nullptr)); } diff --git a/src/os/unix/font_unix.cpp b/src/os/unix/font_unix.cpp index dd8f2f1d2a..204828fe18 100644 --- a/src/os/unix/font_unix.cpp +++ b/src/os/unix/font_unix.cpp @@ -120,10 +120,14 @@ 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; } -bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) +bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) { bool ret = false; @@ -132,22 +136,25 @@ bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string & auto fc_instance = AutoRelease(FcConfigReference(nullptr)); assert(fc_instance != nullptr); + /* Get set of required characters. XXX Do we know what font size we want here? */ + auto chars = callback->GetRequiredGlyphs(fontsizes); + /* Fontconfig doesn't handle full language isocodes, only the part * before the _ of e.g. en_GB is used, so "remove" everything after * the _. */ - std::string lang = fmt::format(":lang={}", language_isocode.substr(0, language_isocode.find('_'))); + std::string lang = language_isocode.empty() ? "" : fmt::format(":lang={}", language_isocode.substr(0, language_isocode.find('_'))); /* First create a pattern to match the wanted language. */ auto pat = AutoRelease(FcNameParse(ToFcString(lang))); /* We only want to know these attributes. */ - auto os = AutoRelease(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, nullptr)); + auto os = AutoRelease(FcObjectSetBuild(FC_FILE, FC_INDEX, FC_SPACING, FC_SLANT, FC_WEIGHT, FC_CHARSET, nullptr)); /* Get the list of filenames matching the wanted language. */ auto fs = AutoRelease(FcFontList(nullptr, pat.get(), os.get())); if (fs == nullptr) return ret; int best_weight = -1; - const char *best_font = nullptr; + std::string best_font; int best_index = 0; for (FcPattern *font : std::span(fs->fonts, fs->nfont)) { @@ -158,7 +165,7 @@ bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string & /* Get a font with the right spacing .*/ int value = 0; FcPatternGetInteger(font, FC_SPACING, 0, &value); - if (callback->Monospace() != (value == FC_MONO) && value != FC_DUAL) continue; + if (fontsizes.Test(FS_MONO) != (value == FC_MONO) && value != FC_DUAL) continue; /* Do not use those that explicitly say they're slanted. */ FcPatternGetInteger(font, FC_SLANT, 0, &value); @@ -168,26 +175,32 @@ bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string & FcPatternGetInteger(font, FC_WEIGHT, 0, &value); if (value <= 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, 0, "Font \"{}\" misses {} glyphs", (char *)file, chars.size() - matching_chars); + continue; + } + /* Possible match based on attributes, get index. */ int32_t index; res = FcPatternGetInteger(font, FC_INDEX, 0, &index); if (res != FcResultMatch) continue; - callback->SetFontNames(settings, FromFcString(file), &index); - - bool missing = callback->FindMissingGlyphs(); - Debug(fontcache, 1, "Font \"{}\" misses{} glyphs", FromFcString(file), missing ? "" : " no"); - - if (!missing) { - best_weight = value; - best_font = FromFcString(file); - best_index = index; - } + best_weight = value; + best_font = FromFcString(file); + best_index = index; } - if (best_font == nullptr) return false; + if (best_font.empty()) return false; + + FontCache::AddFallbackWithHandle(fontsizes, best_font, best_index); + FontCache::LoadFontCaches(fontsizes); - callback->SetFontNames(settings, best_font, &best_index); - FontCache::LoadFontCaches(callback->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); return true; } diff --git a/src/os/unix/font_unix.h b/src/os/unix/font_unix.h index bd1eea4416..37392d800d 100644 --- a/src/os/unix/font_unix.h +++ b/src/os/unix/font_unix.h @@ -19,7 +19,7 @@ FT_Error GetFontByFaceName(std::string_view font_name, FT_Face *face); -bool FontConfigFindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); +bool FontConfigFindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback); #endif /* WITH_FONTCONFIG */ diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index f292a6f805..0f33005eee 100644 --- a/src/os/windows/font_win32.cpp +++ b/src/os/windows/font_win32.cpp @@ -31,8 +31,8 @@ #include "../../safeguards.h" struct EFCParam { - FontCacheSettings *settings; - LOCALESIGNATURE locale; + LOCALESIGNATURE locale; + FontSizes fontsizes; MissingGlyphSearcher *callback; std::vector fonts; @@ -59,7 +59,7 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT /* Don't use SYMBOL fonts */ if (logfont->elfLogFont.lfCharSet == SYMBOL_CHARSET) return 1; /* Use monospaced fonts when asked for it. */ - if (info->callback->Monospace() && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1; + if (info->fontsizes.Test(FS_MONO) && (logfont->elfLogFont.lfPitchAndFamily & (FF_MODERN | FIXED_PITCH)) != (FF_MODERN | FIXED_PITCH)) return 1; /* The font has to have at least one of the supported locales to be usable. */ auto check_bitfields = [&]() { @@ -78,8 +78,8 @@ static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *logfont, const NEWTEXT char font_name[MAX_PATH]; convert_from_fs(logfont->elfFullName, font_name); - info->callback->SetFontNames(info->settings, font_name, &logfont->elfLogFont); - if (info->callback->FindMissingGlyphs()) return 1; + FontCache::AddFallbackWithHandle(info->fontsizes, font_name, logfont->elfLogFont); + if (info->callback->FindMissingGlyphs().None()) return 1; Debug(fontcache, 1, "Fallback font: {}", font_name); return 0; // stop enumerating } @@ -159,7 +159,7 @@ void Win32FontCache::SetFontSize(int pixels) this->fontname = FS2OTTD((LPWSTR)((BYTE *)otm + (ptrdiff_t)otm->otmpFaceName)); - Debug(fontcache, 2, "Loaded font '{}' with size {}", this->fontname, pixels); + Debug(fontcache, 2, "Win32FontCache: Loaded font '{}' with size {}", this->fontname, pixels); delete[] (BYTE*)otm; } @@ -246,7 +246,7 @@ void Win32FontCache::ClearFontCache() return this->SetGlyphPtr(key, std::move(new_glyph)).GetSprite(); } -/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(char32_t key, bool allow_fallback) +/* virtual */ GlyphID Win32FontCache::MapCharToGlyph(char32_t key) { assert(IsPrintable(key)); @@ -263,7 +263,7 @@ void Win32FontCache::ClearFontCache() GetGlyphIndicesW(this->dc, chars, key >= 0x010000U ? 2 : 1, glyphs, GGI_MARK_NONEXISTING_GLYPHS); if (glyphs[0] != 0xFFFF) return glyphs[0]; - return allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END ? this->parent->MapCharToGlyph(key) : 0; + return 0; } class Win32FontCacheFactory : FontCacheFactory { @@ -271,32 +271,28 @@ public: Win32FontCacheFactory() : FontCacheFactory("win32", "Win32 font loader") {} /** - * Loads the GDI font. - * If a GDI font description is present, e.g. from the automatic font - * fallback search, use it. Otherwise, try to resolve it by font name. - * @param fs The font size to load. - */ - std::unique_ptr LoadFont(FontSize fs, FontType fonttype) override + * Loads the GDI font. + * If a GDI font description is present, e.g. from the automatic font + * fallback search, use it. Otherwise, try to resolve it by font name. + * @param fs The font size to load. + */ + std::unique_ptr LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span os_handle) override { if (fonttype != FontType::TrueType) return nullptr; - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); - - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return nullptr; - LOGFONT logfont{}; logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH; logfont.lfCharSet = DEFAULT_CHARSET; logfont.lfOutPrecision = OUT_OUTLINE_PRECIS; logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; - if (settings->os_handle != nullptr) { - logfont = *(const LOGFONT *)settings->os_handle; + if (!os_handle.empty()) { + logfont = *reinterpret_cast(os_handle.data()); } else if (font.find('.') != std::string::npos) { /* Might be a font file name, try load it. */ if (!TryLoadFontFromFile(font, logfont)) { ShowInfo("Unable to load file '{}' for {} font, using default windows font selection instead", font, FontSizeToName(fs)); + if (!search) return nullptr; } } @@ -308,7 +304,7 @@ public: return LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); } - bool FindFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) override + bool FindFallbackFont(const std::string &language_isocode, FontSizes fontsizes, MissingGlyphSearcher *callback) override { Debug(fontcache, 1, "Trying fallback fonts"); EFCParam langInfo; @@ -318,7 +314,7 @@ public: Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode); return false; } - langInfo.settings = settings; + langInfo.fontsizes = fontsizes; langInfo.callback = callback; LOGFONT font; diff --git a/src/os/windows/font_win32.h b/src/os/windows/font_win32.h index 8aa0a42180..cf4d661f92 100644 --- a/src/os/windows/font_win32.h +++ b/src/os/windows/font_win32.h @@ -37,11 +37,11 @@ public: Win32FontCache(FontSize fs, const LOGFONT &logfont, int pixels); ~Win32FontCache(); void ClearFontCache() override; - GlyphID MapCharToGlyph(char32_t key, bool allow_fallback = true) override; + GlyphID MapCharToGlyph(char32_t key) override; std::string GetFontName() override { return this->fontname; } const void *GetOSHandle() override { return &this->logfont; } }; -void LoadWin32Font(FontSize fs); +void LoadWin32Font(FontSize fs, bool search, const std::string &font_name, std::span os_handle); #endif /* FONT_WIN32_H */ diff --git a/src/os/windows/string_uniscribe.cpp b/src/os/windows/string_uniscribe.cpp index 210e7a17f0..ae6c36344e 100644 --- a/src/os/windows/string_uniscribe.cpp +++ b/src/os/windows/string_uniscribe.cpp @@ -10,6 +10,8 @@ #include "../../stdafx.h" #include "../../debug.h" #include "string_uniscribe.h" +#include "../../gfx_func.h" +#include "../../gfx_layout_fallback.h" #include "../../language.h" #include "../../strings_func.h" #include "../../string_func.h" @@ -29,7 +31,7 @@ /** Uniscribe cache for internal font information, cleared when OTTD changes fonts. */ -static SCRIPT_CACHE _script_cache[FS_END]; +static std::map _script_cache; /** * Contains all information about a run of characters. A run are consecutive @@ -52,6 +54,8 @@ struct UniscribeRun { int total_advance; UniscribeRun(int pos, int len, Font *font, SCRIPT_ANALYSIS &sa) : pos(pos), len(len), font(font), sa(sa) {} + + void FallbackShape(const UniscribeParagraphLayoutFactory::CharType *buff); }; /** Break a string into language formatting ranges. */ @@ -94,7 +98,7 @@ public: std::span GetGlyphToCharMap() const override; const Font *GetFont() const override { return this->font; } - int GetLeading() const override { return this->font->fc->GetHeight(); } + int GetLeading() const override { return GetCharacterHeight(this->font->GetFontCache().GetSize()); } int GetGlyphCount() const override { return this->num_glyphs; } int GetAdvance() const { return this->total_advance; } }; @@ -130,31 +134,72 @@ public: std::unique_ptr NextLine(int max_width) override; }; -void UniscribeResetScriptCache(FontSize size) +void UniscribeResetScriptCache(FontSize) { - if (_script_cache[size] != nullptr) { - ScriptFreeCache(&_script_cache[size]); - _script_cache[size] = nullptr; + for (auto &sc : _script_cache) { + ScriptFreeCache(&sc.second); } + _script_cache.clear(); } /** Load the matching native Windows font. */ static HFONT HFontFromFont(Font *font) { - if (font->fc->GetOSHandle() != nullptr) return CreateFontIndirect(reinterpret_cast(const_cast(font->fc->GetOSHandle()))); + FontCache &fc = font->GetFontCache(); + + if (fc.GetOSHandle() != nullptr) return CreateFontIndirect(reinterpret_cast(const_cast(fc.GetOSHandle()))); LOGFONT logfont{}; - logfont.lfHeight = font->fc->GetHeight(); + logfont.lfHeight = fc.GetHeight(); logfont.lfWeight = FW_NORMAL; logfont.lfCharSet = DEFAULT_CHARSET; - convert_to_fs(font->fc->GetFontName(), logfont.lfFaceName); + convert_to_fs(fc.GetFontName(), logfont.lfFaceName); return CreateFontIndirect(&logfont); } +/** + * Manually shape a run for built-in non-truetype fonts. + * Similar to but not quite the same as \a ICURun::FallbackShape. + * @param buff The complete buffer of the run. + */ +void UniscribeRun::FallbackShape(const UniscribeParagraphLayoutFactory::CharType *buff) +{ + FontCache &fc = this->font->GetFontCache(); + + this->glyphs.reserve(this->len); + + /* Read each UTF-16 character, mapping to an appropriate glyph. */ + for (int i = this->pos; i < this->pos + this->len; ++i) { + char32_t c = Utf16DecodeChar(reinterpret_cast(buff + i)); + if (this->sa.fRTL) c = SwapRtlPairedCharacters(c); + this->glyphs.emplace_back(fc.MapCharToGlyph(c)); + if (Utf16IsLeadSurrogate(*(buff + i))) ++i; + } + + /* Reverse the sequence if this run is RTL. */ + if (this->sa.fRTL) { + std::reverse(std::begin(this->glyphs), std::end(this->glyphs)); + } + + this->offsets.reserve(this->glyphs.size()); + + /* Set positions of each glyph. */ + int y_offset = fc.GetGlyphYOffset(); + int advance = 0; + for (const GlyphID glyph : this->glyphs) { + this->offsets.emplace_back(advance, y_offset); + int x_advance = fc.GetGlyphWidth(glyph); + this->advances.push_back(x_advance); + advance += x_advance; + } +} + /** Determine the glyph positions for a run. */ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *buff, UniscribeRun &range) { + FontCache &fc = range.font->GetFontCache(); + /* Initial size guess for the number of glyphs recommended by Uniscribe. */ range.glyphs.resize(range.len * 3 / 2 + 16); range.vis_attribs.resize(range.glyphs.size()); @@ -166,10 +211,15 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b HFONT old_font = nullptr; HFONT cur_font = nullptr; + if (fc.IsBuiltInFont()) { + range.FallbackShape(buff); + return true; + } + while (true) { /* Shape the text run by determining the glyphs needed for display. */ int glyphs_used = 0; - HRESULT hr = ScriptShape(temp_dc, &_script_cache[range.font->fc->GetSize()], buff + range.pos, range.len, (int)range.glyphs.size(), &range.sa, &range.glyphs[0], &range.char_to_glyph[0], &range.vis_attribs[0], &glyphs_used); + HRESULT hr = ScriptShape(temp_dc, &_script_cache[fc.GetIndex()], buff + range.pos, range.len, (int)range.glyphs.size(), &range.sa, &range.glyphs[0], &range.char_to_glyph[0], &range.vis_attribs[0], &glyphs_used); if (SUCCEEDED(hr)) { range.glyphs.resize(glyphs_used); @@ -179,7 +229,7 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b ABC abc; range.advances.resize(range.glyphs.size()); range.offsets.resize(range.glyphs.size()); - hr = ScriptPlace(temp_dc, &_script_cache[range.font->fc->GetSize()], &range.glyphs[0], (int)range.glyphs.size(), &range.vis_attribs[0], &range.sa, &range.advances[0], &range.offsets[0], &abc); + hr = ScriptPlace(temp_dc, &_script_cache[fc.GetIndex()], &range.glyphs[0], (int)range.glyphs.size(), &range.vis_attribs[0], &range.sa, &range.advances[0], &range.offsets[0], &abc); if (SUCCEEDED(hr)) { /* We map our special sprite chars to values that don't fit into a WORD. Copy the glyphs * into a new vector and query the real glyph to use for these special chars. */ @@ -187,22 +237,12 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b for (size_t g_id = 0; g_id < range.glyphs.size(); g_id++) { range.ft_glyphs[g_id] = range.glyphs[g_id]; } - for (int i = 0; i < range.len; i++) { - if (buff[range.pos + i] >= SCC_SPRITE_START && buff[range.pos + i] <= SCC_SPRITE_END) { - auto pos = range.char_to_glyph[i]; - if (range.ft_glyphs[pos] == 0) { // Font doesn't have our special glyph, so remap. - range.ft_glyphs[pos] = range.font->fc->MapCharToGlyph(buff[range.pos + i]); - range.offsets[pos].dv = (range.font->fc->GetHeight() - ScaleSpriteTrad(FontCache::GetDefaultFontHeight(range.font->fc->GetSize()))) / 2; // Align sprite font to centre - range.advances[pos] = range.font->fc->GetGlyphWidth(range.ft_glyphs[pos]); - } - } - } range.total_advance = 0; for (size_t i = 0; i < range.advances.size(); i++) { #ifdef WITH_FREETYPE /* FreeType and GDI/Uniscribe seems to occasionally disagree over the width of a glyph. */ - if (range.advances[i] > 0 && range.ft_glyphs[i] != 0xFFFF) range.advances[i] = range.font->fc->GetGlyphWidth(range.ft_glyphs[i]); + if (range.advances[i] > 0 && range.glyphs[i] != 0xFFFF) range.advances[i] = fc.GetGlyphWidth(range.glyphs[i]); #endif range.total_advance += range.advances[i]; } @@ -280,11 +320,6 @@ static std::vector UniscribeItemizeString(UniscribeParagraphLayoutF /* Can't layout an empty string. */ if (length == 0) return nullptr; - /* Can't layout our in-built sprite fonts. */ - for (auto const &[position, font] : font_mapping) { - if (font->fc->IsBuiltInFont()) return nullptr; - } - /* Itemize text. */ std::vector items = UniscribeItemizeString(buff, length); if (items.empty()) return nullptr; diff --git a/src/strings.cpp b/src/strings.cpp index f4ff461bc7..b91fb12c94 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2273,32 +2273,60 @@ std::string_view GetCurrentLanguageIsoCode() /** * Check whether there are glyphs missing in the current language. - * @return If glyphs are missing, return \c true, else return \c false. + * @return Bit mask of font sizes have any missing glyphs. */ -bool MissingGlyphSearcher::FindMissingGlyphs() +FontSizes MissingGlyphSearcher::FindMissingGlyphs() { - FontCache::LoadFontCaches(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + for (FontSize size : this->fontsizes) { + GetFontCacheSubSetting(size)->fallback_fonts.clear(); + } + + FontCache::LoadFontCaches(this->fontsizes); + + FontSizes bad_fontsizes{}; + + for (FontSize size : this->fontsizes) { + auto set = this->GetRequiredGlyphs(size); + if (set.empty()) continue; + + Debug(fontcache, 0, "Missing {} glyphs in {} font size", set.size(), FontSizeToName(size)); + bad_fontsizes.Set(size); + } + + return bad_fontsizes; +} + +std::set BaseStringMissingGlyphSearcher::GetRequiredGlyphs(FontSizes fontsizes) +{ + std::set glyphs{}; this->Reset(); for (auto text = this->NextString(); text.has_value(); text = this->NextString()) { FontSize size = this->DefaultSize(); - FontCache *fc = FontCache::Get(size); for (char32_t c : Utf8View(*text)) { if (c >= SCC_FIRST_FONT && c <= SCC_LAST_FONT) { size = (FontSize)(c - SCC_FIRST_FONT); - fc = FontCache::Get(size); - } else if (!IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END) && IsPrintable(c) && !IsTextDirectionChar(c) && fc->MapCharToGlyph(c, false) == 0) { - /* The character is printable, but not in the normal font. This is the case we were testing for. */ - Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", static_cast(c), FontSizeToName(size)); - return true; + continue; } + + if (IsInsideMM(c, SCC_SPRITE_START, SCC_SPRITE_END)) continue; + if (!IsPrintable(c) || IsTextDirectionChar(c)) continue; + if (fontsizes.Test(size)) continue; + if (FontCache::GetFontIndexForCharacter(size, c) != INVALID_FONT_INDEX) continue; + + glyphs.insert(c); } } - return false; + + return glyphs; } /** Helper for searching through the language pack. */ -class LanguagePackGlyphSearcher : public MissingGlyphSearcher { +class LanguagePackGlyphSearcher : public BaseStringMissingGlyphSearcher { +public: + LanguagePackGlyphSearcher() : BaseStringMissingGlyphSearcher({FS_NORMAL, FS_SMALL, FS_LARGE}) {} + +private: uint i; ///< Iterator for the primary language tables. uint j; ///< Iterator for the secondary language tables. @@ -2327,26 +2355,9 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher { return ret; } - - bool Monospace() override - { - return false; - } - - void SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data) override - { -#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) - settings->small.font = font_name; - settings->medium.font = font_name; - settings->large.font = font_name; - - settings->small.os_handle = os_data; - settings->medium.os_handle = os_data; - settings->large.os_handle = os_data; -#endif - } }; + /** * Check whether the currently loaded language pack * uses characters that the currently loaded font @@ -2364,20 +2375,16 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher) { static LanguagePackGlyphSearcher pack_searcher; if (searcher == nullptr) searcher = &pack_searcher; - bool bad_font = !base_font || searcher->FindMissingGlyphs(); + FontSizes fontsizes = base_font ? searcher->FindMissingGlyphs() : FS_NORMAL; + bool bad_font = fontsizes.Any(); + #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) if (bad_font) { /* We found an unprintable character... lets try whether we can find * a fallback font that can print the characters in the current language. */ bool any_font_configured = !_fcsettings.medium.font.empty(); - FontCacheSettings backup = _fcsettings; - _fcsettings.mono.os_handle = nullptr; - _fcsettings.medium.os_handle = nullptr; - - bad_font = !FontProviderManager::FindFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher); - - _fcsettings = std::move(backup); + bad_font = !FontProviderManager::FindFallbackFont(_langpack.langpack->isocode, fontsizes, searcher); if (!bad_font && any_font_configured) { /* If the user configured a bad font, and we found a better one, @@ -2394,7 +2401,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 */ - FontCache::LoadFontCaches(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(fontsizes); } } #endif @@ -2411,12 +2418,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() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + LoadStringWidthTable(fontsizes); return; } /* Update the font with cache */ - LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + LoadStringWidthTable(searcher->fontsizes); #if !(defined(WITH_ICU_I18N) && defined(WITH_HARFBUZZ)) && !defined(WITH_UNISCRIBE) && !defined(WITH_COCOA) /* diff --git a/src/strings_func.h b/src/strings_func.h index 7bb9ae99ff..f76792150e 100644 --- a/src/strings_func.h +++ b/src/strings_func.h @@ -158,9 +158,31 @@ 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(); + + /** + * Get set of glyphs required for the current language. + * @param fontsizes Font sizes to test. + * @return Set of required glyphs. + **/ + virtual std::set GetRequiredGlyphs(FontSizes fontsizes) = 0; +}; + +class BaseStringMissingGlyphSearcher : public MissingGlyphSearcher { +public: + BaseStringMissingGlyphSearcher(FontSizes fontsizes) : MissingGlyphSearcher(fontsizes) {} + /** * Get the next string to search through. * @return The next string or nullopt if there is none. @@ -178,23 +200,9 @@ public: */ virtual void Reset() = 0; - /** - * Whether to search for a monospace font or not. - * @return True if searching for monospace. - */ - virtual bool Monospace() = 0; - - /** - * Set the right font names. - * @param settings The settings to modify. - * @param font_name The new font name. - * @param os_data Opaque pointer to OS-specific data. - */ - virtual void SetFontNames(struct FontCacheSettings *settings, std::string_view font_name, const void *os_data = nullptr) = 0; - - bool FindMissingGlyphs(); + std::set GetRequiredGlyphs(FontSizes fontsizes) override; }; -void CheckForMissingGlyphs(bool base_font = true, MissingGlyphSearcher *search = nullptr); +void CheckForMissingGlyphs(bool base_font = true, MissingGlyphSearcher *searcher = nullptr); #endif /* STRINGS_FUNC_H */ diff --git a/src/survey.cpp b/src/survey.cpp index 3d648b00e1..2c1a1e2428 100644 --- a/src/survey.cpp +++ b/src/survey.cpp @@ -298,10 +298,16 @@ void SurveyConfiguration(nlohmann::json &survey) */ void SurveyFont(nlohmann::json &survey) { - survey["small"] = FontCache::Get(FS_SMALL)->GetFontName(); - survey["medium"] = FontCache::Get(FS_NORMAL)->GetFontName(); - survey["large"] = FontCache::Get(FS_LARGE)->GetFontName(); - survey["mono"] = FontCache::Get(FS_MONO)->GetFontName(); + for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { + const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); + auto &font = survey[std::string(FontSizeToName(fs))]; + font["configured"]["font"] = setting->font; + font["configured"]["size"] = setting->size; + } + for (const auto &fc : FontCache::Get()) { + auto &font = survey[std::string(FontSizeToName(fc->GetSize()))]; + font["active"].push_back(fc->GetFontName()); + } } /** diff --git a/src/tests/mock_fontcache.h b/src/tests/mock_fontcache.h index 5be357756d..7312d9a79c 100644 --- a/src/tests/mock_fontcache.h +++ b/src/tests/mock_fontcache.h @@ -25,15 +25,16 @@ public: const Sprite *GetGlyph(GlyphID) override { return nullptr; } uint GetGlyphWidth(GlyphID) override { return this->height / 2; } bool GetDrawGlyphShadow() override { return false; } - GlyphID MapCharToGlyph(char32_t key, [[maybe_unused]] bool allow_fallback = true) override { return key; } + GlyphID MapCharToGlyph(char32_t key) override { return key; } std::string GetFontName() override { return "mock"; } bool IsBuiltInFont() override { return true; } static void InitializeFontCaches() { + FontCache::caches.clear(); for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { - if (FontCache::Get(fs) != nullptr) continue; FontCache::Register(std::make_unique(fs)); + FontCache::UpdateCharacterHeight(fs); } } }; diff --git a/src/textfile_gui.cpp b/src/textfile_gui.cpp index f474ead9c9..2c8be3cd90 100644 --- a/src/textfile_gui.cpp +++ b/src/textfile_gui.cpp @@ -83,7 +83,7 @@ static WindowDesc _textfile_desc( _nested_textfile_widgets ); -TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), file_type(file_type) +TextfileWindow::TextfileWindow(Window *parent, TextfileType file_type) : Window(_textfile_desc), BaseStringMissingGlyphSearcher(FS_MONO), file_type(file_type) { /* Init of nested tree is deferred. * TextfileWindow::ConstructWindow must be called by the inheriting window. */ @@ -754,19 +754,6 @@ bool TextfileWindow::IsTextWrapped() const return this->lines[this->search_iterator++].text; } -/* virtual */ bool TextfileWindow::Monospace() -{ - return true; -} - -/* virtual */ void TextfileWindow::SetFontNames([[maybe_unused]] FontCacheSettings *settings, [[maybe_unused]] std::string_view font_name, [[maybe_unused]] const void *os_data) -{ -#if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) - settings->mono.font = font_name; - settings->mono.os_handle = os_data; -#endif -} - #if defined(WITH_ZLIB) /** diff --git a/src/textfile_gui.h b/src/textfile_gui.h index 74034852bf..7a79bba557 100644 --- a/src/textfile_gui.h +++ b/src/textfile_gui.h @@ -19,7 +19,7 @@ std::optional GetTextfile(TextfileType type, Subdirectory dir, std::string_view filename); /** Window for displaying a textfile */ -struct TextfileWindow : public Window, MissingGlyphSearcher { +struct TextfileWindow : public Window, BaseStringMissingGlyphSearcher { TextfileType file_type{}; ///< Type of textfile to view. Scrollbar *vscroll = nullptr; ///< Vertical scrollbar. Scrollbar *hscroll = nullptr; ///< Horizontal scrollbar. @@ -38,8 +38,6 @@ struct TextfileWindow : public Window, MissingGlyphSearcher { void Reset() override; FontSize DefaultSize() override; std::optional NextString() override; - bool Monospace() override; - void SetFontNames(FontCacheSettings *settings, std::string_view font_name, const void *os_data) override; void ScrollToLine(size_t line); bool IsTextWrapped() const; diff --git a/src/zoom_func.h b/src/zoom_func.h index 6dfe0f1c51..df12a2382d 100644 --- a/src/zoom_func.h +++ b/src/zoom_func.h @@ -119,4 +119,14 @@ inline int ScaleGUITrad(int value) return value * _gui_scale / 100; } +/** + * Scale traditional pixel dimensions to font zoom level, for drawing sprite fonts. + * @param value Pixel amount at #ZOOM_BASE (traditional "normal" interface size). + * @return Pixel amount at _font_zoom (current interface size). + */ +inline int ScaleFontTrad(int value) +{ + return UnScaleByZoom(value * ZOOM_BASE, _font_zoom); +} + #endif /* ZOOM_FUNC_H */