diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 52dfc9cffa..919f22684b 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2369,7 +2369,7 @@ static bool ConFont(std::span argv) FontCacheSubSetting *setting = GetFontCacheSubSetting(fs); /* Make sure all non sprite fonts are loaded. */ if (!setting->font.empty() && !fc->HasParent()) { - InitFontCache(fs); + FontCache::LoadFontCaches(fs); fc = FontCache::Get(fs); } IConsolePrint(CC_DEFAULT, "{} font:", FontSizeToName(fs)); diff --git a/src/fontcache.cpp b/src/fontcache.cpp index d1be5e6ac2..0fe53a7350 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -12,7 +12,6 @@ #include "fontdetection.h" #include "blitter/factory.hpp" #include "gfx_layout.h" -#include "fontcache/spritefontcache.h" #include "openttd.h" #include "settings_func.h" #include "strings_func.h" @@ -30,22 +29,19 @@ FontCacheSettings _fcsettings; /** - * Create a new font cache. - * @param fs The size of the font. + * Try loading a font with any fontcache factory. + * @param fs Font size to load. + * @param fonttype Font type requested. + * @return FontCache of the font if loaded, or nullptr. */ -FontCache::FontCache(FontSize fs) : parent(FontCache::Get(fs)), fs(fs) +/* static */ std::unique_ptr FontProviderManager::LoadFont(FontSize fs, FontType fonttype) { - assert(this->parent == nullptr || this->fs == this->parent->fs); - FontCache::caches[this->fs] = this; - Layouter::ResetFontCache(this->fs); -} + for (auto &provider : FontProviderManager::GetProviders()) { + auto fc = provider->LoadFont(fs, fonttype); + if (fc != nullptr) return fc; + } -/** Clean everything up. */ -FontCache::~FontCache() -{ - assert(this->parent == nullptr || this->fs == this->parent->fs); - FontCache::caches[this->fs] = this->parent; - Layouter::ResetFontCache(this->fs); + return nullptr; } int FontCache::GetDefaultFontHeight(FontSize fs) @@ -80,12 +76,16 @@ int GetCharacterHeight(FontSize size) } -/* static */ FontCache *FontCache::caches[FS_END]; +/* static */ std::array, FS_END> FontCache::caches{}; +/** + * Initialise font caches with the base sprite font cache for all sizes. + */ /* static */ void FontCache::InitializeFontCaches() { for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { - if (FontCache::caches[fs] == nullptr) new SpriteFontCache(fs); /* FontCache inserts itself into to the cache. */ + if (FontCache::Get(fs) != nullptr) continue; + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::Sprite)); } } @@ -126,7 +126,7 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) CheckForMissingGlyphs(); _fcsettings = std::move(backup); } else { - InitFontCache(fontsize); + FontCache::LoadFontCaches(fontsize); } LoadStringWidthTable(fontsize); @@ -136,15 +136,6 @@ void SetFont(FontSize fontsize, const std::string &font, uint size) if (_save_config) SaveToConfig(); } -#ifdef WITH_FREETYPE -extern void LoadFreeTypeFont(FontSize fs); -extern void UninitFreeType(); -#elif defined(_WIN32) -extern void LoadWin32Font(FontSize fs); -#elif defined(WITH_COCOA) -extern void LoadCoreTextFont(FontSize fs); -#endif - /** * Test if a font setting uses the default font. * @return true iff the font is not configured and no fallback font data is present. @@ -211,40 +202,57 @@ std::string GetFontCacheFontName(FontSize fs) return GetDefaultTruetypeFontFile(fs); } +/** + * Register a FontCache for its font size. + * @param fc FontCache to register. + */ +/* static */ void FontCache::Register(std::unique_ptr &&fc) +{ + if (fc == nullptr) return; + + FontSize fs = fc->fs; + + fc->parent = std::move(FontCache::caches[fs]); + FontCache::caches[fs] = std::move(fc); +} + /** * (Re)initialize the font cache related things, i.e. load the non-sprite fonts. * @param fontsizes Font sizes to be initialised. */ -void InitFontCache(FontSizes fontsizes) +/* static */ void FontCache::LoadFontCaches(FontSizes fontsizes) { FontCache::InitializeFontCaches(); for (FontSize fs : fontsizes) { - FontCache *fc = FontCache::Get(fs); - if (fc->HasParent()) delete fc; + Layouter::ResetFontCache(fs); -#ifdef WITH_FREETYPE - LoadFreeTypeFont(fs); -#elif defined(_WIN32) - LoadWin32Font(fs); -#elif defined(WITH_COCOA) - LoadCoreTextFont(fs); -#endif + /* Unload everything except the sprite font cache. */ + while (FontCache::Get(fs)->HasParent()) { + FontCache::caches[fs] = std::move(FontCache::caches[fs]->parent); + } + + FontCache::Register(FontProviderManager::LoadFont(fs, FontType::TrueType)); + } +} + +/** + * Clear cached information for the specified font caches. + * @param fontsizes Font sizes to clear. + */ +/* static */ void FontCache::ClearFontCaches(FontSizes fontsizes) +{ + for (FontSize fs : fontsizes) { + FontCache::Get(fs)->ClearFontCache(); } } /** * Free everything allocated w.r.t. fonts. */ -void UninitFontCache() +/* static */ void FontCache::UninitializeFontCaches() { - for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) { - while (FontCache::Get(fs) != nullptr) delete FontCache::Get(fs); - } - -#ifdef WITH_FREETYPE - UninitFreeType(); -#endif /* WITH_FREETYPE */ + std::ranges::generate(FontCache::caches, []() { return nullptr; }); } #if !defined(_WIN32) && !defined(__APPLE__) && !defined(WITH_FONTCONFIG) && !defined(WITH_COCOA) diff --git a/src/fontcache.h b/src/fontcache.h index 2ee0d240af..566dc86ccc 100644 --- a/src/fontcache.h +++ b/src/fontcache.h @@ -10,6 +10,7 @@ #ifndef FONTCACHE_H #define FONTCACHE_H +#include "provider_manager.h" #include "string_type.h" #include "spritecache.h" @@ -20,18 +21,23 @@ static const GlyphID SPRITE_GLYPH = 1U << 30; /** Font cache for basic fonts. */ class FontCache { protected: - static FontCache *caches[FS_END]; ///< All the font caches. - FontCache *parent; ///< The parent of this font cache. - const FontSize fs; ///< The size of the font. + static std::array, FS_END> caches; ///< All the font caches. + std::unique_ptrparent; ///< The parent of this font cache. + const FontSize fs; ///< The size of the font. int height = 0; ///< The height of the font. int ascender = 0; ///< The ascender value of the font. int descender = 0; ///< The descender value of the font. + FontCache(FontSize fs) : fs(fs) {} + static void Register(std::unique_ptr &&fc); + public: - FontCache(FontSize fs); - virtual ~FontCache(); + virtual ~FontCache() {} static void InitializeFontCaches(); + static void UninitializeFontCaches(); + static void LoadFontCaches(FontSizes fontsizes); + static void ClearFontCaches(FontSizes fontsizes); /** Default unscaled font heights. */ static const int DEFAULT_FONT_HEIGHT[FS_END]; @@ -124,7 +130,7 @@ public: static inline FontCache *Get(FontSize fs) { assert(fs < FS_END); - return FontCache::caches[fs]; + return FontCache::caches[fs].get(); } static std::string GetName(FontSize fs); @@ -143,13 +149,6 @@ public: virtual bool IsBuiltInFont() = 0; }; -inline void ClearFontCache(FontSizes fontsizes) -{ - for (FontSize fs : fontsizes) { - FontCache::Get(fs)->ClearFontCache(); - } -} - /** Get the Sprite for a glyph */ inline const Sprite *GetGlyph(FontSize size, char32_t key) { @@ -207,12 +206,37 @@ inline FontCacheSubSetting *GetFontCacheSubSetting(FontSize fs) uint GetFontCacheFontSize(FontSize fs); std::string GetFontCacheFontName(FontSize fs); -void InitFontCache(FontSizes fontsizes); -void UninitFontCache(); bool GetFontAAState(); void SetFont(FontSize fontsize, const std::string &font, uint size); +/** Different types of font that can be loaded. */ +enum class FontType : uint8_t { + Sprite, ///< Bitmap sprites from GRF files. + TrueType, ///< Scalable TrueType fonts. +}; + +/** Factory for FontCaches. */ +class FontCacheFactory : public BaseProvider { +public: + FontCacheFactory(std::string_view name, std::string_view description) : BaseProvider(name, description) + { + ProviderManager::Register(*this); + } + + virtual ~FontCacheFactory() + { + ProviderManager::Unregister(*this); + } + + virtual std::unique_ptr LoadFont(FontSize fs, FontType fonttype) = 0; +}; + +class FontProviderManager : ProviderManager { +public: + static std::unique_ptr LoadFont(FontSize fs, FontType fonttype); +}; + /* Implemented in spritefontcache.cpp */ void InitializeUnicodeGlyphMap(); void SetUnicodeGlyph(FontSize size, char32_t key, SpriteID sprite); diff --git a/src/fontcache/freetypefontcache.cpp b/src/fontcache/freetypefontcache.cpp index 80bb2cda6d..34c628b651 100644 --- a/src/fontcache/freetypefontcache.cpp +++ b/src/fontcache/freetypefontcache.cpp @@ -46,9 +46,6 @@ public: const void *GetOSHandle() override { return &face; } }; -FT_Library _ft_library = nullptr; - - /** * Create a new FreeTypeFontCache. * @param fs The font size that is going to be cached. @@ -113,93 +110,6 @@ void FreeTypeFontCache::SetFontSize(int pixels) } } -static FT_Error LoadFont(FontSize fs, FT_Face face, std::string_view font_name, uint size) -{ - Debug(fontcache, 2, "Requested '{}', using '{} {}'", font_name, face->family_name, face->style_name); - - /* Attempt to select the unicode character map */ - FT_Error error = FT_Select_Charmap(face, ft_encoding_unicode); - if (error == FT_Err_Ok) goto found_face; // Success - - if (error == FT_Err_Invalid_CharMap_Handle) { - /* Try to pick a different character map instead. We default to - * the first map, but platform_id 0 encoding_id 0 should also - * be unicode (strange system...) */ - FT_CharMap found = face->charmaps[0]; - int i; - - for (i = 0; i < face->num_charmaps; i++) { - FT_CharMap charmap = face->charmaps[i]; - if (charmap->platform_id == 0 && charmap->encoding_id == 0) { - found = charmap; - } - } - - if (found != nullptr) { - error = FT_Set_Charmap(face, found); - if (error == FT_Err_Ok) goto found_face; - } - } - - FT_Done_Face(face); - return error; - -found_face: - new FreeTypeFontCache(fs, face, size); - return FT_Err_Ok; -} - -/** - * Loads the freetype font. - * First type to load the fontname as if it were a path. If that fails, - * try to resolve the filename of the font using fontconfig, where the - * format is 'font family name' or 'font family name, font style'. - * @param fs The font size to load. - */ -void LoadFreeTypeFont(FontSize fs) -{ - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); - - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return; - - if (_ft_library == nullptr) { - if (FT_Init_FreeType(&_ft_library) != FT_Err_Ok) { - ShowInfo("Unable to initialize FreeType, using sprite fonts instead"); - return; - } - - Debug(fontcache, 2, "Initialized"); - } - - FT_Face face = nullptr; - - /* If font is an absolute path to a ttf, try loading that first. */ - int32_t index = 0; - if (settings->os_handle != nullptr) index = *static_cast(settings->os_handle); - FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face); - - if (error != FT_Err_Ok) { - /* Check if font is a relative filename in one of our search-paths. */ - std::string full_font = FioFindFullPath(BASE_DIR, font); - if (!full_font.empty()) { - error = FT_New_Face(_ft_library, full_font.c_str(), 0, &face); - } - } - - /* Try loading based on font face name (OS-wide fonts). */ - if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face); - - if (error == FT_Err_Ok) { - error = LoadFont(fs, face, font, GetFontCacheFontSize(fs)); - if (error != FT_Err_Ok) { - ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}, using sprite font instead", font, FontSizeToName(fs), error); - } - } else { - FT_Done_Face(face); - } -} - /** * Free everything that was allocated for this font cache. */ @@ -296,14 +206,106 @@ GlyphID FreeTypeFontCache::MapCharToGlyph(char32_t key, bool allow_fallback) return glyph; } -/** - * Free everything allocated w.r.t. freetype. - */ -void UninitFreeType() -{ - FT_Done_FreeType(_ft_library); - _ft_library = nullptr; -} +FT_Library _ft_library = nullptr; + +class FreeTypeFontCacheFactory : public FontCacheFactory { +public: + FreeTypeFontCacheFactory() : FontCacheFactory("freetype", "FreeType font provider") {} + + virtual ~FreeTypeFontCacheFactory() + { + FT_Done_FreeType(_ft_library); + _ft_library = nullptr; + } + + /** + * Loads the freetype font. + * First type to load the fontname as if it were a path. If that fails, + * try to resolve the filename of the font using fontconfig, where the + * format is 'font family name' or 'font family name, font style'. + * @param fs The font size to load. + */ + std::unique_ptr LoadFont(FontSize fs, FontType fonttype) override + { + if (fonttype != FontType::TrueType) return nullptr; + + FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); + + std::string font = GetFontCacheFontName(fs); + if (font.empty()) return nullptr; + + if (_ft_library == nullptr) { + if (FT_Init_FreeType(&_ft_library) != FT_Err_Ok) { + ShowInfo("Unable to initialize FreeType, using sprite fonts instead"); + return nullptr; + } + + Debug(fontcache, 2, "Initialized"); + } + + FT_Face face = nullptr; + + /* If font is an absolute path to a ttf, try loading that first. */ + int32_t index = 0; + if (settings->os_handle != nullptr) index = *static_cast(settings->os_handle); + FT_Error error = FT_New_Face(_ft_library, font.c_str(), index, &face); + + if (error != FT_Err_Ok) { + /* Check if font is a relative filename in one of our search-paths. */ + std::string full_font = FioFindFullPath(BASE_DIR, font); + if (!full_font.empty()) { + error = FT_New_Face(_ft_library, full_font.c_str(), 0, &face); + } + } + + /* Try loading based on font face name (OS-wide fonts). */ + if (error != FT_Err_Ok) error = GetFontByFaceName(font, &face); + + if (error != FT_Err_Ok) { + FT_Done_Face(face); + return nullptr; + } + + return LoadFont(fs, face, font, GetFontCacheFontSize(fs)); + } + +private: + static std::unique_ptr LoadFont(FontSize fs, FT_Face face, std::string_view font_name, uint size) + { + Debug(fontcache, 2, "Requested '{}', using '{} {}'", font_name, face->family_name, face->style_name); + + /* Attempt to select the unicode character map */ + FT_Error error = FT_Select_Charmap(face, ft_encoding_unicode); + if (error == FT_Err_Invalid_CharMap_Handle) { + /* Try to pick a different character map instead. We default to + * the first map, but platform_id 0 encoding_id 0 should also + * be unicode (strange system...) */ + FT_CharMap found = face->charmaps[0]; + + for (int i = 0; i < face->num_charmaps; ++i) { + FT_CharMap charmap = face->charmaps[i]; + if (charmap->platform_id == 0 && charmap->encoding_id == 0) { + found = charmap; + } + } + + if (found != nullptr) { + error = FT_Set_Charmap(face, found); + } + } + + if (error != FT_Err_Ok) { + FT_Done_Face(face); + + ShowInfo("Unable to use '{}' for {} font, FreeType reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), error); + return nullptr; + } + + return std::make_unique(fs, face, size); + } +}; + +static FreeTypeFontCacheFactory s_freetype_fontcache_factory; #if !defined(WITH_FONTCONFIG) diff --git a/src/fontcache/spritefontcache.cpp b/src/fontcache/spritefontcache.cpp index e2d93189c2..37bb5a9a6f 100644 --- a/src/fontcache/spritefontcache.cpp +++ b/src/fontcache/spritefontcache.cpp @@ -151,3 +151,17 @@ bool SpriteFontCache::GetDrawGlyphShadow() { return false; } + +class SpriteFontCacheFactory : public FontCacheFactory { +public: + SpriteFontCacheFactory() : FontCacheFactory("sprite", "Sprite font provider") {} + + std::unique_ptr LoadFont(FontSize fs, FontType fonttype) override + { + if (fonttype != FontType::Sprite) return nullptr; + + return std::make_unique(fs); + } +}; + +static SpriteFontCacheFactory s_sprite_fontcache_factory; diff --git a/src/gfx.cpp b/src/gfx.cpp index 9ba985e971..d04ed6733c 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -1246,7 +1246,7 @@ static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, */ void LoadStringWidthTable(FontSizes fontsizes) { - ClearFontCache(fontsizes); + FontCache::ClearFontCaches(fontsizes); for (FontSize fs : fontsizes) { for (uint i = 0; i != 224; i++) { @@ -1813,7 +1813,7 @@ bool AdjustGUIZoom(bool automatic) if (old_font_zoom != _font_zoom) { GfxClearFontSpriteCache(); } - ClearFontCache(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); LoadStringWidthTable(); SetupWidgetDimensions(); diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp index 20c5e69f11..6620876e4d 100644 --- a/src/gfxinit.cpp +++ b/src/gfxinit.cpp @@ -245,7 +245,7 @@ static void RealChangeBlitter(std::string_view repl_blitter) /* Clear caches that might have sprites for another blitter. */ VideoDriver::GetInstance()->ClearSystemSprites(); - ClearFontCache(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); GfxClearSpriteCache(); ReInitAllWindows(false); } @@ -326,7 +326,7 @@ void CheckBlitter() { if (!SwitchNewGRFBlitter()) return; - ClearFontCache(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); GfxClearSpriteCache(); ReInitAllWindows(false); } @@ -338,7 +338,7 @@ void GfxLoadSprites() SwitchNewGRFBlitter(); VideoDriver::GetInstance()->ClearSystemSprites(); - ClearFontCache(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); GfxInitSpriteMem(); LoadSpriteTables(); GfxInitPalettes(); diff --git a/src/openttd.cpp b/src/openttd.cpp index 533ee0446a..e05daac567 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -315,7 +315,7 @@ static void ShutdownGame() /* No NewGRFs were loaded when it was still bootstrapping. */ if (_game_mode != GM_BOOTSTRAP) ResetNewGRFData(); - UninitFontCache(); + FontCache::UninitializeFontCaches(); } /** @@ -700,7 +700,7 @@ int openttd_main(std::span arguments) InitializeLanguagePacks(); /* Initialize the font cache */ - InitFontCache(FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(FONTSIZES_REQUIRED); /* This must be done early, since functions use the SetWindowDirty* calls */ InitWindowSystem(); diff --git a/src/os/macosx/font_osx.cpp b/src/os/macosx/font_osx.cpp index 6588eb7507..c969f9f1db 100644 --- a/src/os/macosx/font_osx.cpp +++ b/src/os/macosx/font_osx.cpp @@ -287,88 +287,98 @@ const Sprite *CoreTextFontCache::InternalGetGlyph(GlyphID key, bool use_aa) return this->SetGlyphPtr(key, std::move(new_glyph)).GetSprite(); } -static CTFontDescriptorRef LoadFontFromFile(const std::string &font_name) -{ - if (!MacOSVersionIsAtLeast(10, 6, 0)) return nullptr; +class CoreTextFontCacheFactory : public FontCacheFactory { +public: + CoreTextFontCacheFactory() : FontCacheFactory("coretext", "CoreText font loader") {} - /* Might be a font file name, try load it. Direct font loading is - * only supported starting on OSX 10.6. */ - CFAutoRelease path; + /** + * Loads the TrueType font. + * If a CoreText font description is present, e.g. from the automatic font + * fallback search, use it. Otherwise, try to resolve it by font name. + * @param fs The font size to load. + */ + std::unique_ptr LoadFont(FontSize fs, FontType fonttype) override + { + if (fonttype != FontType::TrueType) return nullptr; - /* See if this is an absolute path. */ - if (FileExists(font_name)) { - path.reset(CFStringCreateWithCString(kCFAllocatorDefault, font_name.c_str(), kCFStringEncodingUTF8)); - } else { - /* Scan the search-paths to see if it can be found. */ - std::string full_font = FioFindFullPath(BASE_DIR, font_name); - if (!full_font.empty()) { - path.reset(CFStringCreateWithCString(kCFAllocatorDefault, full_font.c_str(), kCFStringEncodingUTF8)); + FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); + + std::string font = GetFontCacheFontName(fs); + if (font.empty()) return nullptr; + + CFAutoRelease font_ref; + + if (settings->os_handle != nullptr) { + font_ref.reset(static_cast(const_cast(settings->os_handle))); + CFRetain(font_ref.get()); // Increase ref count to match a later release. } - } - if (path) { - /* Try getting a font descriptor to see if the system can use it. */ - CFAutoRelease url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle, false)); - CFAutoRelease descs(CTFontManagerCreateFontDescriptorsFromURL(url.get())); - - if (descs && CFArrayGetCount(descs.get()) > 0) { - CTFontDescriptorRef font_ref = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0); - CFRetain(font_ref); - return font_ref; + if (!font_ref && MacOSVersionIsAtLeast(10, 6, 0)) { + /* Might be a font file name, try load it. */ + font_ref.reset(LoadFontFromFile(font)); + if (!font_ref) ShowInfo("Unable to load file '{}' for {} font, using default OS font selection instead", font, FontSizeToName(fs)); } - } - return nullptr; -} + if (!font_ref) { + CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, font.c_str(), kCFStringEncodingUTF8)); -/** - * Loads the TrueType font. - * If a CoreText font description is present, e.g. from the automatic font - * fallback search, use it. Otherwise, try to resolve it by font name. - * @param fs The font size to load. - */ -void LoadCoreTextFont(FontSize fs) -{ - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); + /* Simply creating the font using CTFontCreateWithNameAndSize will *always* return + * something, no matter the name. As such, we can't use it to check for existence. + * We instead query the list of all font descriptors that match the given name which + * does not do this stupid name fallback. */ + CFAutoRelease name_desc(CTFontDescriptorCreateWithNameAndSize(name.get(), 0.0)); + CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontNameAttribute)), 1, &kCFTypeSetCallBacks)); + CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(name_desc.get(), mandatory_attribs.get())); - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return; - - CFAutoRelease font_ref; - - if (settings->os_handle != nullptr) { - font_ref.reset(static_cast(const_cast(settings->os_handle))); - CFRetain(font_ref.get()); // Increase ref count to match a later release. - } - - if (!font_ref && MacOSVersionIsAtLeast(10, 6, 0)) { - /* Might be a font file name, try load it. */ - font_ref.reset(LoadFontFromFile(font)); - if (!font_ref) ShowInfo("Unable to load file '{}' for {} font, using default OS font selection instead", font, FontSizeToName(fs)); - } - - if (!font_ref) { - CFAutoRelease name(CFStringCreateWithCString(kCFAllocatorDefault, font.c_str(), kCFStringEncodingUTF8)); - - /* Simply creating the font using CTFontCreateWithNameAndSize will *always* return - * something, no matter the name. As such, we can't use it to check for existence. - * We instead query the list of all font descriptors that match the given name which - * does not do this stupid name fallback. */ - CFAutoRelease name_desc(CTFontDescriptorCreateWithNameAndSize(name.get(), 0.0)); - CFAutoRelease mandatory_attribs(CFSetCreate(kCFAllocatorDefault, const_cast(reinterpret_cast(&kCTFontNameAttribute)), 1, &kCFTypeSetCallBacks)); - CFAutoRelease descs(CTFontDescriptorCreateMatchingFontDescriptors(name_desc.get(), mandatory_attribs.get())); - - /* Assume the first result is the one we want. */ - if (descs && CFArrayGetCount(descs.get()) > 0) { - font_ref.reset((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0)); - CFRetain(font_ref.get()); + /* Assume the first result is the one we want. */ + if (descs && CFArrayGetCount(descs.get()) > 0) { + font_ref.reset((CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0)); + CFRetain(font_ref.get()); + } } + + if (!font_ref) { + ShowInfo("Unable to use '{}' for {} font, using sprite font instead", font, FontSizeToName(fs)); + return nullptr; + } + + return std::make_unique(fs, std::move(font_ref), GetFontCacheFontSize(fs)); } - if (!font_ref) { - ShowInfo("Unable to use '{}' for {} font, using sprite font instead", font, FontSizeToName(fs)); - return; - } +private: + static CTFontDescriptorRef LoadFontFromFile(const std::string &font_name) + { + if (!MacOSVersionIsAtLeast(10, 6, 0)) return nullptr; - new CoreTextFontCache(fs, std::move(font_ref), GetFontCacheFontSize(fs)); -} + /* Might be a font file name, try load it. Direct font loading is + * only supported starting on OSX 10.6. */ + CFAutoRelease path; + + /* See if this is an absolute path. */ + if (FileExists(font_name)) { + path.reset(CFStringCreateWithCString(kCFAllocatorDefault, font_name.c_str(), kCFStringEncodingUTF8)); + } else { + /* Scan the search-paths to see if it can be found. */ + std::string full_font = FioFindFullPath(BASE_DIR, font_name); + if (!full_font.empty()) { + path.reset(CFStringCreateWithCString(kCFAllocatorDefault, full_font.c_str(), kCFStringEncodingUTF8)); + } + } + + if (path) { + /* Try getting a font descriptor to see if the system can use it. */ + CFAutoRelease url(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path.get(), kCFURLPOSIXPathStyle, false)); + CFAutoRelease descs(CTFontManagerCreateFontDescriptorsFromURL(url.get())); + + if (descs && CFArrayGetCount(descs.get()) > 0) { + CTFontDescriptorRef font_ref = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descs.get(), 0); + CFRetain(font_ref); + return font_ref; + } + } + + return nullptr; + } +}; + +static CoreTextFontCacheFactory s_coretext_fontcache_Factory; diff --git a/src/os/unix/font_unix.cpp b/src/os/unix/font_unix.cpp index feb96713e5..645386066f 100644 --- a/src/os/unix/font_unix.cpp +++ b/src/os/unix/font_unix.cpp @@ -182,6 +182,6 @@ bool SetFallbackFont(FontCacheSettings *settings, const std::string &language_is if (best_font == nullptr) return false; callback->SetFontNames(settings, best_font, &best_index); - InitFontCache(callback->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(callback->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); return true; } diff --git a/src/os/windows/font_win32.cpp b/src/os/windows/font_win32.cpp index 6358ca4d3d..d8074991ce 100644 --- a/src/os/windows/font_win32.cpp +++ b/src/os/windows/font_win32.cpp @@ -293,99 +293,109 @@ void Win32FontCache::ClearFontCache() return allow_fallback && key >= SCC_SPRITE_START && key <= SCC_SPRITE_END ? this->parent->MapCharToGlyph(key) : 0; } +class Win32FontCacheFactory : FontCacheFactory { +public: + Win32FontCacheFactory() : FontCacheFactory("win32", "Win32 font loader") {} -static bool TryLoadFontFromFile(const std::string &font_name, LOGFONT &logfont) -{ - wchar_t fontPath[MAX_PATH] = {}; + /** + * Loads the GDI font. + * If a GDI font description is present, e.g. from the automatic font + * fallback search, use it. Otherwise, try to resolve it by font name. + * @param fs The font size to load. + */ + std::unique_ptr LoadFont(FontSize fs, FontType fonttype) override + { + if (fonttype != FontType::TrueType) return nullptr; - /* See if this is an absolute path. */ - if (FileExists(font_name)) { - convert_to_fs(font_name, fontPath); - } else { - /* Scan the search-paths to see if it can be found. */ - std::string full_font = FioFindFullPath(BASE_DIR, font_name); - if (!full_font.empty()) { - convert_to_fs(font_name, fontPath); + FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); + + std::string font = GetFontCacheFontName(fs); + if (font.empty()) return nullptr; + + LOGFONT logfont{}; + logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH; + logfont.lfCharSet = DEFAULT_CHARSET; + logfont.lfOutPrecision = OUT_OUTLINE_PRECIS; + logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + + if (settings->os_handle != nullptr) { + logfont = *(const LOGFONT *)settings->os_handle; + } else if (font.find('.') != std::string::npos) { + /* Might be a font file name, try load it. */ + if (!TryLoadFontFromFile(font, logfont)) { + ShowInfo("Unable to load file '{}' for {} font, using default windows font selection instead", font, FontSizeToName(fs)); + } } + + if (logfont.lfFaceName[0] == 0) { + logfont.lfWeight = StrContainsIgnoreCase(font, " bold") ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. + convert_to_fs(font, logfont.lfFaceName); + } + + return LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); } - if (fontPath[0] != 0) { - if (AddFontResourceEx(fontPath, FR_PRIVATE, 0) != 0) { - /* Try a nice little undocumented function first for getting the internal font name. - * Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */ - static LibraryLoader _gdi32("gdi32.dll"); - typedef BOOL(WINAPI *PFNGETFONTRESOURCEINFO)(LPCTSTR, LPDWORD, LPVOID, DWORD); - static PFNGETFONTRESOURCEINFO GetFontResourceInfo = _gdi32.GetFunction("GetFontResourceInfoW"); +private: + static std::unique_ptr LoadWin32Font(FontSize fs, const LOGFONT &logfont, uint size, std::string_view font_name) + { + HFONT font = CreateFontIndirect(&logfont); + if (font == nullptr) { + ShowInfo("Unable to use '{}' for {} font, Win32 reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), GetLastError()); + return nullptr; + } + DeleteObject(font); - if (GetFontResourceInfo != nullptr) { - /* Try to query an array of LOGFONTs that describe the file. */ - DWORD len = 0; - if (GetFontResourceInfo(fontPath, &len, nullptr, 2) && len >= sizeof(LOGFONT)) { - LOGFONT *buf = (LOGFONT *)new uint8_t[len]; - if (GetFontResourceInfo(fontPath, &len, buf, 2)) { - logfont = *buf; // Just use first entry. + return std::make_unique(fs, logfont, size); + } + + static bool TryLoadFontFromFile(const std::string &font_name, LOGFONT &logfont) + { + wchar_t fontPath[MAX_PATH] = {}; + + /* See if this is an absolute path. */ + if (FileExists(font_name)) { + convert_to_fs(font_name, fontPath); + } else { + /* Scan the search-paths to see if it can be found. */ + std::string full_font = FioFindFullPath(BASE_DIR, font_name); + if (!full_font.empty()) { + convert_to_fs(font_name, fontPath); + } + } + + if (fontPath[0] != 0) { + if (AddFontResourceEx(fontPath, FR_PRIVATE, 0) != 0) { + /* Try a nice little undocumented function first for getting the internal font name. + * Some documentation is found at: http://www.undocprint.org/winspool/getfontresourceinfo */ + static LibraryLoader _gdi32("gdi32.dll"); + typedef BOOL(WINAPI *PFNGETFONTRESOURCEINFO)(LPCTSTR, LPDWORD, LPVOID, DWORD); + static PFNGETFONTRESOURCEINFO GetFontResourceInfo = _gdi32.GetFunction("GetFontResourceInfoW"); + + if (GetFontResourceInfo != nullptr) { + /* Try to query an array of LOGFONTs that describe the file. */ + DWORD len = 0; + if (GetFontResourceInfo(fontPath, &len, nullptr, 2) && len >= sizeof(LOGFONT)) { + LOGFONT *buf = (LOGFONT *)new uint8_t[len]; + if (GetFontResourceInfo(fontPath, &len, buf, 2)) { + logfont = *buf; // Just use first entry. + } + delete[](uint8_t *)buf; } - delete[](uint8_t *)buf; + } + + /* No dice yet. Use the file name as the font face name, hoping it matches. */ + if (logfont.lfFaceName[0] == 0) { + wchar_t fname[_MAX_FNAME]; + _wsplitpath(fontPath, nullptr, nullptr, fname, nullptr); + + wcsncpy_s(logfont.lfFaceName, lengthof(logfont.lfFaceName), fname, _TRUNCATE); + logfont.lfWeight = StrContainsIgnoreCase(font_name, " bold") || StrContainsIgnoreCase(font_name, "-bold") ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. } } - - /* No dice yet. Use the file name as the font face name, hoping it matches. */ - if (logfont.lfFaceName[0] == 0) { - wchar_t fname[_MAX_FNAME]; - _wsplitpath(fontPath, nullptr, nullptr, fname, nullptr); - - wcsncpy_s(logfont.lfFaceName, lengthof(logfont.lfFaceName), fname, _TRUNCATE); - logfont.lfWeight = StrContainsIgnoreCase(font_name, " bold") || StrContainsIgnoreCase(font_name, "-bold") ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. - } } + + return logfont.lfFaceName[0] != 0; } +}; - return logfont.lfFaceName[0] != 0; -} - -static void LoadWin32Font(FontSize fs, const LOGFONT &logfont, uint size, std::string_view font_name) -{ - HFONT font = CreateFontIndirect(&logfont); - if (font == nullptr) { - ShowInfo("Unable to use '{}' for {} font, Win32 reported error 0x{:X}, using sprite font instead", font_name, FontSizeToName(fs), GetLastError()); - return; - } - DeleteObject(font); - - new Win32FontCache(fs, logfont, size); -} -/** - * Loads the GDI font. - * If a GDI font description is present, e.g. from the automatic font - * fallback search, use it. Otherwise, try to resolve it by font name. - * @param fs The font size to load. - */ -void LoadWin32Font(FontSize fs) -{ - FontCacheSubSetting *settings = GetFontCacheSubSetting(fs); - - std::string font = GetFontCacheFontName(fs); - if (font.empty()) return; - - LOGFONT logfont{}; - logfont.lfPitchAndFamily = fs == FS_MONO ? FIXED_PITCH : VARIABLE_PITCH; - logfont.lfCharSet = DEFAULT_CHARSET; - logfont.lfOutPrecision = OUT_OUTLINE_PRECIS; - logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; - - if (settings->os_handle != nullptr) { - logfont = *(const LOGFONT *)settings->os_handle; - } else if (font.find('.') != std::string::npos) { - /* Might be a font file name, try load it. */ - if (!TryLoadFontFromFile(font, logfont)) { - ShowInfo("Unable to load file '{}' for {} font, using default windows font selection instead", font, FontSizeToName(fs)); - } - } - - if (logfont.lfFaceName[0] == 0) { - logfont.lfWeight = StrContainsIgnoreCase(font, " bold") ? FW_BOLD : FW_NORMAL; // Poor man's way to allow selecting bold fonts. - convert_to_fs(font, logfont.lfFaceName); - } - - LoadWin32Font(fs, logfont, GetFontCacheFontSize(fs), font); -} +static Win32FontCacheFactory s_win32_fontcache_factory; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 3e9353d488..38c5a6de9f 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1036,8 +1036,8 @@ struct GameOptionsWindow : Window { this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA, _fcsettings.prefer_sprite); this->SetDirty(); - InitFontCache(FONTSIZES_ALL); - ClearFontCache(FONTSIZES_ALL); + FontCache::LoadFontCaches(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); CheckForMissingGlyphs(); SetupWidgetDimensions(); UpdateAllVirtCoords(); @@ -1050,7 +1050,7 @@ struct GameOptionsWindow : Window { this->SetWidgetLoweredState(WID_GO_GUI_FONT_AA, _fcsettings.global_aa); MarkWholeScreenDirty(); - ClearFontCache(FONTSIZES_ALL); + FontCache::ClearFontCaches(FONTSIZES_ALL); break; #endif /* HAS_TRUETYPE_FONT */ diff --git a/src/strings.cpp b/src/strings.cpp index 69d605e86d..a8ac71841d 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2278,7 +2278,7 @@ std::string_view GetCurrentLanguageIsoCode() */ bool MissingGlyphSearcher::FindMissingGlyphs() { - InitFontCache(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(this->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); this->Reset(); for (auto text = this->NextString(); text.has_value(); text = this->NextString()) { @@ -2395,7 +2395,7 @@ void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher) /* Our fallback font does miss characters too, so keep the * user chosen font as that is more likely to be any good than * the wild guess we made */ - InitFontCache(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); + FontCache::LoadFontCaches(searcher->Monospace() ? FontSizes{FS_MONO} : FONTSIZES_REQUIRED); } } #endif diff --git a/src/tests/mock_fontcache.h b/src/tests/mock_fontcache.h index c159742a31..b009cfc33e 100644 --- a/src/tests/mock_fontcache.h +++ b/src/tests/mock_fontcache.h @@ -32,7 +32,8 @@ public: static void InitializeFontCaches() { for (FontSize fs = FS_BEGIN; fs != FS_END; fs++) { - if (FontCache::caches[fs] == nullptr) new MockFontCache(fs); /* FontCache inserts itself into to the cache. */ + if (FontCache::Get(fs) != nullptr) continue; + FontCache::caches[fs] = std::make_unique(fs); } } };