/***************************************************************************** * Copyright (c) 2014-2020 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #ifndef NO_TTF # include # include # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdocumentation" # include # include FT_FREETYPE_H # pragma clang diagnostic pop # include "../OpenRCT2.h" # include "../config/Config.h" # include "../core/String.hpp" # include "../localisation/Localisation.h" # include "../localisation/LocalisationService.h" # include "../platform/platform.h" # include "TTF.h" static bool _ttfInitialised = false; # define TTF_SURFACE_CACHE_SIZE 256 # define TTF_GETWIDTH_CACHE_SIZE 1024 struct ttf_cache_entry { TTFSurface* surface; TTF_Font* font; utf8* text; uint32_t lastUseTick; }; struct ttf_getwidth_cache_entry { uint32_t width; TTF_Font* font; utf8* text; uint32_t lastUseTick; }; static ttf_cache_entry _ttfSurfaceCache[TTF_SURFACE_CACHE_SIZE] = {}; static int32_t _ttfSurfaceCacheCount = 0; static int32_t _ttfSurfaceCacheHitCount = 0; static int32_t _ttfSurfaceCacheMissCount = 0; static ttf_getwidth_cache_entry _ttfGetWidthCache[TTF_GETWIDTH_CACHE_SIZE] = {}; static int32_t _ttfGetWidthCacheCount = 0; static int32_t _ttfGetWidthCacheHitCount = 0; static int32_t _ttfGetWidthCacheMissCount = 0; static std::mutex _mutex; static TTF_Font* ttf_open_font(const utf8* fontPath, int32_t ptSize); static void ttf_close_font(TTF_Font* font); static void ttf_surface_cache_dispose(ttf_cache_entry* entry); static void ttf_surface_cache_dispose_all(); static void ttf_getwidth_cache_dispose_all(); static bool ttf_get_size(TTF_Font* font, std::string_view text, int32_t* outWidth, int32_t* outHeight); static void ttf_toggle_hinting(bool); static TTFSurface* ttf_render(TTF_Font* font, std::string_view text); template class FontLockHelper { T& _mutex; const bool _enabled; public: FontLockHelper(T& mutex) : _mutex(mutex) , _enabled(gConfigGeneral.multithreading) { if (_enabled) _mutex.lock(); } ~FontLockHelper() { if (_enabled) _mutex.unlock(); } }; static void ttf_toggle_hinting(bool) { if (!LocalisationService_UseTrueTypeFont()) { return; } for (int32_t i = 0; i < FONT_SIZE_COUNT; i++) { TTFFontDescriptor* fontDesc = &(gCurrentTTFFontSet->size[i]); bool use_hinting = gConfigFonts.enable_hinting && fontDesc->hinting_threshold; TTF_SetFontHinting(fontDesc->font, use_hinting ? 1 : 0); } if (_ttfSurfaceCacheCount) { ttf_surface_cache_dispose_all(); } } bool ttf_initialise() { FontLockHelper lock(_mutex); if (_ttfInitialised) return true; if (TTF_Init() != 0) { log_error("Couldn't initialise FreeType engine"); return false; } for (int32_t i = 0; i < FONT_SIZE_COUNT; i++) { TTFFontDescriptor* fontDesc = &(gCurrentTTFFontSet->size[i]); utf8 fontPath[MAX_PATH]; if (!platform_get_font_path(fontDesc, fontPath, sizeof(fontPath))) { log_verbose("Unable to load font '%s'", fontDesc->font_name); return false; } fontDesc->font = ttf_open_font(fontPath, fontDesc->ptSize); if (fontDesc->font == nullptr) { log_verbose("Unable to load '%s'", fontPath); return false; } } ttf_toggle_hinting(true); _ttfInitialised = true; return true; } void ttf_dispose() { FontLockHelper lock(_mutex); if (!_ttfInitialised) return; ttf_surface_cache_dispose_all(); ttf_getwidth_cache_dispose_all(); for (int32_t i = 0; i < FONT_SIZE_COUNT; i++) { TTFFontDescriptor* fontDesc = &(gCurrentTTFFontSet->size[i]); if (fontDesc->font != nullptr) { ttf_close_font(fontDesc->font); fontDesc->font = nullptr; } } TTF_Quit(); _ttfInitialised = false; } static TTF_Font* ttf_open_font(const utf8* fontPath, int32_t ptSize) { return TTF_OpenFont(fontPath, ptSize); } static void ttf_close_font(TTF_Font* font) { TTF_CloseFont(font); } static uint32_t ttf_surface_cache_hash(TTF_Font* font, std::string_view text) { uint32_t hash = static_cast(((reinterpret_cast(font) * 23) ^ 0xAAAAAAAA) & 0xFFFFFFFF); for (auto c : text) { hash = ror32(hash, 3) ^ (c * 13); } return hash; } static void ttf_surface_cache_dispose(ttf_cache_entry* entry) { if (entry->surface != nullptr) { ttf_free_surface(entry->surface); free(entry->text); entry->surface = nullptr; entry->font = nullptr; entry->text = nullptr; } } static void ttf_surface_cache_dispose_all() { for (int32_t i = 0; i < TTF_SURFACE_CACHE_SIZE; i++) { ttf_surface_cache_dispose(&_ttfSurfaceCache[i]); _ttfSurfaceCacheCount--; } } void ttf_toggle_hinting() { FontLockHelper lock(_mutex); ttf_toggle_hinting(true); } TTFSurface* ttf_surface_cache_get_or_add(TTF_Font* font, std::string_view text) { ttf_cache_entry* entry; uint32_t hash = ttf_surface_cache_hash(font, text); int32_t index = hash % TTF_SURFACE_CACHE_SIZE; FontLockHelper lock(_mutex); for (int32_t i = 0; i < TTF_SURFACE_CACHE_SIZE; i++) { entry = &_ttfSurfaceCache[index]; // Check if entry is a hit if (entry->surface == nullptr) break; if (entry->font == font && String::Equals(entry->text, text)) { _ttfSurfaceCacheHitCount++; entry->lastUseTick = gCurrentDrawCount; return entry->surface; } // If entry hasn't been used for a while, replace it if (entry->lastUseTick < gCurrentDrawCount - 64) { break; } // Check if next entry is a hit if (++index >= TTF_SURFACE_CACHE_SIZE) index = 0; } // Cache miss, replace entry with new surface entry = &_ttfSurfaceCache[index]; ttf_surface_cache_dispose(entry); TTFSurface* surface = ttf_render(font, text); if (surface == nullptr) { return nullptr; } _ttfSurfaceCacheMissCount++; // printf("CACHE HITS: %d MISSES: %d)\n", _ttfSurfaceCacheHitCount, _ttfSurfaceCacheMissCount); _ttfSurfaceCacheCount++; entry->surface = surface; entry->font = font; entry->text = strndup(text.data(), text.size()); entry->lastUseTick = gCurrentDrawCount; return entry->surface; } static void ttf_getwidth_cache_dispose(ttf_getwidth_cache_entry* entry) { if (entry->text != nullptr) { free(entry->text); entry->width = 0; entry->font = nullptr; entry->text = nullptr; } } static void ttf_getwidth_cache_dispose_all() { for (int32_t i = 0; i < TTF_GETWIDTH_CACHE_SIZE; i++) { ttf_getwidth_cache_dispose(&_ttfGetWidthCache[i]); _ttfGetWidthCacheCount--; } } uint32_t ttf_getwidth_cache_get_or_add(TTF_Font* font, std::string_view text) { ttf_getwidth_cache_entry* entry; uint32_t hash = ttf_surface_cache_hash(font, text); int32_t index = hash % TTF_GETWIDTH_CACHE_SIZE; FontLockHelper lock(_mutex); for (int32_t i = 0; i < TTF_GETWIDTH_CACHE_SIZE; i++) { entry = &_ttfGetWidthCache[index]; // Check if entry is a hit if (entry->text == nullptr) break; if (entry->font == font && String::Equals(entry->text, text)) { _ttfGetWidthCacheHitCount++; entry->lastUseTick = gCurrentDrawCount; return entry->width; } // If entry hasn't been used for a while, replace it if (entry->lastUseTick < gCurrentDrawCount - 64) { break; } // Check if next entry is a hit if (++index >= TTF_GETWIDTH_CACHE_SIZE) index = 0; } // Cache miss, replace entry with new width entry = &_ttfGetWidthCache[index]; ttf_getwidth_cache_dispose(entry); int32_t width, height; ttf_get_size(font, text, &width, &height); _ttfGetWidthCacheMissCount++; _ttfGetWidthCacheCount++; entry->width = width; entry->font = font; entry->text = strndup(text.data(), text.size()); entry->lastUseTick = gCurrentDrawCount; return entry->width; } TTFFontDescriptor* ttf_get_font_from_sprite_base(FontSpriteBase spriteBase) { FontLockHelper lock(_mutex); return &gCurrentTTFFontSet->size[font_get_size_from_sprite_base(spriteBase)]; } bool ttf_provides_glyph(const TTF_Font* font, codepoint_t codepoint) { return TTF_GlyphIsProvided(font, codepoint); } static bool ttf_get_size(TTF_Font* font, std::string_view text, int32_t* outWidth, int32_t* outHeight) { thread_local std::string buffer; buffer.assign(text); return TTF_SizeUTF8(font, buffer.c_str(), outWidth, outHeight); } static TTFSurface* ttf_render(TTF_Font* font, std::string_view text) { thread_local std::string buffer; buffer.assign(text); if (TTF_GetFontHinting(font) != 0) { return TTF_RenderUTF8_Shaded(font, buffer.c_str(), 0x000000FF, 0x000000FF); } else { return TTF_RenderUTF8_Solid(font, buffer.c_str(), 0x000000FF); } } void ttf_free_surface(TTFSurface* surface) { free(const_cast(surface->pixels)); free(surface); } #else # include "TTF.h" bool ttf_initialise() { return false; } void ttf_dispose() { } #endif // NO_TTF