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;