From 9b55a040acb48fdf9961e405c97687688c85b17b Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Thu, 12 Jun 2025 18:30:23 +0100 Subject: [PATCH 1/5] Codechange: Font cache sizes can now be reset independently. Instead of choosing either "Normal/Small/Large" or "Monospace", use a BitSet to allow any combination. --- src/console_cmds.cpp | 2 +- src/fontcache.cpp | 10 ++++------ src/fontcache.h | 6 +++--- src/genworld.cpp | 2 +- src/gfx.cpp | 10 +++++----- src/gfx_func.h | 2 +- src/gfx_type.h | 5 +++++ src/gfxinit.cpp | 6 +++--- src/openttd.cpp | 2 +- src/os/unix/font_unix.cpp | 2 +- src/saveload/afterload.cpp | 4 ++-- src/settings_gui.cpp | 7 +++---- src/strings.cpp | 8 ++++---- 13 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index ebc6293355..3c7233fd9c 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2365,7 +2365,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 fb1d757c62..c93440801d 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); @@ -215,13 +215,11 @@ 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. */ -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 5d54879323..e01059dd08 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -162,9 +162,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(); } } @@ -226,7 +226,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/genworld.cpp b/src/genworld.cpp index fe7a8b86d5..a365a1354b 100644 --- a/src/genworld.cpp +++ b/src/genworld.cpp @@ -330,7 +330,7 @@ void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_setti SetCurrentRailTypeLabelList(); SetCurrentRoadTypeLabelList(); InitializeBuildingCounts(); - LoadStringWidthTable(); + LoadStringWidthTable(FONTSIZES_REQUIRED); /* Re-init the windowing system */ ResetWindowSystem(); diff --git a/src/gfx.cpp b/src/gfx.cpp index 6e12254374..b5548a5c20 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -1243,11 +1243,11 @@ 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. */ -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,8 +1812,8 @@ bool AdjustGUIZoom(bool automatic) if (old_font_zoom != _font_zoom) { GfxClearFontSpriteCache(); } - ClearFontCache(); - LoadStringWidthTable(); + ClearFontCache(FONTSIZES_ALL); + LoadStringWidthTable(FONTSIZES_REQUIRED); SetupWidgetDimensions(); UpdateAllVirtCoords(); diff --git a/src/gfx_func.h b/src/gfx_func.h index ed9d2c2cec..ac9aa6ed67 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); 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..de0c5c0516 100644 --- a/src/gfx_type.h +++ b/src/gfx_type.h @@ -258,6 +258,11 @@ enum FontSize : uint8_t { }; DECLARE_INCREMENT_DECREMENT_OPERATORS(FontSize) +using FontSizes = EnumBitSet; + +constexpr FontSizes FONTSIZES_ALL{FS_NORMAL, FS_SMALL, FS_LARGE, FS_MONO}; +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 b6aa3888bf..92535a367b 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/saveload/afterload.cpp b/src/saveload/afterload.cpp index 4e6899ea36..66d66294b7 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -796,7 +796,7 @@ bool AfterLoadGame() /* Load the sprites */ GfxLoadSprites(); - LoadStringWidthTable(); + LoadStringWidthTable(FONTSIZES_REQUIRED); /* Copy temporary data to Engine pool */ CopyTempEngineData(); @@ -3421,7 +3421,7 @@ void ReloadNewGRFData() { /* reload grf data */ GfxLoadSprites(); - LoadStringWidthTable(); + LoadStringWidthTable(FONTSIZES_REQUIRED); RecomputePrices(); /* reload vehicles */ ResetVehicleHash(); diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 4fcd47c1ce..75e242559b 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 cd77c85540..67c5dd6407 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2257,7 +2257,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()) { @@ -2384,7 +2384,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 @@ -2401,12 +2401,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 6a1625f2d07db35876e61c8835aff708a48c9f6e Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 2 Jul 2024 08:57:32 +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 | 143 +++++++++++++++++++----------- src/fontcache/spritefontcache.h | 5 +- src/fontcache/truetypefontcache.h | 2 - src/tests/mock_fontcache.h | 2 - 5 files changed, 96 insertions(+), 84 deletions(-) diff --git a/src/fontcache.h b/src/fontcache.h index e01059dd08..c5a5ff873b 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -65,16 +65,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; @@ -148,20 +138,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) { @@ -232,4 +208,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 c9f75153b5..2d46d0f106 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -8,6 +8,7 @@ /** @file spritefontcache.cpp Sprite fontcache implementation. */ #include "../stdafx.h" + #include "../fontcache.h" #include "../gfx_layout.h" #include "../zoom_func.h" @@ -19,7 +20,7 @@ #include "../safeguards.h" -static const int ASCII_LETTERSTART = 32; ///< First printable ASCII letter. +static std::array, FS_END> _glyph_maps{}; ///< Glyph map for each font size. /** * Scale traditional pixel dimensions to font zoom level, for drawing sprite fonts. @@ -37,61 +38,23 @@ static int ScaleFontTrad(int value) */ 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; } /** - * Get SpriteID associated with a GlyphID. - * @param key Glyph to find. - * @return SpriteID of glyph, or 0 if not present. + * Get the SpriteID associated with a unicode character. + * @param key Character to find. + * @return SpriteID of character, or 0 if not present. */ -SpriteID SpriteFontCache::GetUnicodeGlyph(GlyphID key) +SpriteID SpriteFontCache::GetSpriteIDForChar(char32_t key) { - const auto found = this->glyph_to_spriteid_map.find(key & ~SPRITE_GLYPH); - if (found == std::end(this->glyph_to_spriteid_map)) return 0; - return found->second; -} + const auto &glyph_map = _glyph_maps[this->fs]; -void SpriteFontCache::SetUnicodeGlyph(char32_t key, SpriteID sprite) -{ - this->glyph_to_spriteid_map[key] = sprite; -} + auto it = glyph_map.find(key); + if (it != std::end(glyph_map)) return it->second; -void SpriteFontCache::InitializeUnicodeGlyphMap() -{ - /* Clear out existing glyph map if it exists */ - this->glyph_to_spriteid_map.clear(); - - SpriteID base; - switch (this->fs) { - default: NOT_REACHED(); - case FS_MONO: // Use normal as default for mono spaced font - case FS_NORMAL: base = SPR_ASCII_SPACE; break; - case FS_SMALL: base = SPR_ASCII_SPACE_SMALL; break; - case FS_LARGE: base = SPR_ASCII_SPACE_BIG; break; - } - - 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); - } - - 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); - } else { - SpriteID sprite = base + key - ASCII_LETTERSTART; - this->SetUnicodeGlyph(unicode_map.code, sprite); - } - } + return 0; } void SpriteFontCache::ClearFontCache() @@ -103,22 +66,22 @@ void SpriteFontCache::ClearFontCache() const Sprite *SpriteFontCache::GetGlyph(GlyphID key) { - SpriteID sprite = this->GetUnicodeGlyph(static_cast(key & ~SPRITE_GLYPH)); - if (sprite == 0) sprite = this->GetUnicodeGlyph('?'); + SpriteID sprite = this->GetSpriteIDForChar(static_cast(key & ~SPRITE_GLYPH)); + if (sprite == 0) sprite = this->GetSpriteIDForChar('?'); return GetSprite(sprite, SpriteType::Font); } uint SpriteFontCache::GetGlyphWidth(GlyphID key) { - SpriteID sprite = this->GetUnicodeGlyph(static_cast(key & ~SPRITE_GLYPH)); - if (sprite == 0) sprite = this->GetUnicodeGlyph('?'); + SpriteID sprite = this->GetSpriteIDForChar(static_cast(key & ~SPRITE_GLYPH)); + if (sprite == 0) sprite = this->GetSpriteIDForChar('?'); 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 = this->GetSpriteIDForChar(key); if (sprite == 0) return 0; return SPRITE_GLYPH | key; } @@ -127,3 +90,79 @@ bool SpriteFontCache::GetDrawGlyphShadow() { return false; } + +/** + * 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) +{ + _glyph_maps[fs][key] = sprite; +} + +/** + * Get the sprite font base sprite index. + * @param fs Font size to get base sprite for. + * @return Base sprite for font. + */ +static SpriteID GetSpriteFontBase(FontSize fs) +{ + switch (fs) { + default: NOT_REACHED(); + case FS_MONO: // Use normal as default for mono spaced font + case FS_NORMAL: return SPR_ASCII_SPACE; + case FS_SMALL: return SPR_ASCII_SPACE_SMALL; + case FS_LARGE: return SPR_ASCII_SPACE_BIG; + } +} + +/** + * 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) +{ + static constexpr int ASCII_LETTERSTART = 32; ///< First printable ASCII letter. + + /* Clear existing glyph map. */ + _glyph_maps[fs].clear(); + + SpriteID base = GetSpriteFontBase(fs); + for (uint i = ASCII_LETTERSTART; i < 256; i++) { + SpriteID sprite = base + i - ASCII_LETTERSTART; + if (!SpriteExists(sprite)) continue; + + /* Map the glyph for normal use. */ + SetUnicodeGlyph(fs, i, sprite); + + /* Glyphs are also mapped to the first private use area. */ + 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. */ + _glyph_maps[fs].erase(unicode_map.code); + } else { + SpriteID sprite = base + key - ASCII_LETTERSTART; + SetUnicodeGlyph(fs, unicode_map.code, sprite); + } + } +} + +/** + * Initialize the glyph map. + */ +void InitializeUnicodeGlyphMap() +{ + for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { + InitializeUnicodeGlyphMap(fs); + } +} diff --git a/src/fontcache/spritefontcache.h b/src/fontcache/spritefontcache.h index 61dd5866e2..fc3d49e4eb 100644 --- a/src/fontcache/spritefontcache.h +++ b/src/fontcache/spritefontcache.h @@ -17,8 +17,6 @@ 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; @@ -28,8 +26,7 @@ public: bool IsBuiltInFont() override { return true; } private: - std::unordered_map glyph_to_spriteid_map{}; ///< Mapping of glyphs to sprite IDs. - SpriteID GetUnicodeGlyph(GlyphID key); + SpriteID GetSpriteIDForChar(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 ddb128f10d0cda2be9173185a4c9253e09ba9b97 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 10 Jun 2025 11:26:05 +0100 Subject: [PATCH 3/5] Codechange: Use ProviderManager interface to register FontCache factories. --- src/fontcache.cpp | 36 ++--- src/fontcache.h | 28 ++++ src/fontcache/freetypefontcache.cpp | 202 ++++++++++++++-------------- src/fontcache/spritefontcache.cpp | 14 ++ src/os/macosx/font_osx.cpp | 156 +++++++++++---------- src/os/windows/font_win32.cpp | 178 ++++++++++++------------ 6 files changed, 337 insertions(+), 277 deletions(-) diff --git a/src/fontcache.cpp b/src/fontcache.cpp index c93440801d..6608abf156 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" @@ -28,6 +27,18 @@ static const int _default_font_ascender[FS_END] = { 8, 5, 15, 8}; FontCacheSettings _fcsettings; +/** + * Try loading a font with any fontcache factory. + * @param fs Font size to load. + * @param fonttype Font type requested. + */ +/* static */ void FontProviderManager::LoadFont(FontSize fs, FontType fonttype) +{ + for (auto &provider : FontProviderManager::GetProviders()) { + provider->LoadFont(fs, fonttype); + } +} + /** * Create a new font cache. * @param fs The size of the font. @@ -85,7 +96,7 @@ int GetCharacterHeight(FontSize size) /* 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::caches[fs] == nullptr) FontProviderManager::LoadFont(fs, FontType::Sprite); /* FontCache inserts itself into to the cache. */ } } @@ -136,15 +147,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. @@ -223,13 +225,7 @@ void InitFontCache(FontSizes fontsizes) FontCache *fc = FontCache::Get(fs); if (fc->HasParent()) delete fc; -#ifdef WITH_FREETYPE - LoadFreeTypeFont(fs); -#elif defined(_WIN32) - LoadWin32Font(fs); -#elif defined(WITH_COCOA) - LoadCoreTextFont(fs); -#endif + FontProviderManager::LoadFont(fs, FontType::TrueType); } } @@ -242,10 +238,6 @@ void UninitFontCache() FontCache *fc = FontCache::Get(fs); if (fc->HasParent()) delete fc; } - -#ifdef WITH_FREETYPE - UninitFreeType(); -#endif /* WITH_FREETYPE */ } #if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) diff --git a/src/fontcache.h b/src/fontcache.h index c5a5ff873b..374f2f8766 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" @@ -208,6 +209,33 @@ 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 void LoadFont(FontSize fs, FontType fonttype) = 0; +}; + +class FontProviderManager : ProviderManager { +public: + static void 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..23d622aa92 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,110 @@ 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. + */ + void LoadFont(FontSize fs, FontType fonttype) override + { + if (fonttype != FontType::TrueType) return; + + 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); + } + } + +private: + 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; + } +}; + +static FreeTypeFontCacheFactory s_freetype_fontcache_factory; #if !defined(WITH_FONTCONFIG) diff --git a/src/fontcache/spritefontcache.cpp b/src/fontcache/spritefontcache.cpp index 2d46d0f106..eab13ae388 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -91,6 +91,20 @@ bool SpriteFontCache::GetDrawGlyphShadow() return false; } +class SpriteFontCacheFactory : public FontCacheFactory { +public: + SpriteFontCacheFactory() : FontCacheFactory("sprite", "Sprite font provider") {} + + void LoadFont(FontSize fs, FontType fonttype) override + { + if (fonttype != FontType::Sprite) return; + + new SpriteFontCache(fs); + } +}; + +static SpriteFontCacheFactory s_sprite_fontcache_factory; + /** * Set the SpriteID for a unicode character. * @param fs Font size to set. diff --git a/src/os/macosx/font_osx.cpp b/src/os/macosx/font_osx.cpp index 6588eb7507..5815d5c197 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. + */ + void LoadFont(FontSize fs, FontType fonttype) + { + if (fonttype != FontType::TrueType) return; - /* 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; + + 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; + } + + new CoreTextFontCache(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/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index 6358ca4d3d..df4dcdbcd3 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. + */ + void LoadFont(FontSize fs, FontType fonttype) override + { + if (fonttype != FontType::TrueType) return; - /* 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; + + 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); } - 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 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); - 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. + new Win32FontCache(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; From ee2f0f46eef3528fbc30bdf6ae7a3491d75df99c Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 10 Jun 2025 11:46:18 +0100 Subject: [PATCH 4/5] Codechange: Move fallback font detection to FontCacheFactory. --- 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 +- 12 files changed, 187 insertions(+), 176 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 6608abf156..1713adfcff 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" @@ -39,6 +38,25 @@ FontCacheSettings _fcsettings; } } +/** + * 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::SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) +{ + for (auto &provider : FontProviderManager::GetProviders()) { + if (provider->SetFallbackFont(settings, language_isocode, callback)) { + return true; + } + } + return false; +} + /** * Create a new font cache. * @param fs The size of the font. @@ -239,8 +257,3 @@ void UninitFontCache() if (fc->HasParent()) delete fc; } } - -#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 374f2f8766..86b12c611e 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -229,11 +229,13 @@ public: } virtual void LoadFont(FontSize fs, FontType fonttype) = 0; + virtual bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) = 0; }; class FontProviderManager : ProviderManager { public: static void LoadFont(FontSize fs, FontType fonttype); + static bool SetFallbackFont(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 23d622aa92..04a748e901 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) { error = LoadFont(fs, face, font, GetFontCacheFontSize(fs)); @@ -271,6 +273,15 @@ public: } } + bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) override + { +#ifdef WITH_FONTCONFIG + if (FontConfigSetFallbackFont(settings, language_isocode, callback)) return true; +#endif /* WITH_FONTCONFIG */ + + return false; + } + private: static FT_Error LoadFont(FontSize fs, FT_Face face, std::string_view font_name, uint size) { @@ -311,10 +322,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 eab13ae388..a6cba1ecf6 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -101,6 +101,11 @@ public: new SpriteFontCache(fs); } + + bool SetFallbackFont(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 5815d5c197..c542b7246e 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: new CoreTextFontCache(fs, std::move(font_ref), GetFontCacheFontSize(fs)); } + bool SetFallbackFont(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 feb96713e5..27e23631bd 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 FontConfigSetFallbackFont(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..44562bfaac --- /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 FontConfigSetFallbackFont(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 df4dcdbcd3..c60f32bade 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: LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); } + bool SetFallbackFont(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 void LoadWin32Font(FontSize fs, const LOGFONT &logfont, uint size, std::string_view font_name) { diff --git a/src/strings.cpp b/src/strings.cpp index 67c5dd6407..45ef83fc76 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" @@ -2365,7 +2364,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::SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher); _fcsettings = std::move(backup); From 8c9a3fc3eec8b8b56d1a812b4f5c9b16cab52996 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Fri, 17 May 2024 12:49:26 +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 | 136 ++++++++++++++++++---------- src/fontcache.h | 130 +++++++++++++++++++------- src/fontcache/freetypefontcache.cpp | 38 +++----- src/fontcache/spritefontcache.cpp | 26 ++++-- src/fontcache/spritefontcache.h | 3 +- src/fontcache/truetypefontcache.cpp | 4 - src/gfx.cpp | 23 +++-- src/gfx_layout.cpp | 84 ++++++++++++----- src/gfx_layout.h | 25 ++--- src/gfx_layout_fallback.cpp | 20 ++-- src/gfx_layout_fallback.h | 19 ++++ src/gfx_layout_icu.cpp | 85 ++++++++++++----- src/os/macosx/font_osx.cpp | 25 +++-- src/os/macosx/font_osx.h | 2 +- src/os/macosx/string_osx.cpp | 45 ++++----- src/os/unix/font_unix.cpp | 52 +++++++---- src/os/unix/font_unix.h | 2 +- src/os/windows/font_win32.cpp | 50 +++++----- src/os/windows/font_win32.h | 4 +- src/os/windows/string_uniscribe.cpp | 76 +++++++++++----- src/string.cpp | 4 +- src/string_func.h | 2 +- src/strings.cpp | 104 +++++++++++---------- src/strings_func.h | 40 ++++---- src/survey.cpp | 14 ++- src/tests/mock_fontcache.h | 6 +- src/textfile_gui.cpp | 15 +-- src/textfile_gui.h | 4 +- 29 files changed, 651 insertions(+), 410 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 3c7233fd9c..72552aafdd 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2360,17 +2360,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()) { - InitFontCache(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 1713adfcff..c976bb7b5b 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -8,6 +8,8 @@ /** @file fontcache.cpp Cache for characters from fonts. */ #include "stdafx.h" + +#include "core/string_consumer.hpp" #include "fontcache.h" #include "blitter/factory.hpp" #include "gfx_layout.h" @@ -17,13 +19,10 @@ #include "viewport_func.h" #include "window_func.h" #include "fileio_func.h" +#include "zoom_func.h" #include "safeguards.h" -/** Default heights for the different sizes of fonts. */ -static const int _default_font_height[FS_END] = {10, 6, 18, 10}; -static const int _default_font_ascender[FS_END] = { 8, 5, 15, 8}; - FontCacheSettings _fcsettings; /** @@ -31,10 +30,10 @@ FontCacheSettings _fcsettings; * @param fs Font size to load. * @param fonttype Font type requested. */ -/* static */ void FontProviderManager::LoadFont(FontSize fs, FontType fonttype) +/* static */ void FontProviderManager::LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span os_handle) { for (auto &provider : FontProviderManager::GetProviders()) { - provider->LoadFont(fs, fonttype); + provider->LoadFont(fs, fonttype, search, font_name, os_handle); } } @@ -47,10 +46,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::SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) +/* static */ bool FontProviderManager::SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, class MissingGlyphSearcher *callback) { for (auto &provider : FontProviderManager::GetProviders()) { - if (provider->SetFallbackFont(settings, language_isocode, callback)) { + if (provider->SetFallbackFont(language_isocode, bad_mask, callback)) { return true; } } @@ -61,42 +60,49 @@ FontCacheSettings _fcsettings; * Create a new font cache. * @param fs The size of the font. */ -FontCache::FontCache(FontSize fs) : parent(FontCache::Get(fs)), fs(fs), height(_default_font_height[fs]), - ascender(_default_font_ascender[fs]), descender(_default_font_ascender[fs] - _default_font_height[fs]) +FontCache::FontCache(FontSize fs) : fs(fs) { - assert(this->parent == nullptr || this->fs == this->parent->fs); - FontCache::caches[this->fs] = this; + /* 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); + + /* Register this font cache in the slot. */ + it->reset(this); + + /* Set up our font index and make us the default font cache for this font size. */ + this->font_index = static_cast(std::distance(std::begin(FontCache::caches), it)); + FontCache::default_font_index[fs] = this->font_index; + Layouter::ResetFontCache(this->fs); } /** Clean everything up. */ FontCache::~FontCache() { - assert(this->fs == this->parent->fs); - FontCache::caches[this->fs] = this->parent; Layouter::ResetFontCache(this->fs); } int FontCache::GetDefaultFontHeight(FontSize fs) { - return _default_font_height[fs]; + return DEFAULT_FONT_HEIGHT[fs]; } -/** - * Get the font name of a given font size. - * @param fs The font size to look up. - * @return The font name. - */ -std::string FontCache::GetName(FontSize fs) +/* static */ void FontCache::UpdateCharacterHeight(FontSize fs) { - FontCache *fc = FontCache::Get(fs); - if (fc != nullptr) { - return fc->GetFontName(); - } else { - return "[NULL]"; + FontCache::max_height[fs] = 0; + for (const auto &fc : FontCache::caches) { + if (fc == nullptr || fc->fs != fs) continue; + FontCache::max_height[fs] = std::max(FontCache::max_height[fs], fc->height); } + if (FontCache::max_height[fs] == 0) FontCache::max_height[fs] = GetDefaultFontHeight(fs); } +int FontCache::GetGlyphYOffset() +{ + int fs_height = FontCache::GetCharacterHeight(this->GetSize()); + int height = this->GetAscender() - this->GetDescender(); + return (fs_height - height) / 2; +} /** * Get height of a character for a given font size. @@ -105,16 +111,19 @@ 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 */ FontCache *FontCache::caches[FS_END]; +/* static */ FontCache::FontCaches FontCache::caches; +/* static */ std::array FontCache::max_height{}; +/* static */ std::array FontCache::default_font_index{}; /* static */ void FontCache::InitializeFontCaches() { - for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { - if (FontCache::caches[fs] == nullptr) FontProviderManager::LoadFont(fs, FontType::Sprite); /* FontCache inserts itself into to the cache. */ + for (FontSize fs : FONTSIZES_ALL) { + FontCache::max_height[fs] = ScaleSpriteTrad(GetDefaultFontHeight(fs)); } } @@ -145,15 +154,8 @@ 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 { InitFontCache(fontsize); } @@ -171,7 +173,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(); } /** @@ -208,7 +210,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. */ @@ -237,13 +239,52 @@ std::string GetFontCacheFontName(FontSize fs) */ void InitFontCache(FontSizes fontsizes) { - FontCache::InitializeFontCaches(); + static constexpr std::string_view DEFAULT_FONT = "default"; for (FontSize fs : fontsizes) { - FontCache *fc = FontCache::Get(fs); - if (fc->HasParent()) delete fc; + Layouter::ResetFontCache(fs); + FontCache::default_font_index[fs] = INVALID_FONT_INDEX; + } - FontProviderManager::LoadFont(fs, FontType::TrueType); + /* Remove all existing FontCaches. */ + 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 &fallback : setting->fallback_fonts) { + FontProviderManager::LoadFont(fs, FontType::TrueType, /*fallback.dynamic ? "missing-fallback" : "language-fallback", */ false, fallback.name, fallback.os_handle); + } + + /* 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. */ + FontProviderManager::LoadFont(fs, FontType::Sprite, /*"default"*/ false, {}, {}); + if (!_fcsettings.prefer_sprite) { + /* Load the default truetype font if sprite not isn't preferred. */ + FontProviderManager::LoadFont(fs, FontType::TrueType, /*"default",*/ false, GetDefaultTruetypeFontFile(fs), {}); + } + } else { + FontProviderManager::LoadFont(fs, FontType::TrueType, /*"configured",*/ true, std::string{*it}, {}); + } + } } } @@ -252,8 +293,5 @@ void InitFontCache(FontSizes fontsizes) */ void UninitFontCache() { - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { - FontCache *fc = FontCache::Get(fs); - if (fc->HasParent()) delete fc; - } + FontCache::caches.clear(); } diff --git a/src/fontcache.h b/src/fontcache.h index 86b12c611e..a2f4f11a8f 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -16,17 +16,31 @@ /** 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 FontCache *caches[FS_END]; ///< All the font caches. - FontCache *parent; ///< The parent of this font cache. + using FontCaches = std::vector>; + static FontCaches caches; + + static std::array max_height; + static std::array default_font_index; + const FontSize fs; ///< The size of the font. - int height; ///< The height of the font. - int ascender; ///< The ascender value of the font. - int descender; ///< The descender value 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. + + friend void UninitFontCache(); + friend void InitFontCache(FontSizes fontsizes); public: FontCache(FontSize fs); @@ -42,6 +56,8 @@ public: */ 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. @@ -92,10 +108,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. @@ -112,25 +127,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]; + 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::max_height[fs]; + } + + 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; } /** @@ -141,36 +188,55 @@ public: inline void ClearFontCache(FontSizes fontsizes) { - for (FontSize fs : fontsizes) { - FontCache::Get(fs)->ClearFontCache(); + for (const auto &fc : FontCache::Get()) { + if (fc == nullptr) continue; + if (!fontsizes.Test(fc->GetSize())) continue; + fc->ClearFontCache(); } } /** 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; + + /** + * Add a fallback font this font size configuration. + * @param name Name of font to add. + * @param handle OS-specific handle or data of font. + */ + template + void AddFallback(const std::string &name, T &handle) + { + auto os_data = std::as_bytes(std::span(&handle, 1)); + this->fallback_fonts.emplace_back(name, std::vector{os_data.begin(), os_data.end()}); + } }; /** Settings for the four different fonts. */ @@ -228,14 +294,14 @@ public: ProviderManager::Unregister(*this); } - virtual void LoadFont(FontSize fs, FontType fonttype) = 0; - virtual bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) = 0; + virtual void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span os_handle) = 0; + virtual bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, class MissingGlyphSearcher *callback) = 0; }; class FontProviderManager : ProviderManager { public: - static void LoadFont(FontSize fs, FontType fonttype); - static bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); + static void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font_name, std::span os_handle); + static bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, class MissingGlyphSearcher *callback); }; /* Implemented in spritefontcache.cpp */ diff --git a/src/fontcache/freetypefontcache.cpp b/src/fontcache/freetypefontcache.cpp index 04a748e901..332dbf2503 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; } @@ -104,6 +104,7 @@ void FreeTypeFontCache::SetFontSize(int pixels) this->ascender = this->face->size->metrics.ascender >> 6; this->descender = this->face->size->metrics.descender >> 6; this->height = this->ascender - this->descender; + FontCache::UpdateCharacterHeight(this->fs); } else { /* Both FT_Set_Pixel_Sizes and FT_Select_Size failed. */ Debug(fontcache, 0, "Font size selection failed. Using FontCache defaults."); @@ -193,17 +194,11 @@ const Sprite *FreeTypeFontCache::InternalGetGlyph(GlyphID key, bool aa) } -GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) +GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key) { assert(IsPrintable(key)); - FT_UInt glyph = FT_Get_Char_Index(this->face, key); - - if (glyph == 0 && allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) { - return this->parent->MapCharToGlyph(key); - } - - return glyph; + return FT_Get_Char_Index(this->face, key); } FT_Library _ft_library = nullptr; @@ -225,15 +220,10 @@ public: * format is 'font family name' or 'font family name, font style'. * @param fs The font size to load. */ - void LoadFont(FontSize fs, FontType fonttype) override + void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span os_handle) override { if (fonttype != FontType::TrueType) return; - 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"); @@ -247,7 +237,9 @@ public: /* If font is an absolute path to a ttf, try loading that first. */ int32_t index = 0; - if (settings->os_handle != nullptr) index = *static_cast(settings->os_handle); + if (os_handle.size() == sizeof(index)) { + index = *reinterpret_cast(os_handle.data()); + } FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face); if (error != FT_Err_Ok) { @@ -259,24 +251,24 @@ 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) { 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); + ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}", font, FontSizeToName(fs), error); } } else { FT_Done_Face(face); } } - bool SetFallbackFont(struct FontCacheSettings *settings, const std::string &language_isocode, class MissingGlyphSearcher *callback) override + bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, MissingGlyphSearcher *callback) override { #ifdef WITH_FONTCONFIG - if (FontConfigSetFallbackFont(settings, language_isocode, callback)) return true; + if (FontConfigSetFallbackFont(language_isocode, bad_mask, callback)) return true; #endif /* WITH_FONTCONFIG */ return false; @@ -293,8 +285,8 @@ private: 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...) */ + * the first map, but platform_id 0 encoding_id 0 should also + * be unicode (strange system...) */ FT_CharMap found = face->charmaps[0]; int i; diff --git a/src/fontcache/spritefontcache.cpp b/src/fontcache/spritefontcache.cpp index a6cba1ecf6..f6891ba14c 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -38,8 +38,15 @@ static int ScaleFontTrad(int value) */ 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::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]); + FontCache::UpdateCharacterHeight(this->fs); } /** @@ -60,30 +67,29 @@ SpriteID SpriteFontCache::GetSpriteIDForChar(char32_t key) 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(); } const Sprite *SpriteFontCache::GetGlyph(GlyphID key) { - SpriteID sprite = this->GetSpriteIDForChar(static_cast(key & ~SPRITE_GLYPH)); + SpriteID sprite = this->GetSpriteIDForChar(static_cast(key)); if (sprite == 0) sprite = this->GetSpriteIDForChar('?'); return GetSprite(sprite, SpriteType::Font); } uint SpriteFontCache::GetGlyphWidth(GlyphID key) { - SpriteID sprite = this->GetSpriteIDForChar(static_cast(key & ~SPRITE_GLYPH)); + SpriteID sprite = this->GetSpriteIDForChar(static_cast(key)); if (sprite == 0) sprite = this->GetSpriteIDForChar('?'); 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 = this->GetSpriteIDForChar(key); if (sprite == 0) return 0; - return SPRITE_GLYPH | key; + return static_cast(key); } bool SpriteFontCache::GetDrawGlyphShadow() @@ -95,14 +101,14 @@ class SpriteFontCacheFactory : public FontCacheFactory { public: SpriteFontCacheFactory() : FontCacheFactory("sprite", "Sprite font provider") {} - void LoadFont(FontSize fs, FontType fonttype) override + void LoadFont(FontSize fs, FontType fonttype, bool, const std::string &, std::span) override { if (fonttype != FontType::Sprite) return; new SpriteFontCache(fs); } - bool SetFallbackFont(struct FontCacheSettings *, const std::string &, class MissingGlyphSearcher *) override + bool SetFallbackFont(const std::string &, FontSizes, class MissingGlyphSearcher *) override { return false; } diff --git a/src/fontcache/spritefontcache.h b/src/fontcache/spritefontcache.h index fc3d49e4eb..0e80cdc56e 100644 --- a/src/fontcache/spritefontcache.h +++ b/src/fontcache/spritefontcache.h @@ -21,11 +21,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(); SpriteID GetSpriteIDForChar(char32_t key); }; 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 b5548a5c20..d15895343e 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -481,6 +481,12 @@ static void SetColourRemap(TextColour colour) _colour_remap_ptr = _string_colourremap; } +static void RenderGlyph(FontCache *fc, GlyphID glyph, int left, int, int top, int) +{ + const Sprite *sprite = fc->GetGlyph(glyph); + GfxMainBlitter(sprite, left, top, BlitterMode::ColourRemap); +}; + /** * Drawing routine for drawing a laid out line of text. * @param line String to draw. @@ -595,6 +601,9 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, 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; + int height = GetCharacterHeight(fc->GetSize()); + + auto render = RenderGlyph; DrawPixelInfo *dpi = _cur_dpi; int dpi_left = dpi->left; @@ -612,14 +621,16 @@ 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) { + begin_x += shadow_offset; + end_x += shadow_offset; + top += shadow_offset; + } - if (do_shadow && (glyph & SPRITE_GLYPH) != 0) continue; - - GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap); + render(fc, glyph, begin_x, end_x, top, top + height - 1); } } diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp index 8c43f7b2c0..15757f2135 100644 --- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -38,18 +38,17 @@ std::unique_ptr Layouter::linecache; /** Cache of Font instances. */ -Layouter::FontColourMap Layouter::fonts[FS_END]; +std::unordered_map Layouter::fonts; /** * Construct a new font. - * @param size The font size to use for this font. + * @param font_index The font index 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) +Font::Font(FontIndex font_index, TextColour colour) : + fc(FontCache::Get(font_index)), colour(colour) { - assert(size < FS_END); } /** @@ -71,7 +70,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 +79,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 +97,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 +139,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::all_of(std::begin(font_mapping), std::end(font_mapping), [](const auto &i) { return i.second->fc->IsBuiltInFont(); })) { + return; + } + } + line.layout = T::GetParagraphLayout(buff_begin, buff, font_mapping); line.state_after = state; } @@ -128,7 +159,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 +372,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 +397,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..eef446fc9c 100644 --- a/src/gfx_layout.h +++ b/src/gfx_layout.h @@ -22,12 +22,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 +68,7 @@ struct FontState { inline void SetFontSize(FontSize f) { this->fontsize = f; + this->font_index = FontCache::GetDefaultFontIndex(this->fontsize); } }; @@ -85,9 +87,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); } }; @@ -99,7 +102,7 @@ public: FontCache *fc; ///< The font we are using. TextColour colour; ///< The colour this font has to be. - Font(FontSize size, TextColour colour); + Font(FontIndex font_index, TextColour colour); }; /** 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..f49d1fa090 100644 --- a/src/gfx_layout_fallback.cpp +++ b/src/gfx_layout_fallback.cpp @@ -51,7 +51,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()->fc->GetSize()); } std::span GetGlyphToCharMap() const override { return this->glyph_to_char; } }; @@ -112,23 +112,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->fc; + 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); } @@ -234,6 +228,7 @@ std::unique_ptr FallbackParagraphLayout::NextLine } const FontCache *fc = iter->second->fc; + assert(fc != nullptr); const char32_t *next_run = this->buffer_begin + iter->first; const char32_t *begin = this->buffer; @@ -251,6 +246,7 @@ std::unique_ptr FallbackParagraphLayout::NextLine if (this->buffer == next_run) { int w = l->GetWidth(); + assert(iter->second->fc != nullptr); 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..982f51ac25 100644 --- a/src/gfx_layout_icu.cpp +++ b/src/gfx_layout_icu.cpp @@ -9,8 +9,10 @@ #include "stdafx.h" #include "gfx_layout_icu.h" +#include "gfx_layout_fallback.h" #include "debug.h" +#include "string_func.h" #include "strings_func.h" #include "language.h" #include "table/control_codes.h" @@ -51,6 +53,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 +79,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->fc->GetSize()); } int GetGlyphCount() const override { return this->glyphs.size(); } int GetAdvance() const { return this->total_advance; } }; @@ -135,12 +138,52 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) : assert(!run.positions.empty()); this->positions.reserve(run.positions.size()); + int y_offset = this->font->fc->GetGlyphYOffset(); /* Copy positions, moving x coordinate by x offset. */ for (const auto &pos : run.positions) { - this->positions.emplace_back(pos.left + x, pos.right + x, pos.top); + this->positions.emplace_back(pos.left + x, pos.right + x, pos.top + y_offset); } } +/** + * 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) +{ + 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(buff + i); + if (this->level & 1) c = SwapRtlPairedCharacters(c); + this->glyphs.emplace_back(this->font->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 = (GetCharacterHeight(this->font->fc->GetSize()) - this->font->fc->GetHeight()) / 2; + int advance = 0; + for (const GlyphID glyph : this->glyphs) { + int x_advance = this->font->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,6 +192,17 @@ ICUParagraphLayout::ICUVisualRun::ICUVisualRun(const ICURun &run, int x) : */ void ICURun::Shape(UChar *buff, size_t buff_length) { + /* Make sure any former run is lost. */ + this->glyphs.clear(); + this->glyph_to_char.clear(); + this->positions.clear(); + this->advance.clear(); + + if (this->font->fc->IsBuiltInFont()) { + this->FallbackShape(buff); + return; + } + auto hbfont = hb_ft_font_create_referenced(*(static_cast(font->fc->GetOSHandle()))); /* Match the flags with how we render the glyphs. */ hb_ft_font_set_load_flags(hbfont, GetFontAAState() ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO); @@ -170,12 +224,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 +231,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 = (GetCharacterHeight(this->font->fc->GetSize()) - this->font->fc->GetHeight()) / 2; 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 +399,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 c542b7246e..7917818f06 100644 --- a/src/os/macosx/font_osx.cpp +++ b/src/os/macosx/font_osx.cpp @@ -26,6 +26,7 @@ CoreTextFontCache::CoreTextFontCache(FontSize fs, CFAutoRelease &&font, int pixels) : TrueTypeFontCache(fs, pixels), font_desc(std::move(font)) { this->SetFontSize(pixels); + FontCache::UpdateCharacterHeight(this->fs); } /** @@ -94,7 +95,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 +113,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,7 +208,7 @@ public: * fallback search, use it. Otherwise, try to resolve it by font name. * @param fs The font size to load. */ - void LoadFont(FontSize fs, FontType fonttype) + void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span os_handle) override { if (fonttype != FontType::TrueType) return; @@ -259,7 +256,7 @@ public: new CoreTextFontCache(fs, std::move(font_ref), GetFontCacheFontSize(fs)); } - bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) override + bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, MissingGlyphSearcher *callback) { /* Determine fallback font using CoreText. This uses the language isocode * to find a suitable font. CoreText is available from 10.5 onwards. */ @@ -305,7 +302,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,7 +320,13 @@ public: if (name.starts_with(".") || name.starts_with("LastResort")) continue; /* Save result. */ - callback->SetFontNames(settings, name); + for (FontSize fs : bad_mask) { + GetFontCacheSubSetting(fs)->AddFallback(name, std::nullopt); + } + + // InitFontCache(bad_mask); + // return true; + if (!callback->FindMissingGlyphs()) { Debug(fontcache, 2, "CT-Font for {}: {}", language_isocode, name); result = true; @@ -335,7 +338,9 @@ 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"); + for (FontSize fs : bad_mask) { + GetFontCacheSubSetting(fs)->AddFallback("Arial Unicode MS", std::nullopt); + } result = !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..8568cd8a55 100644 --- a/src/os/macosx/string_osx.cpp +++ b/src/os/macosx/string_osx.cpp @@ -52,7 +52,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 +88,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->fc->GetSize()); } int GetGlyphCount() const override { return (int)this->glyphs.size(); } int GetAdvance() const { return this->total_advance; } }; @@ -137,17 +137,17 @@ public: /** Get the width of an encoded sprite font character. */ -static CGFloat SpriteFontGetWidth(void *ref_con) +static CGFloat CustomFontGetWidth(void *ref_con) { - FontSize fs = (FontSize)((size_t)ref_con >> 24); - char32_t c = (char32_t)((size_t)ref_con & 0xFFFFFF); + FontIndex fi = static_cast(reinterpret_cast(ref_con) >> 24); + char32_t c = static_cast(reinterpret_cast(ref_con) & 0xFFFFFF); - return GetGlyphWidth(fs, c); + return FontCache::Get(fi)->GetGlyphWidth(c); } static const CTRunDelegateCallbacks _sprite_font_callback = { kCTRunDelegateCurrentVersion, nullptr, nullptr, nullptr, - &SpriteFontGetWidth + &CustomFontGetWidth }; /* static */ std::unique_ptr CoreTextParagraphLayoutFactory::GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &font_mapping) @@ -158,11 +158,6 @@ static const CTRunDelegateCallbacks _sprite_font_callback = { ptrdiff_t length = buff_end - buff; if (length == 0) return nullptr; - /* Can't layout our in-built sprite fonts. */ - for (const auto &[position, font] : font_mapping) { - if (font->fc->IsBuiltInFont()) return nullptr; - } - /* Make attributed string with embedded font information. */ CFAutoRelease str(CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)); CFAttributedStringBeginEditing(str.get()); @@ -181,12 +176,12 @@ static const CTRunDelegateCallbacks _sprite_font_callback = { CTFontRef font_handle = static_cast(font->fc->GetOSHandle()); if (font_handle == nullptr) { - if (!_font_cache[font->fc->GetSize()]) { + if (!_font_cache[font->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)); + _font_cache[font->fc->GetIndex()].reset(CTFontCreateWithName(font_name.get(), font->fc->GetFontSize(), nullptr)); } - font_handle = _font_cache[font->fc->GetSize()].get(); + font_handle = _font_cache[font->fc->GetIndex()].get(); } CFAttributedStringSetAttribute(str.get(), CFRangeMake(last, position - last), kCTFontAttributeName, font_handle); @@ -194,10 +189,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 (font->fc->IsBuiltInFont()) { + for (ssize_t c = last; c < i.first; c++) { + CFAutoRelease del(CTRunDelegateCreate(&_custom_font_callback, static_cast(reinterpret_cast(buff[c] | (font->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 +242,15 @@ CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font CTRunGetAdvances(run, CFRangeMake(0, 0), advs); this->positions.reserve(this->glyphs.size()); + int y_offset = this->font->fc->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 27e23631bd..0da26027c3 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 FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) +bool FontConfigSetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, MissingGlyphSearcher *callback) { bool ret = false; @@ -132,22 +136,25 @@ bool FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &l 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(bad_mask); + /* 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 FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &l /* 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 (bad_mask.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,35 @@ bool FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &l 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; + + for (FontSize fs : bad_mask) { + GetFontCacheSubSetting(fs)->AddFallback(best_font, best_index); + } + + InitFontCache(bad_mask); - callback->SetFontNames(settings, best_font, &best_index); - InitFontCache(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 44562bfaac..596f3b7d58 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 FontConfigSetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback); +bool FontConfigSetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, MissingGlyphSearcher *callback); #endif /* WITH_FONTCONFIG */ diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index c60f32bade..1e4cafa801 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,15 @@ 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; + Debug(misc, 0, "Trying font {}", font_name); + for (FontSize fs : info->fontsizes) { + GetFontCacheSubSetting(fs)->AddFallback(font_name, logfont->elfLogFont); + } + + InitFontCache(info->fontsizes); + if (info->callback->FindMissingGlyphs().None()) { + return 1; + } Debug(fontcache, 1, "Fallback font: {}", font_name); return 0; // stop enumerating } @@ -156,10 +163,11 @@ void Win32FontCache::SetFontSize(int pixels) this->height = this->ascender + this->descender; this->glyph_size.cx = otm->otmTextMetrics.tmMaxCharWidth; this->glyph_size.cy = otm->otmTextMetrics.tmHeight; + FontCache::UpdateCharacterHeight(this->fs); 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 +254,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 +271,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 +279,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. - */ - void 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. + */ + void LoadFont(FontSize fs, FontType fonttype, bool search, const std::string &font, std::span os_handle) override { if (fonttype != FontType::TrueType) return; - 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; + 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; } } @@ -308,7 +312,7 @@ public: LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); } - bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_isocode, MissingGlyphSearcher *callback) override + bool SetFallbackFont(const std::string &language_isocode, FontSizes bad_mask, MissingGlyphSearcher *callback) override { Debug(fontcache, 1, "Trying fallback fonts"); EFCParam langInfo; @@ -318,7 +322,7 @@ public: Debug(fontcache, 1, "Can't get locale info for fallback font (isocode={})", language_isocode); return false; } - langInfo.settings = settings; + langInfo.fontsizes = bad_mask; 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..9905b50a85 100644 --- a/src/os/windows/string_uniscribe.cpp +++ b/src/os/windows/string_uniscribe.cpp @@ -10,6 +10,7 @@ #include "../../stdafx.h" #include "../../debug.h" #include "string_uniscribe.h" +#include "../../gfx_layout_fallback.h" #include "../../language.h" #include "../../strings_func.h" #include "../../string_func.h" @@ -29,7 +30,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 +53,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 +97,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->fc->GetSize()); } int GetGlyphCount() const override { return this->num_glyphs; } int GetAdvance() const { return this->total_advance; } }; @@ -130,12 +133,12 @@ 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. */ @@ -152,6 +155,41 @@ static HFONT HFontFromFont(Font *font) 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) +{ + 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(this->font->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 = this->font->fc->GetGlyphYOffset(); + int advance = 0; + for (const GlyphID glyph : this->glyphs) { + this->offsets.emplace_back(advance, y_offset); + int x_advance = this->font->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) { @@ -166,10 +204,15 @@ static bool UniscribeShapeRun(const UniscribeParagraphLayoutFactory::CharType *b HFONT old_font = nullptr; HFONT cur_font = nullptr; + if (range.font->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[range.font->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 +222,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[range.font->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 +230,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] = range.font->fc->GetGlyphWidth(range.glyphs[i]); #endif range.total_advance += range.advances[i]; } @@ -280,11 +313,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/string.cpp b/src/string.cpp index 16441e802f..d8ec0916c1 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -697,7 +697,7 @@ public: * break point, but we only want word starts. Move to the next location in * case the new position points to whitespace. */ while (pos != icu::BreakIterator::DONE && - IsWhitespace(Utf16DecodeChar((const uint16_t *)&this->utf16_str[pos]))) { + IsWhitespace(Utf16DecodeChar(&this->utf16_str[pos]))) { int32_t new_pos = this->word_itr->next(); /* Don't set it to DONE if it was valid before. Otherwise we'll return END * even though the iterator wasn't at the end of the string before. */ @@ -729,7 +729,7 @@ public: * break point, but we only want word starts. Move to the previous location in * case the new position points to whitespace. */ while (pos != icu::BreakIterator::DONE && - IsWhitespace(Utf16DecodeChar((const uint16_t *)&this->utf16_str[pos]))) { + IsWhitespace(Utf16DecodeChar(&this->utf16_str[pos]))) { int32_t new_pos = this->word_itr->previous(); /* Don't set it to DONE if it was valid before. Otherwise we'll return END * even though the iterator wasn't at the start of the string before. */ diff --git a/src/string_func.h b/src/string_func.h index 828d123505..ba64225fc1 100644 --- a/src/string_func.h +++ b/src/string_func.h @@ -94,7 +94,7 @@ inline char32_t Utf16DecodeSurrogate(uint lead, uint trail) * @param c Pointer to one or two UTF-16 code points. * @return Decoded Unicode character. */ -inline char32_t Utf16DecodeChar(const uint16_t *c) +inline char32_t Utf16DecodeChar(const char16_t *c) { if (Utf16IsLeadSurrogate(c[0])) { return Utf16DecodeSurrogate(c[0], c[1]); diff --git a/src/strings.cpp b/src/strings.cpp index 45ef83fc76..d00a014819 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2252,42 +2252,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() { - InitFontCache(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + for (FontSize size : this->font_sizes) { + GetFontCacheSubSetting(size)->fallback_fonts.clear(); + } + + InitFontCache(this->font_sizes); + + FontSizes bad_fontsizes{}; + + for (FontSize size : this->font_sizes) { + 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. */ - std::string size_name; - - switch (size) { - case FS_NORMAL: size_name = "medium"; break; - case FS_SMALL: size_name = "small"; break; - case FS_LARGE: size_name = "large"; break; - case FS_MONO: size_name = "mono"; break; - default: NOT_REACHED(); - } - - Debug(fontcache, 0, "Font is missing glyphs to display char 0x{:X} in {} font size", (int)c, size_name); - 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. @@ -2316,26 +2334,11 @@ 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 - } }; +static LanguagePackGlyphSearcher _language_pack_searcher; + + /** * Check whether the currently loaded language pack * uses characters that the currently loaded font @@ -2351,22 +2354,17 @@ class LanguagePackGlyphSearcher : public MissingGlyphSearcher { */ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher) { - static LanguagePackGlyphSearcher pack_searcher; - if (searcher == nullptr) searcher = &pack_searcher; - bool bad_font = !base_font || searcher->FindMissingGlyphs(); + if (searcher == nullptr) searcher = &_language_pack_searcher; + FontSizes bad_mask = searcher->FindMissingGlyphs(); + bool bad_font = bad_mask.Any(); + #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) - if (bad_font) { + if (bad_mask.Any()) { /* 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::SetFallbackFont(&_fcsettings, _langpack.langpack->isocode, searcher); - - _fcsettings = std::move(backup); + bad_font = !FontProviderManager::SetFallbackFont(_langpack.langpack->isocode, bad_mask, searcher); if (!bad_font && any_font_configured) { /* If the user configured a bad font, and we found a better one, @@ -2383,7 +2381,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); + InitFontCache(bad_mask); } } #endif @@ -2400,12 +2398,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(bad_mask); return; } /* Update the font with cache */ - LoadStringWidthTable(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + LoadStringWidthTable(searcher->font_sizes); #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 f813dc0fb5..240e476661 100644 --- a/src/strings_func.h +++ b/src/strings_func.h @@ -157,9 +157,31 @@ EncodedString GetEncodedString(StringID string, const Args&... args) */ class MissingGlyphSearcher { public: + FontSizes font_sizes; ///< Font sizes to search for. + + MissingGlyphSearcher(FontSizes font_sizes) : font_sizes(font_sizes) {} + /** 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 font_sizes) : MissingGlyphSearcher(font_sizes) {} + /** * Get the next string to search through. * @return The next string or nullopt if there is none. @@ -177,23 +199,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 c159742a31..bdfc579bdb 100644 --- a/src/tests/mock_fontcache.h +++ b/src/tests/mock_fontcache.h @@ -19,20 +19,22 @@ public: MockFontCache(FontSize fs) : FontCache(fs) { this->height = FontCache::GetDefaultFontHeight(this->fs); + FontCache::UpdateCharacterHeight(this->fs); } void ClearFontCache() override {} 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::caches[fs] == nullptr) new MockFontCache(fs); /* FontCache inserts itself into to the cache. */ + new MockFontCache(fs); /* FontCache inserts itself into to the cache. */ } } }; diff --git a/src/textfile_gui.cpp b/src/textfile_gui.cpp index 8725b55f4d..14e734f8a7 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;