blob: 0416b0e9f7b2e9bd885c74f61e43fa85533f0fd4 [file] [log] [blame]
Vojtech Bocek76ee9032014-09-07 15:01:56 +02001#include <stdbool.h>
2#include <stdlib.h>
3#include <unistd.h>
4
5#include <errno.h>
6#include <stdio.h>
7
8#include "minui.h"
9
10#include <cutils/hashmap.h>
11#include <ft2build.h>
12#include FT_FREETYPE_H
13#include FT_GLYPH_H
14
15#include <pixelflinger/pixelflinger.h>
16#include <pthread.h>
Vladimir Olteand32b7eb2018-07-03 00:04:03 +030017// For std::min and std::max
18#include <algorithm>
Vojtech Bocek76ee9032014-09-07 15:01:56 +020019
20#define STRING_CACHE_MAX_ENTRIES 400
21#define STRING_CACHE_TRUNCATE_ENTRIES 150
22
23typedef struct
24{
25 int size;
26 int dpi;
27 char *path;
28} TrueTypeFontKey;
29
30typedef struct
31{
32 int type;
33 int refcount;
34 int size;
35 int dpi;
36 int max_height;
37 int base;
38 FT_Face face;
39 Hashmap *glyph_cache;
40 Hashmap *string_cache;
41 struct StringCacheEntry *string_cache_head;
42 struct StringCacheEntry *string_cache_tail;
43 pthread_mutex_t mutex;
44 TrueTypeFontKey *key;
45} TrueTypeFont;
46
47typedef struct
48{
49 FT_BBox bbox;
50 FT_BitmapGlyph glyph;
51} TrueTypeCacheEntry;
52
53typedef struct
54{
55 char *text;
56 int max_width;
57} StringCacheKey;
58
59struct StringCacheEntry
60{
61 GGLSurface surface;
xiaolue738da52015-02-22 20:49:35 +080062 int rendered_bytes; // number of bytes from C string rendered, not number of UTF8 characters!
Vojtech Bocek76ee9032014-09-07 15:01:56 +020063 StringCacheKey *key;
64 struct StringCacheEntry *prev;
65 struct StringCacheEntry *next;
66};
67
68typedef struct StringCacheEntry StringCacheEntry;
69
xiaolue738da52015-02-22 20:49:35 +080070typedef struct
Vojtech Bocek76ee9032014-09-07 15:01:56 +020071{
72 FT_Library ft_library;
73 Hashmap *fonts;
74 pthread_mutex_t mutex;
75} FontData;
76
77static FontData font_data = {
78 .ft_library = NULL,
79 .fonts = NULL,
80 .mutex = PTHREAD_MUTEX_INITIALIZER,
81};
82
83#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
84#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
85
86// 32bit FNV-1a hash algorithm
87// http://isthe.com/chongo/tech/comp/fnv/#FNV-1a
88static const uint32_t FNV_prime = 16777619U;
89static const uint32_t offset_basis = 2166136261U;
90
91static uint32_t fnv_hash(void *data, uint32_t len)
92{
Ethan Yonkerfbb43532015-12-28 21:54:50 +010093 uint8_t *d8 = (uint8_t *)data;
94 uint32_t *d32 = (uint32_t *)data;
Vojtech Bocek76ee9032014-09-07 15:01:56 +020095 uint32_t i, max;
96 uint32_t hash = offset_basis;
97
98 max = len/4;
99
100 // 32 bit data
101 for(i = 0; i < max; ++i)
102 {
103 hash ^= *d32++;
104 hash *= FNV_prime;
105 }
106
107 // last bits
108 for(i *= 4; i < len; ++i)
109 {
110 hash ^= (uint32_t) d8[i];
111 hash *= FNV_prime;
112 }
113 return hash;
114}
115
116static inline uint32_t fnv_hash_add(uint32_t cur_hash, uint32_t word)
117{
118 cur_hash ^= word;
119 cur_hash *= FNV_prime;
120 return cur_hash;
121}
122
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100123int utf8_to_unicode(const char* pIn, unsigned int *pOut)
soyud5a7c0e2014-11-28 14:33:53 +0800124{
soyud5a7c0e2014-11-28 14:33:53 +0800125 int utf_bytes = 1;
126 unsigned int unicode = 0;
127 unsigned char tmp;
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100128 tmp = (unsigned char)*pIn++;
soyud5a7c0e2014-11-28 14:33:53 +0800129 if (tmp < 0x80)
130 {
131 *pOut = tmp;
132 }
133 else
134 {
135 unsigned int high_bit_mask = 0x3F;
136 unsigned int high_bit_shift = 0;
137 int total_bits = 0;
138 while((tmp & 0xC0) == 0xC0)
139 {
140 utf_bytes ++;
Xuefer0eb2aab2015-04-17 02:01:32 +0800141 if(utf_bytes > 6)
142 {
143 *pOut = tmp;
144 return 1;
145 }
soyud5a7c0e2014-11-28 14:33:53 +0800146 tmp = 0xFF & (tmp << 1);
147 total_bits += 6;
148 high_bit_mask >>= 1;
149 high_bit_shift++;
150 unicode <<= 6;
151 unicode |= (*pIn++) & 0x3F;
152 }
153 unicode |= ((tmp >> high_bit_shift) & high_bit_mask) << total_bits;
154 *pOut = unicode;
155 }
156
157 return utf_bytes;
158}
159
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200160static bool gr_ttf_string_cache_equals(void *keyA, void *keyB)
161{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100162 StringCacheKey *a = (StringCacheKey *)keyA;
163 StringCacheKey *b = (StringCacheKey *)keyB;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200164 return a->max_width == b->max_width && strcmp(a->text, b->text) == 0;
165}
166
167static int gr_ttf_string_cache_hash(void *key)
168{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100169 StringCacheKey *k = (StringCacheKey *)key;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200170 return fnv_hash(k->text, strlen(k->text));
171}
172
173static bool gr_ttf_font_cache_equals(void *keyA, void *keyB)
174{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100175 TrueTypeFontKey *a = (TrueTypeFontKey *)keyA;
176 TrueTypeFontKey *b = (TrueTypeFontKey *)keyB;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200177 return (a->size == b->size) && (a->dpi == b->dpi) && !strcmp(a->path, b->path);
178}
179
180static int gr_ttf_font_cache_hash(void *key)
181{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100182 TrueTypeFontKey *k = (TrueTypeFontKey *)key;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200183
184 uint32_t hash = fnv_hash(k->path, strlen(k->path));
185 hash = fnv_hash_add(hash, k->size);
186 hash = fnv_hash_add(hash, k->dpi);
187 return hash;
188}
189
190void *gr_ttf_loadFont(const char *filename, int size, int dpi)
191{
192 int error;
193 TrueTypeFont *res = NULL;
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100194 TrueTypeFontKey *key = NULL;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200195
196 pthread_mutex_lock(&font_data.mutex);
197
198 if(font_data.fonts)
199 {
200 TrueTypeFontKey k = {
201 .size = size,
202 .dpi = dpi,
203 .path = (char*)filename
204 };
205
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100206 res = (TrueTypeFont *)hashmapGet(font_data.fonts, &k);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200207 if(res)
208 {
209 ++res->refcount;
210 goto exit;
211 }
212 }
213
214 if(!font_data.ft_library)
215 {
216 error = FT_Init_FreeType(&font_data.ft_library);
217 if(error)
218 {
219 fprintf(stderr, "Failed to init libfreetype! %d\n", error);
220 goto exit;
221 }
222 }
223
224 FT_Face face;
225 error = FT_New_Face(font_data.ft_library, filename, 0, &face);
226 if(error)
227 {
228 fprintf(stderr, "Failed to load truetype face %s: %d\n", filename, error);
229 goto exit;
230 }
231
232 error = FT_Set_Char_Size(face, 0, size*16, dpi, dpi);
233 if(error)
234 {
235 fprintf(stderr, "Failed to set truetype face size to %d, dpi %d: %d\n", size, dpi, error);
236 FT_Done_Face(face);
237 goto exit;
238 }
239
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100240 res = (TrueTypeFont *)malloc(sizeof(TrueTypeFont));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200241 memset(res, 0, sizeof(TrueTypeFont));
242 res->type = FONT_TYPE_TTF;
243 res->size = size;
244 res->dpi = dpi;
245 res->face = face;
246 res->max_height = -1;
247 res->base = -1;
248 res->refcount = 1;
249 res->glyph_cache = hashmapCreate(32, hashmapIntHash, hashmapIntEquals);
250 res->string_cache = hashmapCreate(128, gr_ttf_string_cache_hash, gr_ttf_string_cache_equals);
251 pthread_mutex_init(&res->mutex, 0);
252
253 if(!font_data.fonts)
254 font_data.fonts = hashmapCreate(4, gr_ttf_font_cache_hash, gr_ttf_font_cache_equals);
255
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100256 key = (TrueTypeFontKey *)malloc(sizeof(TrueTypeFontKey));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200257 memset(key, 0, sizeof(TrueTypeFontKey));
258 key->path = strdup(filename);
259 key->size = size;
260 key->dpi = dpi;
261
262 res->key = key;
263
264 hashmapPut(font_data.fonts, key, res);
265
266exit:
267 pthread_mutex_unlock(&font_data.mutex);
268 return res;
269}
270
Ethan Yonkerb7a54a32015-10-05 10:16:27 -0500271void *gr_ttf_scaleFont(void *font, int max_width, int measured_width)
272{
273 if (!font)
274 return NULL;
275
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100276 TrueTypeFont *f = (TrueTypeFont *)font;
Ethan Yonkerb7a54a32015-10-05 10:16:27 -0500277 float scale_value = (float)(max_width) / (float)(measured_width);
278 int new_size = ((int)((float)f->size * scale_value)) - 1;
279 if (new_size < 1)
280 new_size = 1;
281 const char* file = f->key->path;
282 int dpi = f->dpi;
283 return gr_ttf_loadFont(file, new_size, dpi);
284}
285
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100286static bool gr_ttf_freeFontCache(void *key, void *value, void *context __unused)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200287{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100288 TrueTypeCacheEntry *e = (TrueTypeCacheEntry *)value;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200289 FT_Done_Glyph((FT_Glyph)e->glyph);
290 free(e);
291 free(key);
292 return true;
293}
294
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100295static bool gr_ttf_freeStringCache(void *key, void *value, void *context __unused)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200296{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100297 StringCacheKey *k = (StringCacheKey *)key;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200298 free(k->text);
299 free(k);
300
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100301 StringCacheEntry *e = (StringCacheEntry *)value;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200302 free(e->surface.data);
303 free(e);
304 return true;
305}
306
307void gr_ttf_freeFont(void *font)
308{
309 pthread_mutex_lock(&font_data.mutex);
310
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100311 TrueTypeFont *d = (TrueTypeFont *)font;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200312
313 if(--d->refcount == 0)
314 {
315 hashmapRemove(font_data.fonts, d->key);
316
317 if(hashmapSize(font_data.fonts) == 0)
318 {
319 hashmapFree(font_data.fonts);
320 font_data.fonts = NULL;
321 }
322
323 free(d->key->path);
324 free(d->key);
325
326 FT_Done_Face(d->face);
327 hashmapForEach(d->string_cache, gr_ttf_freeStringCache, NULL);
328 hashmapFree(d->string_cache);
329 hashmapForEach(d->glyph_cache, gr_ttf_freeFontCache, NULL);
330 hashmapFree(d->glyph_cache);
331 pthread_mutex_destroy(&d->mutex);
332 free(d);
333 }
334
335 pthread_mutex_unlock(&font_data.mutex);
336}
337
338static TrueTypeCacheEntry *gr_ttf_glyph_cache_peek(TrueTypeFont *font, int char_index)
339{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100340 return (TrueTypeCacheEntry *)hashmapGet(font->glyph_cache, &char_index);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200341}
342
343static TrueTypeCacheEntry *gr_ttf_glyph_cache_get(TrueTypeFont *font, int char_index)
344{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100345 TrueTypeCacheEntry *res = (TrueTypeCacheEntry *)hashmapGet(font->glyph_cache, &char_index);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200346 if(!res)
347 {
348 int error = FT_Load_Glyph(font->face, char_index, FT_LOAD_RENDER);
349 if(error)
350 {
351 fprintf(stderr, "Failed to load glyph idx %d: %d\n", char_index, error);
352 return NULL;
353 }
354
355 FT_BitmapGlyph glyph;
356 error = FT_Get_Glyph(font->face->glyph, (FT_Glyph*)&glyph);
357 if(error)
358 {
359 fprintf(stderr, "Failed to copy glyph %d: %d\n", char_index, error);
360 return NULL;
361 }
362
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100363 res = (TrueTypeCacheEntry *)malloc(sizeof(TrueTypeCacheEntry));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200364 memset(res, 0, sizeof(TrueTypeCacheEntry));
365 res->glyph = glyph;
366 FT_Glyph_Get_CBox((FT_Glyph)glyph, FT_GLYPH_BBOX_PIXELS, &res->bbox);
367
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100368 int *key = (int *)malloc(sizeof(int));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200369 *key = char_index;
370
371 hashmapPut(font->glyph_cache, key, res);
372 }
373
374 return res;
375}
376
377static int gr_ttf_copy_glyph_to_surface(GGLSurface *dest, FT_BitmapGlyph glyph, int offX, int offY, int base)
378{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100379 unsigned y;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200380 uint8_t *src_itr = glyph->bitmap.buffer;
381 uint8_t *dest_itr = dest->data;
382
383 if(glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
384 {
385 fprintf(stderr, "Unsupported pixel mode in FT_BitmapGlyph %d\n", glyph->bitmap.pixel_mode);
386 return -1;
387 }
388
389 dest_itr += (offY + base - glyph->top)*dest->stride + (offX + glyph->left);
390
Vojtech Bocek54cf1082015-03-15 16:50:26 +0100391 // FIXME: if glyph->left is negative and everything else is 0 (e.g. letter 'j' in Roboto-Regular),
392 // the result might end up being before the buffer - I'm not sure how to properly handle this.
393 if(dest_itr < dest->data)
394 dest_itr = dest->data;
395
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200396 for(y = 0; y < glyph->bitmap.rows; ++y)
397 {
398 memcpy(dest_itr, src_itr, glyph->bitmap.width);
399 src_itr += glyph->bitmap.pitch;
400 dest_itr += dest->stride;
401 }
402 return 0;
403}
404
Vojtech Boceka482f252015-03-15 17:03:50 +0100405static void gr_ttf_calcMaxFontHeight(TrueTypeFont *f)
406{
407 char c;
408 int char_idx;
409 int error;
410 FT_Glyph glyph;
411 FT_BBox bbox;
412 FT_BBox bbox_glyph;
413 TrueTypeCacheEntry *ent;
414
415 bbox.yMin = bbox_glyph.yMin = LONG_MAX;
416 bbox.yMax = bbox_glyph.yMax = LONG_MIN;
417
418 for(c = '!'; c <= '~'; ++c)
419 {
420 char_idx = FT_Get_Char_Index(f->face, c);
421 ent = gr_ttf_glyph_cache_peek(f, char_idx);
422 if(ent)
423 {
424 bbox.yMin = MIN(bbox.yMin, ent->bbox.yMin);
425 bbox.yMax = MAX(bbox.yMax, ent->bbox.yMax);
426 }
427 else
428 {
429 error = FT_Load_Glyph(f->face, char_idx, 0);
430 if(error)
431 continue;
432
433 error = FT_Get_Glyph(f->face->glyph, &glyph);
434 if(error)
435 continue;
436
437 FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox_glyph);
438 bbox.yMin = MIN(bbox.yMin, bbox_glyph.yMin);
439 bbox.yMax = MAX(bbox.yMax, bbox_glyph.yMax);
440
441 FT_Done_Glyph(glyph);
442 }
443 }
444
445 if(bbox.yMin > bbox.yMax)
446 bbox.yMin = bbox.yMax = 0;
447
448 f->max_height = bbox.yMax - bbox.yMin;
449 f->base = bbox.yMax;
450
451 // FIXME: twrp fonts have some padding on top, I'll add it here
452 // Should be fixed in the themes
453 f->max_height += f->size / 4;
454 f->base += f->size / 4;
455}
456
xiaolue738da52015-02-22 20:49:35 +0800457// returns number of bytes from const char *text rendered to fit max_width, not number of UTF8 characters!
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200458static int gr_ttf_render_text(TrueTypeFont *font, GGLSurface *surface, const char *text, int max_width)
459{
460 TrueTypeFont *f = font;
461 TrueTypeCacheEntry *ent;
xiaolue738da52015-02-22 20:49:35 +0800462 int bytes_rendered = 0, total_w = 0;
soyud5a7c0e2014-11-28 14:33:53 +0800463 int utf_bytes = 0;
464 unsigned int unicode = 0;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200465 int i, x, diff, char_idx, prev_idx = 0;
Ethan Yonker58f21322018-08-24 11:17:36 -0500466 int height;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200467 FT_Vector delta;
468 uint8_t *data = NULL;
469 const char *text_itr = text;
soyud5a7c0e2014-11-28 14:33:53 +0800470 int *char_idxs;
xiaolue738da52015-02-22 20:49:35 +0800471 int char_idxs_len = 0;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200472
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100473 char_idxs = (int *)malloc(strlen(text) * sizeof(int));
xiaolue738da52015-02-22 20:49:35 +0800474
soyud5a7c0e2014-11-28 14:33:53 +0800475 while(*text_itr)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200476 {
soyud5a7c0e2014-11-28 14:33:53 +0800477 utf_bytes = utf8_to_unicode(text_itr, &unicode);
xiaolue738da52015-02-22 20:49:35 +0800478 text_itr += utf_bytes;
479 bytes_rendered += utf_bytes;
480
soyud5a7c0e2014-11-28 14:33:53 +0800481 char_idx = FT_Get_Char_Index(f->face, unicode);
xiaolue738da52015-02-22 20:49:35 +0800482 char_idxs[char_idxs_len] = char_idx;
483
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200484 ent = gr_ttf_glyph_cache_get(f, char_idx);
485 if(ent)
486 {
487 diff = ent->glyph->root.advance.x >> 16;
488
489 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
490 {
491 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
492 diff += delta.x >> 6;
493 }
494
495 if(max_width != -1 && total_w + diff > max_width)
496 break;
497
498 total_w += diff;
499 }
500 prev_idx = char_idx;
xiaolue738da52015-02-22 20:49:35 +0800501 ++char_idxs_len;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200502 }
503
504 if(font->max_height == -1)
Vojtech Boceka482f252015-03-15 17:03:50 +0100505 gr_ttf_calcMaxFontHeight(font);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200506
507 if(font->max_height == -1)
soyud5a7c0e2014-11-28 14:33:53 +0800508 {
509 free(char_idxs);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200510 return -1;
soyud5a7c0e2014-11-28 14:33:53 +0800511 }
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200512
513 height = font->max_height;
514
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100515 data = (uint8_t *)malloc(total_w*height);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200516 memset(data, 0, total_w*height);
517 x = 0;
518 prev_idx = 0;
519
520 surface->version = sizeof(*surface);
521 surface->width = total_w;
522 surface->height = height;
523 surface->stride = total_w;
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100524 surface->data = (GGLubyte*)data;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200525 surface->format = GGL_PIXEL_FORMAT_A_8;
526
xiaolue738da52015-02-22 20:49:35 +0800527 for(i = 0; i < char_idxs_len; ++i)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200528 {
soyud5a7c0e2014-11-28 14:33:53 +0800529 char_idx = char_idxs[i];
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200530 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
531 {
532 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
533 x += delta.x >> 6;
534 }
535
536 ent = gr_ttf_glyph_cache_get(f, char_idx);
537 if(ent)
538 {
539 gr_ttf_copy_glyph_to_surface(surface, ent->glyph, x, 0, font->base);
540 x += ent->glyph->root.advance.x >> 16;
541 }
542
543 prev_idx = char_idx;
544 }
545
soyud5a7c0e2014-11-28 14:33:53 +0800546 free(char_idxs);
xiaolue738da52015-02-22 20:49:35 +0800547 return bytes_rendered;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200548}
549
550static StringCacheEntry *gr_ttf_string_cache_peek(TrueTypeFont *font, const char *text, int max_width)
551{
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200552 StringCacheKey k = {
553 .text = (char*)text,
554 .max_width = max_width
555 };
556
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100557 return (StringCacheEntry *)hashmapGet(font->string_cache, &k);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200558}
559
560static StringCacheEntry *gr_ttf_string_cache_get(TrueTypeFont *font, const char *text, int max_width)
561{
562 StringCacheEntry *res;
563 StringCacheKey k = {
564 .text = (char*)text,
565 .max_width = max_width
566 };
567
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100568 res = (StringCacheEntry *)hashmapGet(font->string_cache, &k);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200569 if(!res)
570 {
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100571 res = (StringCacheEntry *)malloc(sizeof(StringCacheEntry));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200572 memset(res, 0, sizeof(StringCacheEntry));
xiaolue738da52015-02-22 20:49:35 +0800573 res->rendered_bytes = gr_ttf_render_text(font, &res->surface, text, max_width);
574 if(res->rendered_bytes < 0)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200575 {
576 free(res);
577 return NULL;
578 }
579
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100580 StringCacheKey *new_key = (StringCacheKey *)malloc(sizeof(StringCacheKey));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200581 memset(new_key, 0, sizeof(StringCacheKey));
582 new_key->max_width = max_width;
583 new_key->text = strdup(text);
584
585 res->key = new_key;
586
587 if(font->string_cache_tail)
588 {
589 res->prev = font->string_cache_tail;
590 res->prev->next = res;
591 }
592 else
593 font->string_cache_head = res;
594 font->string_cache_tail = res;
595
596 hashmapPut(font->string_cache, new_key, res);
597 }
598 else if(res->next)
599 {
600 // move this entry to the tail of the linked list
601 // if it isn't already there
602 if(res->prev)
603 res->prev->next = res->next;
604
605 res->next->prev = res->prev;
606
607 if(!res->prev)
608 font->string_cache_head = res->next;
609
610 res->next = NULL;
611 res->prev = font->string_cache_tail;
612 res->prev->next = res;
613 font->string_cache_tail = res;
614
615 // truncate old entries
616 if(hashmapSize(font->string_cache) >= STRING_CACHE_MAX_ENTRIES)
617 {
618 printf("Truncating string cache entries.\n");
619 int i;
620 StringCacheEntry *ent;
621 for(i = 0; i < STRING_CACHE_TRUNCATE_ENTRIES; ++i)
622 {
623 ent = font->string_cache_head;
624 font->string_cache_head = ent->next;
625 font->string_cache_head->prev = NULL;
626
627 hashmapRemove(font->string_cache, ent->key);
628
629 gr_ttf_freeStringCache(ent->key, ent, NULL);
630 }
631 }
632 }
633 return res;
634}
635
636int gr_ttf_measureEx(const char *s, void *font)
637{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100638 TrueTypeFont *f = (TrueTypeFont *)font;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200639 int res = -1;
640
641 pthread_mutex_lock(&f->mutex);
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100642 StringCacheEntry *e = gr_ttf_string_cache_get(f, s, -1);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200643 if(e)
644 res = e->surface.width;
645 pthread_mutex_unlock(&f->mutex);
646
647 return res;
648}
649
650int gr_ttf_maxExW(const char *s, void *font, int max_width)
651{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100652 TrueTypeFont *f = (TrueTypeFont *)font;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200653 TrueTypeCacheEntry *ent;
xiaolue738da52015-02-22 20:49:35 +0800654 int max_bytes = 0, total_w = 0;
655 int utf_bytes, prev_utf_bytes = 0;
656 unsigned int unicode = 0;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200657 int char_idx, prev_idx = 0;
658 FT_Vector delta;
659 StringCacheEntry *e;
660
661 pthread_mutex_lock(&f->mutex);
662
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100663 e = gr_ttf_string_cache_peek(f, s, max_width);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200664 if(e)
665 {
xiaolue738da52015-02-22 20:49:35 +0800666 max_bytes = e->rendered_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200667 pthread_mutex_unlock(&f->mutex);
xiaolue738da52015-02-22 20:49:35 +0800668 return max_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200669 }
670
xiaolue738da52015-02-22 20:49:35 +0800671 while(*s)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200672 {
soyud5a7c0e2014-11-28 14:33:53 +0800673 utf_bytes = utf8_to_unicode(s, &unicode);
xiaolue738da52015-02-22 20:49:35 +0800674 s += utf_bytes;
675
676 char_idx = FT_Get_Char_Index(f->face, unicode);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200677 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
678 {
679 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
680 total_w += delta.x >> 6;
681 }
682 prev_idx = char_idx;
683
684 if(total_w > max_width)
xiaolue738da52015-02-22 20:49:35 +0800685 {
686 max_bytes -= prev_utf_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200687 break;
xiaolue738da52015-02-22 20:49:35 +0800688 }
689 prev_utf_bytes = utf_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200690
691 ent = gr_ttf_glyph_cache_get(f, char_idx);
692 if(!ent)
693 continue;
694
695 total_w += ent->glyph->root.advance.x >> 16;
xiaolue738da52015-02-22 20:49:35 +0800696 max_bytes += utf_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200697 }
698 pthread_mutex_unlock(&f->mutex);
xiaolue738da52015-02-22 20:49:35 +0800699 return max_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200700}
701
Vladimir Olteand32b7eb2018-07-03 00:04:03 +0300702int gr_ttf_textExWH(void *context, int x, int y,
703 const char *s, void *pFont,
704 int max_width, int max_height,
705 const gr_surface gr_draw_surface)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200706{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100707 GGLContext *gl = (GGLContext *)context;
708 TrueTypeFont *font = (TrueTypeFont *)pFont;
Vladimir Olteand32b7eb2018-07-03 00:04:03 +0300709 const GRSurface *gr_draw = (const GRSurface*) gr_draw_surface;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200710
711 // not actualy max width, but max_width + x
712 if(max_width != -1)
713 {
714 max_width -= x;
715 if(max_width <= 0)
716 return 0;
717 }
718
719 pthread_mutex_lock(&font->mutex);
720
721 StringCacheEntry *e = gr_ttf_string_cache_get(font, s, max_width);
722 if(!e)
723 {
724 pthread_mutex_unlock(&font->mutex);
725 return -1;
726 }
727
Vladimir Olteand32b7eb2018-07-03 00:04:03 +0300728#if TW_ROTATION != 0
729 // Do not perform relatively expensive operation if not needed
730 GGLSurface string_surface_rotated;
731 string_surface_rotated.version = sizeof(string_surface_rotated);
732 // Skip the **(TW_ROTATION == 0)** || (TW_ROTATION == 180) check
733 // because we are under a TW_ROTATION != 0 conditional compilation statement
734 string_surface_rotated.width = (TW_ROTATION == 180) ? e->surface.width : e->surface.height;
735 string_surface_rotated.height = (TW_ROTATION == 180) ? e->surface.height : e->surface.width;
736 string_surface_rotated.stride = string_surface_rotated.width;
737 string_surface_rotated.format = e->surface.format;
738 // e->surface.format is GGL_PIXEL_FORMAT_A_8 (grayscale)
739 string_surface_rotated.data = (GGLubyte*) malloc(string_surface_rotated.stride * string_surface_rotated.height * 1);
740 surface_ROTATION_transform((gr_surface) &string_surface_rotated, (const gr_surface) &e->surface, 1);
741#endif
742
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200743 int y_bottom = y + e->surface.height;
xiaolue738da52015-02-22 20:49:35 +0800744 int res = e->rendered_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200745
746 if(max_height != -1 && max_height < y_bottom)
747 {
748 y_bottom = max_height;
749 if(y_bottom <= y)
750 {
751 pthread_mutex_unlock(&font->mutex);
752 return 0;
753 }
754 }
755
Vladimir Olteand32b7eb2018-07-03 00:04:03 +0300756 // Figuring out display coordinates works for TW_ROTATION == 0 too,
757 // and isn't as expensive as allocating and rotating another surface,
758 // so we do this anyway.
759 int x0_disp, y0_disp, x1_disp, y1_disp;
760 int l_disp, r_disp, t_disp, b_disp;
761
762 x0_disp = ROTATION_X_DISP(x, y, gr_draw);
763 y0_disp = ROTATION_Y_DISP(x, y, gr_draw);
764 x1_disp = ROTATION_X_DISP(x + e->surface.width, y_bottom, gr_draw);
765 y1_disp = ROTATION_Y_DISP(x + e->surface.width, y_bottom, gr_draw);
766 l_disp = std::min(x0_disp, x1_disp);
767 r_disp = std::max(x0_disp, x1_disp);
768 t_disp = std::min(y0_disp, y1_disp);
769 b_disp = std::max(y0_disp, y1_disp);
770
771#if TW_ROTATION != 0
772 gl->bindTexture(gl, &string_surface_rotated);
773#else
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200774 gl->bindTexture(gl, &e->surface);
Vladimir Olteand32b7eb2018-07-03 00:04:03 +0300775#endif
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200776 gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
777 gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
778 gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200779
Vojtech Bocek3041c882015-03-06 00:28:21 +0100780 gl->enable(gl, GGL_TEXTURE_2D);
Vladimir Olteand32b7eb2018-07-03 00:04:03 +0300781 gl->texCoord2i(gl, -l_disp, -t_disp);
782 gl->recti(gl, l_disp, t_disp, r_disp, b_disp);
Vojtech Bocek3041c882015-03-06 00:28:21 +0100783 gl->disable(gl, GGL_TEXTURE_2D);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200784
Vladimir Olteand32b7eb2018-07-03 00:04:03 +0300785#if TW_ROTATION != 0
786 free(string_surface_rotated.data);
787#endif
788
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200789 pthread_mutex_unlock(&font->mutex);
790 return res;
791}
792
793int gr_ttf_getMaxFontHeight(void *font)
794{
795 int res;
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100796 TrueTypeFont *f = (TrueTypeFont *)font;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200797
798 pthread_mutex_lock(&f->mutex);
799
800 if(f->max_height == -1)
Vojtech Boceka482f252015-03-15 17:03:50 +0100801 gr_ttf_calcMaxFontHeight(f);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200802 res = f->max_height;
803
804 pthread_mutex_unlock(&f->mutex);
805 return res;
806}
807
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100808static bool gr_ttf_dump_stats_count_string_cache(void *key __unused, void *value, void *context)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200809{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100810 int *string_cache_size = (int *)context;
811 StringCacheEntry *e = (StringCacheEntry *)value;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200812 *string_cache_size += e->surface.height*e->surface.width + sizeof(StringCacheEntry);
813 return true;
814}
815
816static bool gr_ttf_dump_stats_font(void *key, void *value, void *context)
817{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100818 TrueTypeFontKey *k = (TrueTypeFontKey *)key;
819 TrueTypeFont *f = (TrueTypeFont *)value;
820 int *total_string_cache_size = (int *)context;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200821 int string_cache_size = 0;
822
823 pthread_mutex_lock(&f->mutex);
824
825 hashmapForEach(f->string_cache, gr_ttf_dump_stats_count_string_cache, &string_cache_size);
826
827 printf(" Font %s (size %d, dpi %d):\n"
828 " refcount: %d\n"
829 " max_height: %d\n"
830 " base: %d\n"
Ethan Yonker58f21322018-08-24 11:17:36 -0500831 " glyph_cache: %zu entries\n"
832 " string_cache: %zu entries (%.2f kB)\n",
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200833 k->path, k->size, k->dpi,
834 f->refcount, f->max_height, f->base,
835 hashmapSize(f->glyph_cache),
836 hashmapSize(f->string_cache), ((double)string_cache_size)/1024);
837
838 pthread_mutex_unlock(&f->mutex);
839
840 *total_string_cache_size += string_cache_size;
841 return true;
842}
843
844void gr_ttf_dump_stats(void)
845{
846 pthread_mutex_lock(&font_data.mutex);
847
848 printf("TrueType fonts system stats: ");
849 if(!font_data.fonts)
850 printf("no truetype fonts loaded.\n");
851 else
852 {
853 int total_string_cache_size = 0;
Ethan Yonker58f21322018-08-24 11:17:36 -0500854 printf("%zu fonts loaded.\n", hashmapSize(font_data.fonts));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200855 hashmapForEach(font_data.fonts, gr_ttf_dump_stats_font, &total_string_cache_size);
856 printf(" Total string cache size: %.2f kB\n", ((double)total_string_cache_size)/1024);
857 }
858
859 pthread_mutex_unlock(&font_data.mutex);
860}