blob: 18635a8868e415289aa9c0dd2aee62ec6a07373f [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>
17
18#define STRING_CACHE_MAX_ENTRIES 400
19#define STRING_CACHE_TRUNCATE_ENTRIES 150
20
21typedef struct
22{
23 int size;
24 int dpi;
25 char *path;
26} TrueTypeFontKey;
27
28typedef struct
29{
30 int type;
31 int refcount;
32 int size;
33 int dpi;
34 int max_height;
35 int base;
36 FT_Face face;
37 Hashmap *glyph_cache;
38 Hashmap *string_cache;
39 struct StringCacheEntry *string_cache_head;
40 struct StringCacheEntry *string_cache_tail;
41 pthread_mutex_t mutex;
42 TrueTypeFontKey *key;
43} TrueTypeFont;
44
45typedef struct
46{
47 FT_BBox bbox;
48 FT_BitmapGlyph glyph;
49} TrueTypeCacheEntry;
50
51typedef struct
52{
53 char *text;
54 int max_width;
55} StringCacheKey;
56
57struct StringCacheEntry
58{
59 GGLSurface surface;
xiaolue738da52015-02-22 20:49:35 +080060 int rendered_bytes; // number of bytes from C string rendered, not number of UTF8 characters!
Vojtech Bocek76ee9032014-09-07 15:01:56 +020061 StringCacheKey *key;
62 struct StringCacheEntry *prev;
63 struct StringCacheEntry *next;
64};
65
66typedef struct StringCacheEntry StringCacheEntry;
67
xiaolue738da52015-02-22 20:49:35 +080068typedef struct
Vojtech Bocek76ee9032014-09-07 15:01:56 +020069{
70 FT_Library ft_library;
71 Hashmap *fonts;
72 pthread_mutex_t mutex;
73} FontData;
74
75static FontData font_data = {
76 .ft_library = NULL,
77 .fonts = NULL,
78 .mutex = PTHREAD_MUTEX_INITIALIZER,
79};
80
81#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
82#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
83
84// 32bit FNV-1a hash algorithm
85// http://isthe.com/chongo/tech/comp/fnv/#FNV-1a
86static const uint32_t FNV_prime = 16777619U;
87static const uint32_t offset_basis = 2166136261U;
88
89static uint32_t fnv_hash(void *data, uint32_t len)
90{
Ethan Yonkerfbb43532015-12-28 21:54:50 +010091 uint8_t *d8 = (uint8_t *)data;
92 uint32_t *d32 = (uint32_t *)data;
Vojtech Bocek76ee9032014-09-07 15:01:56 +020093 uint32_t i, max;
94 uint32_t hash = offset_basis;
95
96 max = len/4;
97
98 // 32 bit data
99 for(i = 0; i < max; ++i)
100 {
101 hash ^= *d32++;
102 hash *= FNV_prime;
103 }
104
105 // last bits
106 for(i *= 4; i < len; ++i)
107 {
108 hash ^= (uint32_t) d8[i];
109 hash *= FNV_prime;
110 }
111 return hash;
112}
113
114static inline uint32_t fnv_hash_add(uint32_t cur_hash, uint32_t word)
115{
116 cur_hash ^= word;
117 cur_hash *= FNV_prime;
118 return cur_hash;
119}
120
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100121int utf8_to_unicode(const char* pIn, unsigned int *pOut)
soyud5a7c0e2014-11-28 14:33:53 +0800122{
soyud5a7c0e2014-11-28 14:33:53 +0800123 int utf_bytes = 1;
124 unsigned int unicode = 0;
125 unsigned char tmp;
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100126 tmp = (unsigned char)*pIn++;
soyud5a7c0e2014-11-28 14:33:53 +0800127 if (tmp < 0x80)
128 {
129 *pOut = tmp;
130 }
131 else
132 {
133 unsigned int high_bit_mask = 0x3F;
134 unsigned int high_bit_shift = 0;
135 int total_bits = 0;
136 while((tmp & 0xC0) == 0xC0)
137 {
138 utf_bytes ++;
Xuefer0eb2aab2015-04-17 02:01:32 +0800139 if(utf_bytes > 6)
140 {
141 *pOut = tmp;
142 return 1;
143 }
soyud5a7c0e2014-11-28 14:33:53 +0800144 tmp = 0xFF & (tmp << 1);
145 total_bits += 6;
146 high_bit_mask >>= 1;
147 high_bit_shift++;
148 unicode <<= 6;
149 unicode |= (*pIn++) & 0x3F;
150 }
151 unicode |= ((tmp >> high_bit_shift) & high_bit_mask) << total_bits;
152 *pOut = unicode;
153 }
154
155 return utf_bytes;
156}
157
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200158static bool gr_ttf_string_cache_equals(void *keyA, void *keyB)
159{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100160 StringCacheKey *a = (StringCacheKey *)keyA;
161 StringCacheKey *b = (StringCacheKey *)keyB;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200162 return a->max_width == b->max_width && strcmp(a->text, b->text) == 0;
163}
164
165static int gr_ttf_string_cache_hash(void *key)
166{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100167 StringCacheKey *k = (StringCacheKey *)key;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200168 return fnv_hash(k->text, strlen(k->text));
169}
170
171static bool gr_ttf_font_cache_equals(void *keyA, void *keyB)
172{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100173 TrueTypeFontKey *a = (TrueTypeFontKey *)keyA;
174 TrueTypeFontKey *b = (TrueTypeFontKey *)keyB;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200175 return (a->size == b->size) && (a->dpi == b->dpi) && !strcmp(a->path, b->path);
176}
177
178static int gr_ttf_font_cache_hash(void *key)
179{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100180 TrueTypeFontKey *k = (TrueTypeFontKey *)key;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200181
182 uint32_t hash = fnv_hash(k->path, strlen(k->path));
183 hash = fnv_hash_add(hash, k->size);
184 hash = fnv_hash_add(hash, k->dpi);
185 return hash;
186}
187
188void *gr_ttf_loadFont(const char *filename, int size, int dpi)
189{
190 int error;
191 TrueTypeFont *res = NULL;
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100192 TrueTypeFontKey *key = NULL;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200193
194 pthread_mutex_lock(&font_data.mutex);
195
196 if(font_data.fonts)
197 {
198 TrueTypeFontKey k = {
199 .size = size,
200 .dpi = dpi,
201 .path = (char*)filename
202 };
203
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100204 res = (TrueTypeFont *)hashmapGet(font_data.fonts, &k);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200205 if(res)
206 {
207 ++res->refcount;
208 goto exit;
209 }
210 }
211
212 if(!font_data.ft_library)
213 {
214 error = FT_Init_FreeType(&font_data.ft_library);
215 if(error)
216 {
217 fprintf(stderr, "Failed to init libfreetype! %d\n", error);
218 goto exit;
219 }
220 }
221
222 FT_Face face;
223 error = FT_New_Face(font_data.ft_library, filename, 0, &face);
224 if(error)
225 {
226 fprintf(stderr, "Failed to load truetype face %s: %d\n", filename, error);
227 goto exit;
228 }
229
230 error = FT_Set_Char_Size(face, 0, size*16, dpi, dpi);
231 if(error)
232 {
233 fprintf(stderr, "Failed to set truetype face size to %d, dpi %d: %d\n", size, dpi, error);
234 FT_Done_Face(face);
235 goto exit;
236 }
237
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100238 res = (TrueTypeFont *)malloc(sizeof(TrueTypeFont));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200239 memset(res, 0, sizeof(TrueTypeFont));
240 res->type = FONT_TYPE_TTF;
241 res->size = size;
242 res->dpi = dpi;
243 res->face = face;
244 res->max_height = -1;
245 res->base = -1;
246 res->refcount = 1;
247 res->glyph_cache = hashmapCreate(32, hashmapIntHash, hashmapIntEquals);
248 res->string_cache = hashmapCreate(128, gr_ttf_string_cache_hash, gr_ttf_string_cache_equals);
249 pthread_mutex_init(&res->mutex, 0);
250
251 if(!font_data.fonts)
252 font_data.fonts = hashmapCreate(4, gr_ttf_font_cache_hash, gr_ttf_font_cache_equals);
253
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100254 key = (TrueTypeFontKey *)malloc(sizeof(TrueTypeFontKey));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200255 memset(key, 0, sizeof(TrueTypeFontKey));
256 key->path = strdup(filename);
257 key->size = size;
258 key->dpi = dpi;
259
260 res->key = key;
261
262 hashmapPut(font_data.fonts, key, res);
263
264exit:
265 pthread_mutex_unlock(&font_data.mutex);
266 return res;
267}
268
Ethan Yonkerb7a54a32015-10-05 10:16:27 -0500269void *gr_ttf_scaleFont(void *font, int max_width, int measured_width)
270{
271 if (!font)
272 return NULL;
273
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100274 TrueTypeFont *f = (TrueTypeFont *)font;
Ethan Yonkerb7a54a32015-10-05 10:16:27 -0500275 float scale_value = (float)(max_width) / (float)(measured_width);
276 int new_size = ((int)((float)f->size * scale_value)) - 1;
277 if (new_size < 1)
278 new_size = 1;
279 const char* file = f->key->path;
280 int dpi = f->dpi;
281 return gr_ttf_loadFont(file, new_size, dpi);
282}
283
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100284static bool gr_ttf_freeFontCache(void *key, void *value, void *context __unused)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200285{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100286 TrueTypeCacheEntry *e = (TrueTypeCacheEntry *)value;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200287 FT_Done_Glyph((FT_Glyph)e->glyph);
288 free(e);
289 free(key);
290 return true;
291}
292
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100293static bool gr_ttf_freeStringCache(void *key, void *value, void *context __unused)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200294{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100295 StringCacheKey *k = (StringCacheKey *)key;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200296 free(k->text);
297 free(k);
298
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100299 StringCacheEntry *e = (StringCacheEntry *)value;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200300 free(e->surface.data);
301 free(e);
302 return true;
303}
304
305void gr_ttf_freeFont(void *font)
306{
307 pthread_mutex_lock(&font_data.mutex);
308
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100309 TrueTypeFont *d = (TrueTypeFont *)font;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200310
311 if(--d->refcount == 0)
312 {
313 hashmapRemove(font_data.fonts, d->key);
314
315 if(hashmapSize(font_data.fonts) == 0)
316 {
317 hashmapFree(font_data.fonts);
318 font_data.fonts = NULL;
319 }
320
321 free(d->key->path);
322 free(d->key);
323
324 FT_Done_Face(d->face);
325 hashmapForEach(d->string_cache, gr_ttf_freeStringCache, NULL);
326 hashmapFree(d->string_cache);
327 hashmapForEach(d->glyph_cache, gr_ttf_freeFontCache, NULL);
328 hashmapFree(d->glyph_cache);
329 pthread_mutex_destroy(&d->mutex);
330 free(d);
331 }
332
333 pthread_mutex_unlock(&font_data.mutex);
334}
335
336static TrueTypeCacheEntry *gr_ttf_glyph_cache_peek(TrueTypeFont *font, int char_index)
337{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100338 return (TrueTypeCacheEntry *)hashmapGet(font->glyph_cache, &char_index);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200339}
340
341static TrueTypeCacheEntry *gr_ttf_glyph_cache_get(TrueTypeFont *font, int char_index)
342{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100343 TrueTypeCacheEntry *res = (TrueTypeCacheEntry *)hashmapGet(font->glyph_cache, &char_index);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200344 if(!res)
345 {
346 int error = FT_Load_Glyph(font->face, char_index, FT_LOAD_RENDER);
347 if(error)
348 {
349 fprintf(stderr, "Failed to load glyph idx %d: %d\n", char_index, error);
350 return NULL;
351 }
352
353 FT_BitmapGlyph glyph;
354 error = FT_Get_Glyph(font->face->glyph, (FT_Glyph*)&glyph);
355 if(error)
356 {
357 fprintf(stderr, "Failed to copy glyph %d: %d\n", char_index, error);
358 return NULL;
359 }
360
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100361 res = (TrueTypeCacheEntry *)malloc(sizeof(TrueTypeCacheEntry));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200362 memset(res, 0, sizeof(TrueTypeCacheEntry));
363 res->glyph = glyph;
364 FT_Glyph_Get_CBox((FT_Glyph)glyph, FT_GLYPH_BBOX_PIXELS, &res->bbox);
365
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100366 int *key = (int *)malloc(sizeof(int));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200367 *key = char_index;
368
369 hashmapPut(font->glyph_cache, key, res);
370 }
371
372 return res;
373}
374
375static int gr_ttf_copy_glyph_to_surface(GGLSurface *dest, FT_BitmapGlyph glyph, int offX, int offY, int base)
376{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100377 unsigned y;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200378 uint8_t *src_itr = glyph->bitmap.buffer;
379 uint8_t *dest_itr = dest->data;
380
381 if(glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
382 {
383 fprintf(stderr, "Unsupported pixel mode in FT_BitmapGlyph %d\n", glyph->bitmap.pixel_mode);
384 return -1;
385 }
386
387 dest_itr += (offY + base - glyph->top)*dest->stride + (offX + glyph->left);
388
Vojtech Bocek54cf1082015-03-15 16:50:26 +0100389 // FIXME: if glyph->left is negative and everything else is 0 (e.g. letter 'j' in Roboto-Regular),
390 // the result might end up being before the buffer - I'm not sure how to properly handle this.
391 if(dest_itr < dest->data)
392 dest_itr = dest->data;
393
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200394 for(y = 0; y < glyph->bitmap.rows; ++y)
395 {
396 memcpy(dest_itr, src_itr, glyph->bitmap.width);
397 src_itr += glyph->bitmap.pitch;
398 dest_itr += dest->stride;
399 }
400 return 0;
401}
402
Vojtech Boceka482f252015-03-15 17:03:50 +0100403static void gr_ttf_calcMaxFontHeight(TrueTypeFont *f)
404{
405 char c;
406 int char_idx;
407 int error;
408 FT_Glyph glyph;
409 FT_BBox bbox;
410 FT_BBox bbox_glyph;
411 TrueTypeCacheEntry *ent;
412
413 bbox.yMin = bbox_glyph.yMin = LONG_MAX;
414 bbox.yMax = bbox_glyph.yMax = LONG_MIN;
415
416 for(c = '!'; c <= '~'; ++c)
417 {
418 char_idx = FT_Get_Char_Index(f->face, c);
419 ent = gr_ttf_glyph_cache_peek(f, char_idx);
420 if(ent)
421 {
422 bbox.yMin = MIN(bbox.yMin, ent->bbox.yMin);
423 bbox.yMax = MAX(bbox.yMax, ent->bbox.yMax);
424 }
425 else
426 {
427 error = FT_Load_Glyph(f->face, char_idx, 0);
428 if(error)
429 continue;
430
431 error = FT_Get_Glyph(f->face->glyph, &glyph);
432 if(error)
433 continue;
434
435 FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox_glyph);
436 bbox.yMin = MIN(bbox.yMin, bbox_glyph.yMin);
437 bbox.yMax = MAX(bbox.yMax, bbox_glyph.yMax);
438
439 FT_Done_Glyph(glyph);
440 }
441 }
442
443 if(bbox.yMin > bbox.yMax)
444 bbox.yMin = bbox.yMax = 0;
445
446 f->max_height = bbox.yMax - bbox.yMin;
447 f->base = bbox.yMax;
448
449 // FIXME: twrp fonts have some padding on top, I'll add it here
450 // Should be fixed in the themes
451 f->max_height += f->size / 4;
452 f->base += f->size / 4;
453}
454
xiaolue738da52015-02-22 20:49:35 +0800455// 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 +0200456static int gr_ttf_render_text(TrueTypeFont *font, GGLSurface *surface, const char *text, int max_width)
457{
458 TrueTypeFont *f = font;
459 TrueTypeCacheEntry *ent;
xiaolue738da52015-02-22 20:49:35 +0800460 int bytes_rendered = 0, total_w = 0;
soyud5a7c0e2014-11-28 14:33:53 +0800461 int utf_bytes = 0;
462 unsigned int unicode = 0;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200463 int i, x, diff, char_idx, prev_idx = 0;
464 int height, base;
465 FT_Vector delta;
466 uint8_t *data = NULL;
467 const char *text_itr = text;
soyud5a7c0e2014-11-28 14:33:53 +0800468 int *char_idxs;
xiaolue738da52015-02-22 20:49:35 +0800469 int char_idxs_len = 0;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200470
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100471 char_idxs = (int *)malloc(strlen(text) * sizeof(int));
xiaolue738da52015-02-22 20:49:35 +0800472
soyud5a7c0e2014-11-28 14:33:53 +0800473 while(*text_itr)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200474 {
soyud5a7c0e2014-11-28 14:33:53 +0800475 utf_bytes = utf8_to_unicode(text_itr, &unicode);
xiaolue738da52015-02-22 20:49:35 +0800476 text_itr += utf_bytes;
477 bytes_rendered += utf_bytes;
478
soyud5a7c0e2014-11-28 14:33:53 +0800479 char_idx = FT_Get_Char_Index(f->face, unicode);
xiaolue738da52015-02-22 20:49:35 +0800480 char_idxs[char_idxs_len] = char_idx;
481
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200482 ent = gr_ttf_glyph_cache_get(f, char_idx);
483 if(ent)
484 {
485 diff = ent->glyph->root.advance.x >> 16;
486
487 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
488 {
489 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
490 diff += delta.x >> 6;
491 }
492
493 if(max_width != -1 && total_w + diff > max_width)
494 break;
495
496 total_w += diff;
497 }
498 prev_idx = char_idx;
xiaolue738da52015-02-22 20:49:35 +0800499 ++char_idxs_len;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200500 }
501
502 if(font->max_height == -1)
Vojtech Boceka482f252015-03-15 17:03:50 +0100503 gr_ttf_calcMaxFontHeight(font);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200504
505 if(font->max_height == -1)
soyud5a7c0e2014-11-28 14:33:53 +0800506 {
507 free(char_idxs);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200508 return -1;
soyud5a7c0e2014-11-28 14:33:53 +0800509 }
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200510
511 height = font->max_height;
512
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100513 data = (uint8_t *)malloc(total_w*height);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200514 memset(data, 0, total_w*height);
515 x = 0;
516 prev_idx = 0;
517
518 surface->version = sizeof(*surface);
519 surface->width = total_w;
520 surface->height = height;
521 surface->stride = total_w;
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100522 surface->data = (GGLubyte*)data;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200523 surface->format = GGL_PIXEL_FORMAT_A_8;
524
xiaolue738da52015-02-22 20:49:35 +0800525 for(i = 0; i < char_idxs_len; ++i)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200526 {
soyud5a7c0e2014-11-28 14:33:53 +0800527 char_idx = char_idxs[i];
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200528 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
529 {
530 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
531 x += delta.x >> 6;
532 }
533
534 ent = gr_ttf_glyph_cache_get(f, char_idx);
535 if(ent)
536 {
537 gr_ttf_copy_glyph_to_surface(surface, ent->glyph, x, 0, font->base);
538 x += ent->glyph->root.advance.x >> 16;
539 }
540
541 prev_idx = char_idx;
542 }
543
soyud5a7c0e2014-11-28 14:33:53 +0800544 free(char_idxs);
xiaolue738da52015-02-22 20:49:35 +0800545 return bytes_rendered;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200546}
547
548static StringCacheEntry *gr_ttf_string_cache_peek(TrueTypeFont *font, const char *text, int max_width)
549{
550 StringCacheEntry *res;
551 StringCacheKey k = {
552 .text = (char*)text,
553 .max_width = max_width
554 };
555
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100556 return (StringCacheEntry *)hashmapGet(font->string_cache, &k);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200557}
558
559static StringCacheEntry *gr_ttf_string_cache_get(TrueTypeFont *font, const char *text, int max_width)
560{
561 StringCacheEntry *res;
562 StringCacheKey k = {
563 .text = (char*)text,
564 .max_width = max_width
565 };
566
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100567 res = (StringCacheEntry *)hashmapGet(font->string_cache, &k);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200568 if(!res)
569 {
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100570 res = (StringCacheEntry *)malloc(sizeof(StringCacheEntry));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200571 memset(res, 0, sizeof(StringCacheEntry));
xiaolue738da52015-02-22 20:49:35 +0800572 res->rendered_bytes = gr_ttf_render_text(font, &res->surface, text, max_width);
573 if(res->rendered_bytes < 0)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200574 {
575 free(res);
576 return NULL;
577 }
578
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100579 StringCacheKey *new_key = (StringCacheKey *)malloc(sizeof(StringCacheKey));
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200580 memset(new_key, 0, sizeof(StringCacheKey));
581 new_key->max_width = max_width;
582 new_key->text = strdup(text);
583
584 res->key = new_key;
585
586 if(font->string_cache_tail)
587 {
588 res->prev = font->string_cache_tail;
589 res->prev->next = res;
590 }
591 else
592 font->string_cache_head = res;
593 font->string_cache_tail = res;
594
595 hashmapPut(font->string_cache, new_key, res);
596 }
597 else if(res->next)
598 {
599 // move this entry to the tail of the linked list
600 // if it isn't already there
601 if(res->prev)
602 res->prev->next = res->next;
603
604 res->next->prev = res->prev;
605
606 if(!res->prev)
607 font->string_cache_head = res->next;
608
609 res->next = NULL;
610 res->prev = font->string_cache_tail;
611 res->prev->next = res;
612 font->string_cache_tail = res;
613
614 // truncate old entries
615 if(hashmapSize(font->string_cache) >= STRING_CACHE_MAX_ENTRIES)
616 {
617 printf("Truncating string cache entries.\n");
618 int i;
619 StringCacheEntry *ent;
620 for(i = 0; i < STRING_CACHE_TRUNCATE_ENTRIES; ++i)
621 {
622 ent = font->string_cache_head;
623 font->string_cache_head = ent->next;
624 font->string_cache_head->prev = NULL;
625
626 hashmapRemove(font->string_cache, ent->key);
627
628 gr_ttf_freeStringCache(ent->key, ent, NULL);
629 }
630 }
631 }
632 return res;
633}
634
635int gr_ttf_measureEx(const char *s, void *font)
636{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100637 TrueTypeFont *f = (TrueTypeFont *)font;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200638 int res = -1;
639
640 pthread_mutex_lock(&f->mutex);
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100641 StringCacheEntry *e = gr_ttf_string_cache_get(f, s, -1);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200642 if(e)
643 res = e->surface.width;
644 pthread_mutex_unlock(&f->mutex);
645
646 return res;
647}
648
649int gr_ttf_maxExW(const char *s, void *font, int max_width)
650{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100651 TrueTypeFont *f = (TrueTypeFont *)font;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200652 TrueTypeCacheEntry *ent;
xiaolue738da52015-02-22 20:49:35 +0800653 int max_bytes = 0, total_w = 0;
654 int utf_bytes, prev_utf_bytes = 0;
655 unsigned int unicode = 0;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200656 int char_idx, prev_idx = 0;
657 FT_Vector delta;
658 StringCacheEntry *e;
659
660 pthread_mutex_lock(&f->mutex);
661
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100662 e = gr_ttf_string_cache_peek(f, s, max_width);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200663 if(e)
664 {
xiaolue738da52015-02-22 20:49:35 +0800665 max_bytes = e->rendered_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200666 pthread_mutex_unlock(&f->mutex);
xiaolue738da52015-02-22 20:49:35 +0800667 return max_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200668 }
669
xiaolue738da52015-02-22 20:49:35 +0800670 while(*s)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200671 {
soyud5a7c0e2014-11-28 14:33:53 +0800672 utf_bytes = utf8_to_unicode(s, &unicode);
xiaolue738da52015-02-22 20:49:35 +0800673 s += utf_bytes;
674
675 char_idx = FT_Get_Char_Index(f->face, unicode);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200676 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
677 {
678 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
679 total_w += delta.x >> 6;
680 }
681 prev_idx = char_idx;
682
683 if(total_w > max_width)
xiaolue738da52015-02-22 20:49:35 +0800684 {
685 max_bytes -= prev_utf_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200686 break;
xiaolue738da52015-02-22 20:49:35 +0800687 }
688 prev_utf_bytes = utf_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200689
690 ent = gr_ttf_glyph_cache_get(f, char_idx);
691 if(!ent)
692 continue;
693
694 total_w += ent->glyph->root.advance.x >> 16;
xiaolue738da52015-02-22 20:49:35 +0800695 max_bytes += utf_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200696 }
697 pthread_mutex_unlock(&f->mutex);
xiaolue738da52015-02-22 20:49:35 +0800698 return max_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200699}
700
701int gr_ttf_textExWH(void *context, int x, int y, const char *s, void *pFont, int max_width, int max_height)
702{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100703 GGLContext *gl = (GGLContext *)context;
704 TrueTypeFont *font = (TrueTypeFont *)pFont;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200705
706 // not actualy max width, but max_width + x
707 if(max_width != -1)
708 {
709 max_width -= x;
710 if(max_width <= 0)
711 return 0;
712 }
713
714 pthread_mutex_lock(&font->mutex);
715
716 StringCacheEntry *e = gr_ttf_string_cache_get(font, s, max_width);
717 if(!e)
718 {
719 pthread_mutex_unlock(&font->mutex);
720 return -1;
721 }
722
723 int y_bottom = y + e->surface.height;
xiaolue738da52015-02-22 20:49:35 +0800724 int res = e->rendered_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200725
726 if(max_height != -1 && max_height < y_bottom)
727 {
728 y_bottom = max_height;
729 if(y_bottom <= y)
730 {
731 pthread_mutex_unlock(&font->mutex);
732 return 0;
733 }
734 }
735
736 gl->bindTexture(gl, &e->surface);
737 gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
738 gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
739 gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200740
Vojtech Bocek3041c882015-03-06 00:28:21 +0100741 gl->enable(gl, GGL_TEXTURE_2D);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200742 gl->texCoord2i(gl, -x, -y);
743 gl->recti(gl, x, y, x + e->surface.width, y_bottom);
Vojtech Bocek3041c882015-03-06 00:28:21 +0100744 gl->disable(gl, GGL_TEXTURE_2D);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200745
746 pthread_mutex_unlock(&font->mutex);
747 return res;
748}
749
750int gr_ttf_getMaxFontHeight(void *font)
751{
752 int res;
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100753 TrueTypeFont *f = (TrueTypeFont *)font;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200754
755 pthread_mutex_lock(&f->mutex);
756
757 if(f->max_height == -1)
Vojtech Boceka482f252015-03-15 17:03:50 +0100758 gr_ttf_calcMaxFontHeight(f);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200759 res = f->max_height;
760
761 pthread_mutex_unlock(&f->mutex);
762 return res;
763}
764
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100765static bool gr_ttf_dump_stats_count_string_cache(void *key __unused, void *value, void *context)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200766{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100767 int *string_cache_size = (int *)context;
768 StringCacheEntry *e = (StringCacheEntry *)value;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200769 *string_cache_size += e->surface.height*e->surface.width + sizeof(StringCacheEntry);
770 return true;
771}
772
773static bool gr_ttf_dump_stats_font(void *key, void *value, void *context)
774{
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100775 TrueTypeFontKey *k = (TrueTypeFontKey *)key;
776 TrueTypeFont *f = (TrueTypeFont *)value;
777 int *total_string_cache_size = (int *)context;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200778 int string_cache_size = 0;
779
780 pthread_mutex_lock(&f->mutex);
781
782 hashmapForEach(f->string_cache, gr_ttf_dump_stats_count_string_cache, &string_cache_size);
783
784 printf(" Font %s (size %d, dpi %d):\n"
785 " refcount: %d\n"
786 " max_height: %d\n"
787 " base: %d\n"
788 " glyph_cache: %d entries\n"
789 " string_cache: %d entries (%.2f kB)\n",
790 k->path, k->size, k->dpi,
791 f->refcount, f->max_height, f->base,
792 hashmapSize(f->glyph_cache),
793 hashmapSize(f->string_cache), ((double)string_cache_size)/1024);
794
795 pthread_mutex_unlock(&f->mutex);
796
797 *total_string_cache_size += string_cache_size;
798 return true;
799}
800
801void gr_ttf_dump_stats(void)
802{
803 pthread_mutex_lock(&font_data.mutex);
804
805 printf("TrueType fonts system stats: ");
806 if(!font_data.fonts)
807 printf("no truetype fonts loaded.\n");
808 else
809 {
810 int total_string_cache_size = 0;
811 printf("%d fonts loaded.\n", hashmapSize(font_data.fonts));
812 hashmapForEach(font_data.fonts, gr_ttf_dump_stats_font, &total_string_cache_size);
813 printf(" Total string cache size: %.2f kB\n", ((double)total_string_cache_size)/1024);
814 }
815
816 pthread_mutex_unlock(&font_data.mutex);
817}