1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #ifndef NO_TTF
11 
12 #    include <atomic>
13 #    include <mutex>
14 #    pragma clang diagnostic push
15 #    pragma clang diagnostic ignored "-Wdocumentation"
16 #    include <ft2build.h>
17 #    include FT_FREETYPE_H
18 #    pragma clang diagnostic pop
19 
20 #    include "../OpenRCT2.h"
21 #    include "../config/Config.h"
22 #    include "../core/Numerics.hpp"
23 #    include "../core/String.hpp"
24 #    include "../localisation/Localisation.h"
25 #    include "../localisation/LocalisationService.h"
26 #    include "../platform/platform.h"
27 #    include "TTF.h"
28 
29 static bool _ttfInitialised = false;
30 
31 #    define TTF_SURFACE_CACHE_SIZE 256
32 #    define TTF_GETWIDTH_CACHE_SIZE 1024
33 
34 struct ttf_cache_entry
35 {
36     TTFSurface* surface;
37     TTF_Font* font;
38     utf8* text;
39     uint32_t lastUseTick;
40 };
41 
42 struct ttf_getwidth_cache_entry
43 {
44     uint32_t width;
45     TTF_Font* font;
46     utf8* text;
47     uint32_t lastUseTick;
48 };
49 
50 static ttf_cache_entry _ttfSurfaceCache[TTF_SURFACE_CACHE_SIZE] = {};
51 static int32_t _ttfSurfaceCacheCount = 0;
52 static int32_t _ttfSurfaceCacheHitCount = 0;
53 static int32_t _ttfSurfaceCacheMissCount = 0;
54 
55 static ttf_getwidth_cache_entry _ttfGetWidthCache[TTF_GETWIDTH_CACHE_SIZE] = {};
56 static int32_t _ttfGetWidthCacheCount = 0;
57 static int32_t _ttfGetWidthCacheHitCount = 0;
58 static int32_t _ttfGetWidthCacheMissCount = 0;
59 
60 static std::mutex _mutex;
61 
62 static TTF_Font* ttf_open_font(const utf8* fontPath, int32_t ptSize);
63 static void ttf_close_font(TTF_Font* font);
64 static void ttf_surface_cache_dispose(ttf_cache_entry* entry);
65 static void ttf_surface_cache_dispose_all();
66 static void ttf_getwidth_cache_dispose_all();
67 static bool ttf_get_size(TTF_Font* font, std::string_view text, int32_t* outWidth, int32_t* outHeight);
68 static void ttf_toggle_hinting(bool);
69 static TTFSurface* ttf_render(TTF_Font* font, std::string_view text);
70 
71 template<typename T> class FontLockHelper
72 {
73     T& _mutex;
74     const bool _enabled;
75 
76 public:
FontLockHelper(T & mutex)77     FontLockHelper(T& mutex)
78         : _mutex(mutex)
79         , _enabled(gConfigGeneral.multithreading)
80     {
81         if (_enabled)
82             _mutex.lock();
83     }
~FontLockHelper()84     ~FontLockHelper()
85     {
86         if (_enabled)
87             _mutex.unlock();
88     }
89 };
90 
ttf_toggle_hinting(bool)91 static void ttf_toggle_hinting(bool)
92 {
93     if (!LocalisationService_UseTrueTypeFont())
94     {
95         return;
96     }
97 
98     for (int32_t i = 0; i < FONT_SIZE_COUNT; i++)
99     {
100         TTFFontDescriptor* fontDesc = &(gCurrentTTFFontSet->size[i]);
101         bool use_hinting = gConfigFonts.enable_hinting && fontDesc->hinting_threshold;
102         TTF_SetFontHinting(fontDesc->font, use_hinting ? 1 : 0);
103     }
104 
105     if (_ttfSurfaceCacheCount)
106     {
107         ttf_surface_cache_dispose_all();
108     }
109 }
110 
ttf_initialise()111 bool ttf_initialise()
112 {
113     FontLockHelper<std::mutex> lock(_mutex);
114 
115     if (_ttfInitialised)
116         return true;
117 
118     if (TTF_Init() != 0)
119     {
120         log_error("Couldn't initialise FreeType engine");
121         return false;
122     }
123 
124     for (int32_t i = 0; i < FONT_SIZE_COUNT; i++)
125     {
126         TTFFontDescriptor* fontDesc = &(gCurrentTTFFontSet->size[i]);
127 
128         utf8 fontPath[MAX_PATH];
129         if (!platform_get_font_path(fontDesc, fontPath, sizeof(fontPath)))
130         {
131             log_verbose("Unable to load font '%s'", fontDesc->font_name);
132             return false;
133         }
134 
135         fontDesc->font = ttf_open_font(fontPath, fontDesc->ptSize);
136         if (fontDesc->font == nullptr)
137         {
138             log_verbose("Unable to load '%s'", fontPath);
139             return false;
140         }
141     }
142 
143     ttf_toggle_hinting(true);
144 
145     _ttfInitialised = true;
146 
147     return true;
148 }
149 
ttf_dispose()150 void ttf_dispose()
151 {
152     FontLockHelper<std::mutex> lock(_mutex);
153 
154     if (!_ttfInitialised)
155         return;
156 
157     ttf_surface_cache_dispose_all();
158     ttf_getwidth_cache_dispose_all();
159 
160     for (int32_t i = 0; i < FONT_SIZE_COUNT; i++)
161     {
162         TTFFontDescriptor* fontDesc = &(gCurrentTTFFontSet->size[i]);
163         if (fontDesc->font != nullptr)
164         {
165             ttf_close_font(fontDesc->font);
166             fontDesc->font = nullptr;
167         }
168     }
169 
170     TTF_Quit();
171 
172     _ttfInitialised = false;
173 }
174 
ttf_open_font(const utf8 * fontPath,int32_t ptSize)175 static TTF_Font* ttf_open_font(const utf8* fontPath, int32_t ptSize)
176 {
177     return TTF_OpenFont(fontPath, ptSize);
178 }
179 
ttf_close_font(TTF_Font * font)180 static void ttf_close_font(TTF_Font* font)
181 {
182     TTF_CloseFont(font);
183 }
184 
ttf_surface_cache_hash(TTF_Font * font,std::string_view text)185 static uint32_t ttf_surface_cache_hash(TTF_Font* font, std::string_view text)
186 {
187     uint32_t hash = static_cast<uint32_t>(((reinterpret_cast<uintptr_t>(font) * 23) ^ 0xAAAAAAAA) & 0xFFFFFFFF);
188     for (auto c : text)
189     {
190         hash = Numerics::ror32(hash, 3) ^ (c * 13);
191     }
192     return hash;
193 }
194 
ttf_surface_cache_dispose(ttf_cache_entry * entry)195 static void ttf_surface_cache_dispose(ttf_cache_entry* entry)
196 {
197     if (entry->surface != nullptr)
198     {
199         ttf_free_surface(entry->surface);
200         free(entry->text);
201 
202         entry->surface = nullptr;
203         entry->font = nullptr;
204         entry->text = nullptr;
205     }
206 }
207 
ttf_surface_cache_dispose_all()208 static void ttf_surface_cache_dispose_all()
209 {
210     for (int32_t i = 0; i < TTF_SURFACE_CACHE_SIZE; i++)
211     {
212         ttf_surface_cache_dispose(&_ttfSurfaceCache[i]);
213         _ttfSurfaceCacheCount--;
214     }
215 }
216 
ttf_toggle_hinting()217 void ttf_toggle_hinting()
218 {
219     FontLockHelper<std::mutex> lock(_mutex);
220     ttf_toggle_hinting(true);
221 }
222 
ttf_surface_cache_get_or_add(TTF_Font * font,std::string_view text)223 TTFSurface* ttf_surface_cache_get_or_add(TTF_Font* font, std::string_view text)
224 {
225     ttf_cache_entry* entry;
226 
227     uint32_t hash = ttf_surface_cache_hash(font, text);
228     int32_t index = hash % TTF_SURFACE_CACHE_SIZE;
229 
230     FontLockHelper<std::mutex> lock(_mutex);
231 
232     for (int32_t i = 0; i < TTF_SURFACE_CACHE_SIZE; i++)
233     {
234         entry = &_ttfSurfaceCache[index];
235 
236         // Check if entry is a hit
237         if (entry->surface == nullptr)
238             break;
239         if (entry->font == font && String::Equals(entry->text, text))
240         {
241             _ttfSurfaceCacheHitCount++;
242             entry->lastUseTick = gCurrentDrawCount;
243             return entry->surface;
244         }
245 
246         // If entry hasn't been used for a while, replace it
247         if (entry->lastUseTick < gCurrentDrawCount - 64)
248         {
249             break;
250         }
251 
252         // Check if next entry is a hit
253         if (++index >= TTF_SURFACE_CACHE_SIZE)
254             index = 0;
255     }
256 
257     // Cache miss, replace entry with new surface
258     entry = &_ttfSurfaceCache[index];
259     ttf_surface_cache_dispose(entry);
260 
261     TTFSurface* surface = ttf_render(font, text);
262     if (surface == nullptr)
263     {
264         return nullptr;
265     }
266 
267     _ttfSurfaceCacheMissCount++;
268     // printf("CACHE HITS: %d   MISSES: %d)\n", _ttfSurfaceCacheHitCount, _ttfSurfaceCacheMissCount);
269 
270     _ttfSurfaceCacheCount++;
271     entry->surface = surface;
272     entry->font = font;
273     entry->text = strndup(text.data(), text.size());
274     entry->lastUseTick = gCurrentDrawCount;
275     return entry->surface;
276 }
277 
ttf_getwidth_cache_dispose(ttf_getwidth_cache_entry * entry)278 static void ttf_getwidth_cache_dispose(ttf_getwidth_cache_entry* entry)
279 {
280     if (entry->text != nullptr)
281     {
282         free(entry->text);
283 
284         entry->width = 0;
285         entry->font = nullptr;
286         entry->text = nullptr;
287     }
288 }
289 
ttf_getwidth_cache_dispose_all()290 static void ttf_getwidth_cache_dispose_all()
291 {
292     for (int32_t i = 0; i < TTF_GETWIDTH_CACHE_SIZE; i++)
293     {
294         ttf_getwidth_cache_dispose(&_ttfGetWidthCache[i]);
295         _ttfGetWidthCacheCount--;
296     }
297 }
298 
ttf_getwidth_cache_get_or_add(TTF_Font * font,std::string_view text)299 uint32_t ttf_getwidth_cache_get_or_add(TTF_Font* font, std::string_view text)
300 {
301     ttf_getwidth_cache_entry* entry;
302 
303     uint32_t hash = ttf_surface_cache_hash(font, text);
304     int32_t index = hash % TTF_GETWIDTH_CACHE_SIZE;
305 
306     FontLockHelper<std::mutex> lock(_mutex);
307 
308     for (int32_t i = 0; i < TTF_GETWIDTH_CACHE_SIZE; i++)
309     {
310         entry = &_ttfGetWidthCache[index];
311 
312         // Check if entry is a hit
313         if (entry->text == nullptr)
314             break;
315         if (entry->font == font && String::Equals(entry->text, text))
316         {
317             _ttfGetWidthCacheHitCount++;
318             entry->lastUseTick = gCurrentDrawCount;
319             return entry->width;
320         }
321 
322         // If entry hasn't been used for a while, replace it
323         if (entry->lastUseTick < gCurrentDrawCount - 64)
324         {
325             break;
326         }
327 
328         // Check if next entry is a hit
329         if (++index >= TTF_GETWIDTH_CACHE_SIZE)
330             index = 0;
331     }
332 
333     // Cache miss, replace entry with new width
334     entry = &_ttfGetWidthCache[index];
335     ttf_getwidth_cache_dispose(entry);
336 
337     int32_t width, height;
338     ttf_get_size(font, text, &width, &height);
339 
340     _ttfGetWidthCacheMissCount++;
341 
342     _ttfGetWidthCacheCount++;
343     entry->width = width;
344     entry->font = font;
345     entry->text = strndup(text.data(), text.size());
346     entry->lastUseTick = gCurrentDrawCount;
347     return entry->width;
348 }
349 
ttf_get_font_from_sprite_base(FontSpriteBase spriteBase)350 TTFFontDescriptor* ttf_get_font_from_sprite_base(FontSpriteBase spriteBase)
351 {
352     FontLockHelper<std::mutex> lock(_mutex);
353     return &gCurrentTTFFontSet->size[font_get_size_from_sprite_base(spriteBase)];
354 }
355 
ttf_provides_glyph(const TTF_Font * font,codepoint_t codepoint)356 bool ttf_provides_glyph(const TTF_Font* font, codepoint_t codepoint)
357 {
358     return TTF_GlyphIsProvided(font, codepoint);
359 }
360 
ttf_get_size(TTF_Font * font,std::string_view text,int32_t * outWidth,int32_t * outHeight)361 static bool ttf_get_size(TTF_Font* font, std::string_view text, int32_t* outWidth, int32_t* outHeight)
362 {
363     thread_local std::string buffer;
364     buffer.assign(text);
365     return TTF_SizeUTF8(font, buffer.c_str(), outWidth, outHeight);
366 }
367 
ttf_render(TTF_Font * font,std::string_view text)368 static TTFSurface* ttf_render(TTF_Font* font, std::string_view text)
369 {
370     thread_local std::string buffer;
371     buffer.assign(text);
372     if (TTF_GetFontHinting(font) != 0)
373     {
374         return TTF_RenderUTF8_Shaded(font, buffer.c_str(), 0x000000FF, 0x000000FF);
375     }
376 
377     return TTF_RenderUTF8_Solid(font, buffer.c_str(), 0x000000FF);
378 }
379 
ttf_free_surface(TTFSurface * surface)380 void ttf_free_surface(TTFSurface* surface)
381 {
382     free(const_cast<void*>(surface->pixels));
383     free(surface);
384 }
385 
386 #else
387 
388 #    include "TTF.h"
389 
ttf_initialise()390 bool ttf_initialise()
391 {
392     return false;
393 }
394 
ttf_dispose()395 void ttf_dispose()
396 {
397 }
398 
399 #endif // NO_TTF
400