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