blob: 8f62ff26b103022013fe65fdcca293fdc8a63d3a [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{
91 uint8_t *d8 = data;
92 uint32_t *d32 = data;
93 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
soyud5a7c0e2014-11-28 14:33:53 +0800121int utf8_to_unicode(unsigned char* pIn, unsigned int *pOut)
122{
soyud5a7c0e2014-11-28 14:33:53 +0800123 int utf_bytes = 1;
124 unsigned int unicode = 0;
125 unsigned char tmp;
126 tmp = *pIn++;
127 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{
160 StringCacheKey *a = keyA;
161 StringCacheKey *b = keyB;
162 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{
167 StringCacheKey *k = key;
168 return fnv_hash(k->text, strlen(k->text));
169}
170
171static bool gr_ttf_font_cache_equals(void *keyA, void *keyB)
172{
173 TrueTypeFontKey *a = keyA;
174 TrueTypeFontKey *b = keyB;
175 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{
180 TrueTypeFontKey *k = key;
181
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;
192
193 pthread_mutex_lock(&font_data.mutex);
194
195 if(font_data.fonts)
196 {
197 TrueTypeFontKey k = {
198 .size = size,
199 .dpi = dpi,
200 .path = (char*)filename
201 };
202
203 res = hashmapGet(font_data.fonts, &k);
204 if(res)
205 {
206 ++res->refcount;
207 goto exit;
208 }
209 }
210
211 if(!font_data.ft_library)
212 {
213 error = FT_Init_FreeType(&font_data.ft_library);
214 if(error)
215 {
216 fprintf(stderr, "Failed to init libfreetype! %d\n", error);
217 goto exit;
218 }
219 }
220
221 FT_Face face;
222 error = FT_New_Face(font_data.ft_library, filename, 0, &face);
223 if(error)
224 {
225 fprintf(stderr, "Failed to load truetype face %s: %d\n", filename, error);
226 goto exit;
227 }
228
229 error = FT_Set_Char_Size(face, 0, size*16, dpi, dpi);
230 if(error)
231 {
232 fprintf(stderr, "Failed to set truetype face size to %d, dpi %d: %d\n", size, dpi, error);
233 FT_Done_Face(face);
234 goto exit;
235 }
236
237 res = malloc(sizeof(TrueTypeFont));
238 memset(res, 0, sizeof(TrueTypeFont));
239 res->type = FONT_TYPE_TTF;
240 res->size = size;
241 res->dpi = dpi;
242 res->face = face;
243 res->max_height = -1;
244 res->base = -1;
245 res->refcount = 1;
246 res->glyph_cache = hashmapCreate(32, hashmapIntHash, hashmapIntEquals);
247 res->string_cache = hashmapCreate(128, gr_ttf_string_cache_hash, gr_ttf_string_cache_equals);
248 pthread_mutex_init(&res->mutex, 0);
249
250 if(!font_data.fonts)
251 font_data.fonts = hashmapCreate(4, gr_ttf_font_cache_hash, gr_ttf_font_cache_equals);
252
253 TrueTypeFontKey *key = malloc(sizeof(TrueTypeFontKey));
254 memset(key, 0, sizeof(TrueTypeFontKey));
255 key->path = strdup(filename);
256 key->size = size;
257 key->dpi = dpi;
258
259 res->key = key;
260
261 hashmapPut(font_data.fonts, key, res);
262
263exit:
264 pthread_mutex_unlock(&font_data.mutex);
265 return res;
266}
267
268static bool gr_ttf_freeFontCache(void *key, void *value, void *context)
269{
270 TrueTypeCacheEntry *e = value;
271 FT_Done_Glyph((FT_Glyph)e->glyph);
272 free(e);
273 free(key);
274 return true;
275}
276
277static bool gr_ttf_freeStringCache(void *key, void *value, void *context)
278{
279 StringCacheKey *k = key;
280 free(k->text);
281 free(k);
282
283 StringCacheEntry *e = value;
284 free(e->surface.data);
285 free(e);
286 return true;
287}
288
289void gr_ttf_freeFont(void *font)
290{
291 pthread_mutex_lock(&font_data.mutex);
292
293 TrueTypeFont *d = font;
294
295 if(--d->refcount == 0)
296 {
297 hashmapRemove(font_data.fonts, d->key);
298
299 if(hashmapSize(font_data.fonts) == 0)
300 {
301 hashmapFree(font_data.fonts);
302 font_data.fonts = NULL;
303 }
304
305 free(d->key->path);
306 free(d->key);
307
308 FT_Done_Face(d->face);
309 hashmapForEach(d->string_cache, gr_ttf_freeStringCache, NULL);
310 hashmapFree(d->string_cache);
311 hashmapForEach(d->glyph_cache, gr_ttf_freeFontCache, NULL);
312 hashmapFree(d->glyph_cache);
313 pthread_mutex_destroy(&d->mutex);
314 free(d);
315 }
316
317 pthread_mutex_unlock(&font_data.mutex);
318}
319
320static TrueTypeCacheEntry *gr_ttf_glyph_cache_peek(TrueTypeFont *font, int char_index)
321{
322 return hashmapGet(font->glyph_cache, &char_index);
323}
324
325static TrueTypeCacheEntry *gr_ttf_glyph_cache_get(TrueTypeFont *font, int char_index)
326{
327 TrueTypeCacheEntry *res = hashmapGet(font->glyph_cache, &char_index);
328 if(!res)
329 {
330 int error = FT_Load_Glyph(font->face, char_index, FT_LOAD_RENDER);
331 if(error)
332 {
333 fprintf(stderr, "Failed to load glyph idx %d: %d\n", char_index, error);
334 return NULL;
335 }
336
337 FT_BitmapGlyph glyph;
338 error = FT_Get_Glyph(font->face->glyph, (FT_Glyph*)&glyph);
339 if(error)
340 {
341 fprintf(stderr, "Failed to copy glyph %d: %d\n", char_index, error);
342 return NULL;
343 }
344
345 res = malloc(sizeof(TrueTypeCacheEntry));
346 memset(res, 0, sizeof(TrueTypeCacheEntry));
347 res->glyph = glyph;
348 FT_Glyph_Get_CBox((FT_Glyph)glyph, FT_GLYPH_BBOX_PIXELS, &res->bbox);
349
350 int *key = malloc(sizeof(int));
351 *key = char_index;
352
353 hashmapPut(font->glyph_cache, key, res);
354 }
355
356 return res;
357}
358
359static int gr_ttf_copy_glyph_to_surface(GGLSurface *dest, FT_BitmapGlyph glyph, int offX, int offY, int base)
360{
361 int y;
362 uint8_t *src_itr = glyph->bitmap.buffer;
363 uint8_t *dest_itr = dest->data;
364
365 if(glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
366 {
367 fprintf(stderr, "Unsupported pixel mode in FT_BitmapGlyph %d\n", glyph->bitmap.pixel_mode);
368 return -1;
369 }
370
371 dest_itr += (offY + base - glyph->top)*dest->stride + (offX + glyph->left);
372
Vojtech Bocek54cf1082015-03-15 16:50:26 +0100373 // FIXME: if glyph->left is negative and everything else is 0 (e.g. letter 'j' in Roboto-Regular),
374 // the result might end up being before the buffer - I'm not sure how to properly handle this.
375 if(dest_itr < dest->data)
376 dest_itr = dest->data;
377
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200378 for(y = 0; y < glyph->bitmap.rows; ++y)
379 {
380 memcpy(dest_itr, src_itr, glyph->bitmap.width);
381 src_itr += glyph->bitmap.pitch;
382 dest_itr += dest->stride;
383 }
384 return 0;
385}
386
Vojtech Boceka482f252015-03-15 17:03:50 +0100387static void gr_ttf_calcMaxFontHeight(TrueTypeFont *f)
388{
389 char c;
390 int char_idx;
391 int error;
392 FT_Glyph glyph;
393 FT_BBox bbox;
394 FT_BBox bbox_glyph;
395 TrueTypeCacheEntry *ent;
396
397 bbox.yMin = bbox_glyph.yMin = LONG_MAX;
398 bbox.yMax = bbox_glyph.yMax = LONG_MIN;
399
400 for(c = '!'; c <= '~'; ++c)
401 {
402 char_idx = FT_Get_Char_Index(f->face, c);
403 ent = gr_ttf_glyph_cache_peek(f, char_idx);
404 if(ent)
405 {
406 bbox.yMin = MIN(bbox.yMin, ent->bbox.yMin);
407 bbox.yMax = MAX(bbox.yMax, ent->bbox.yMax);
408 }
409 else
410 {
411 error = FT_Load_Glyph(f->face, char_idx, 0);
412 if(error)
413 continue;
414
415 error = FT_Get_Glyph(f->face->glyph, &glyph);
416 if(error)
417 continue;
418
419 FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox_glyph);
420 bbox.yMin = MIN(bbox.yMin, bbox_glyph.yMin);
421 bbox.yMax = MAX(bbox.yMax, bbox_glyph.yMax);
422
423 FT_Done_Glyph(glyph);
424 }
425 }
426
427 if(bbox.yMin > bbox.yMax)
428 bbox.yMin = bbox.yMax = 0;
429
430 f->max_height = bbox.yMax - bbox.yMin;
431 f->base = bbox.yMax;
432
433 // FIXME: twrp fonts have some padding on top, I'll add it here
434 // Should be fixed in the themes
435 f->max_height += f->size / 4;
436 f->base += f->size / 4;
437}
438
xiaolue738da52015-02-22 20:49:35 +0800439// 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 +0200440static int gr_ttf_render_text(TrueTypeFont *font, GGLSurface *surface, const char *text, int max_width)
441{
442 TrueTypeFont *f = font;
443 TrueTypeCacheEntry *ent;
xiaolue738da52015-02-22 20:49:35 +0800444 int bytes_rendered = 0, total_w = 0;
soyud5a7c0e2014-11-28 14:33:53 +0800445 int utf_bytes = 0;
446 unsigned int unicode = 0;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200447 int i, x, diff, char_idx, prev_idx = 0;
448 int height, base;
449 FT_Vector delta;
450 uint8_t *data = NULL;
451 const char *text_itr = text;
soyud5a7c0e2014-11-28 14:33:53 +0800452 int *char_idxs;
xiaolue738da52015-02-22 20:49:35 +0800453 int char_idxs_len = 0;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200454
xiaolue738da52015-02-22 20:49:35 +0800455 char_idxs = malloc(strlen(text) * sizeof(int));
456
soyud5a7c0e2014-11-28 14:33:53 +0800457 while(*text_itr)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200458 {
soyud5a7c0e2014-11-28 14:33:53 +0800459 utf_bytes = utf8_to_unicode(text_itr, &unicode);
xiaolue738da52015-02-22 20:49:35 +0800460 text_itr += utf_bytes;
461 bytes_rendered += utf_bytes;
462
soyud5a7c0e2014-11-28 14:33:53 +0800463 char_idx = FT_Get_Char_Index(f->face, unicode);
xiaolue738da52015-02-22 20:49:35 +0800464 char_idxs[char_idxs_len] = char_idx;
465
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200466 ent = gr_ttf_glyph_cache_get(f, char_idx);
467 if(ent)
468 {
469 diff = ent->glyph->root.advance.x >> 16;
470
471 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
472 {
473 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
474 diff += delta.x >> 6;
475 }
476
477 if(max_width != -1 && total_w + diff > max_width)
478 break;
479
480 total_w += diff;
481 }
482 prev_idx = char_idx;
xiaolue738da52015-02-22 20:49:35 +0800483 ++char_idxs_len;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200484 }
485
486 if(font->max_height == -1)
Vojtech Boceka482f252015-03-15 17:03:50 +0100487 gr_ttf_calcMaxFontHeight(font);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200488
489 if(font->max_height == -1)
soyud5a7c0e2014-11-28 14:33:53 +0800490 {
491 free(char_idxs);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200492 return -1;
soyud5a7c0e2014-11-28 14:33:53 +0800493 }
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200494
495 height = font->max_height;
496
497 data = malloc(total_w*height);
498 memset(data, 0, total_w*height);
499 x = 0;
500 prev_idx = 0;
501
502 surface->version = sizeof(*surface);
503 surface->width = total_w;
504 surface->height = height;
505 surface->stride = total_w;
506 surface->data = (void*)data;
507 surface->format = GGL_PIXEL_FORMAT_A_8;
508
xiaolue738da52015-02-22 20:49:35 +0800509 for(i = 0; i < char_idxs_len; ++i)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200510 {
soyud5a7c0e2014-11-28 14:33:53 +0800511 char_idx = char_idxs[i];
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200512 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
513 {
514 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
515 x += delta.x >> 6;
516 }
517
518 ent = gr_ttf_glyph_cache_get(f, char_idx);
519 if(ent)
520 {
521 gr_ttf_copy_glyph_to_surface(surface, ent->glyph, x, 0, font->base);
522 x += ent->glyph->root.advance.x >> 16;
523 }
524
525 prev_idx = char_idx;
526 }
527
soyud5a7c0e2014-11-28 14:33:53 +0800528 free(char_idxs);
xiaolue738da52015-02-22 20:49:35 +0800529 return bytes_rendered;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200530}
531
532static StringCacheEntry *gr_ttf_string_cache_peek(TrueTypeFont *font, const char *text, int max_width)
533{
534 StringCacheEntry *res;
535 StringCacheKey k = {
536 .text = (char*)text,
537 .max_width = max_width
538 };
539
540 return hashmapGet(font->string_cache, &k);
541}
542
543static StringCacheEntry *gr_ttf_string_cache_get(TrueTypeFont *font, const char *text, int max_width)
544{
545 StringCacheEntry *res;
546 StringCacheKey k = {
547 .text = (char*)text,
548 .max_width = max_width
549 };
550
551 res = hashmapGet(font->string_cache, &k);
552 if(!res)
553 {
554 res = malloc(sizeof(StringCacheEntry));
555 memset(res, 0, sizeof(StringCacheEntry));
xiaolue738da52015-02-22 20:49:35 +0800556 res->rendered_bytes = gr_ttf_render_text(font, &res->surface, text, max_width);
557 if(res->rendered_bytes < 0)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200558 {
559 free(res);
560 return NULL;
561 }
562
563 StringCacheKey *new_key = malloc(sizeof(StringCacheKey));
564 memset(new_key, 0, sizeof(StringCacheKey));
565 new_key->max_width = max_width;
566 new_key->text = strdup(text);
567
568 res->key = new_key;
569
570 if(font->string_cache_tail)
571 {
572 res->prev = font->string_cache_tail;
573 res->prev->next = res;
574 }
575 else
576 font->string_cache_head = res;
577 font->string_cache_tail = res;
578
579 hashmapPut(font->string_cache, new_key, res);
580 }
581 else if(res->next)
582 {
583 // move this entry to the tail of the linked list
584 // if it isn't already there
585 if(res->prev)
586 res->prev->next = res->next;
587
588 res->next->prev = res->prev;
589
590 if(!res->prev)
591 font->string_cache_head = res->next;
592
593 res->next = NULL;
594 res->prev = font->string_cache_tail;
595 res->prev->next = res;
596 font->string_cache_tail = res;
597
598 // truncate old entries
599 if(hashmapSize(font->string_cache) >= STRING_CACHE_MAX_ENTRIES)
600 {
601 printf("Truncating string cache entries.\n");
602 int i;
603 StringCacheEntry *ent;
604 for(i = 0; i < STRING_CACHE_TRUNCATE_ENTRIES; ++i)
605 {
606 ent = font->string_cache_head;
607 font->string_cache_head = ent->next;
608 font->string_cache_head->prev = NULL;
609
610 hashmapRemove(font->string_cache, ent->key);
611
612 gr_ttf_freeStringCache(ent->key, ent, NULL);
613 }
614 }
615 }
616 return res;
617}
618
619int gr_ttf_measureEx(const char *s, void *font)
620{
621 TrueTypeFont *f = font;
622 int res = -1;
623
624 pthread_mutex_lock(&f->mutex);
625 StringCacheEntry *e = gr_ttf_string_cache_get(font, s, -1);
626 if(e)
627 res = e->surface.width;
628 pthread_mutex_unlock(&f->mutex);
629
630 return res;
631}
632
633int gr_ttf_maxExW(const char *s, void *font, int max_width)
634{
635 TrueTypeFont *f = font;
636 TrueTypeCacheEntry *ent;
xiaolue738da52015-02-22 20:49:35 +0800637 int max_bytes = 0, total_w = 0;
638 int utf_bytes, prev_utf_bytes = 0;
639 unsigned int unicode = 0;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200640 int char_idx, prev_idx = 0;
641 FT_Vector delta;
642 StringCacheEntry *e;
643
644 pthread_mutex_lock(&f->mutex);
645
646 e = gr_ttf_string_cache_peek(font, s, max_width);
647 if(e)
648 {
xiaolue738da52015-02-22 20:49:35 +0800649 max_bytes = e->rendered_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200650 pthread_mutex_unlock(&f->mutex);
xiaolue738da52015-02-22 20:49:35 +0800651 return max_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200652 }
653
xiaolue738da52015-02-22 20:49:35 +0800654 while(*s)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200655 {
soyud5a7c0e2014-11-28 14:33:53 +0800656 utf_bytes = utf8_to_unicode(s, &unicode);
xiaolue738da52015-02-22 20:49:35 +0800657 s += utf_bytes;
658
659 char_idx = FT_Get_Char_Index(f->face, unicode);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200660 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
661 {
662 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
663 total_w += delta.x >> 6;
664 }
665 prev_idx = char_idx;
666
667 if(total_w > max_width)
xiaolue738da52015-02-22 20:49:35 +0800668 {
669 max_bytes -= prev_utf_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200670 break;
xiaolue738da52015-02-22 20:49:35 +0800671 }
672 prev_utf_bytes = utf_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200673
674 ent = gr_ttf_glyph_cache_get(f, char_idx);
675 if(!ent)
676 continue;
677
678 total_w += ent->glyph->root.advance.x >> 16;
xiaolue738da52015-02-22 20:49:35 +0800679 max_bytes += utf_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200680 }
681 pthread_mutex_unlock(&f->mutex);
xiaolue738da52015-02-22 20:49:35 +0800682 return max_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200683}
684
685int gr_ttf_textExWH(void *context, int x, int y, const char *s, void *pFont, int max_width, int max_height)
686{
687 GGLContext *gl = context;
688 TrueTypeFont *font = pFont;
689
690 // not actualy max width, but max_width + x
691 if(max_width != -1)
692 {
693 max_width -= x;
694 if(max_width <= 0)
695 return 0;
696 }
697
698 pthread_mutex_lock(&font->mutex);
699
700 StringCacheEntry *e = gr_ttf_string_cache_get(font, s, max_width);
701 if(!e)
702 {
703 pthread_mutex_unlock(&font->mutex);
704 return -1;
705 }
706
707 int y_bottom = y + e->surface.height;
xiaolue738da52015-02-22 20:49:35 +0800708 int res = e->rendered_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200709
710 if(max_height != -1 && max_height < y_bottom)
711 {
712 y_bottom = max_height;
713 if(y_bottom <= y)
714 {
715 pthread_mutex_unlock(&font->mutex);
716 return 0;
717 }
718 }
719
720 gl->bindTexture(gl, &e->surface);
721 gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
722 gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
723 gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200724
Vojtech Bocek3041c882015-03-06 00:28:21 +0100725 gl->enable(gl, GGL_TEXTURE_2D);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200726 gl->texCoord2i(gl, -x, -y);
727 gl->recti(gl, x, y, x + e->surface.width, y_bottom);
Vojtech Bocek3041c882015-03-06 00:28:21 +0100728 gl->disable(gl, GGL_TEXTURE_2D);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200729
730 pthread_mutex_unlock(&font->mutex);
731 return res;
732}
733
734int gr_ttf_getMaxFontHeight(void *font)
735{
736 int res;
737 TrueTypeFont *f = font;
738
739 pthread_mutex_lock(&f->mutex);
740
741 if(f->max_height == -1)
Vojtech Boceka482f252015-03-15 17:03:50 +0100742 gr_ttf_calcMaxFontHeight(f);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200743 res = f->max_height;
744
745 pthread_mutex_unlock(&f->mutex);
746 return res;
747}
748
749static bool gr_ttf_dump_stats_count_string_cache(void *key, void *value, void *context)
750{
751 int *string_cache_size = context;
752 StringCacheEntry *e = value;
753 *string_cache_size += e->surface.height*e->surface.width + sizeof(StringCacheEntry);
754 return true;
755}
756
757static bool gr_ttf_dump_stats_font(void *key, void *value, void *context)
758{
759 TrueTypeFontKey *k = key;
760 TrueTypeFont *f = value;
761 int *total_string_cache_size = context;
762 int string_cache_size = 0;
763
764 pthread_mutex_lock(&f->mutex);
765
766 hashmapForEach(f->string_cache, gr_ttf_dump_stats_count_string_cache, &string_cache_size);
767
768 printf(" Font %s (size %d, dpi %d):\n"
769 " refcount: %d\n"
770 " max_height: %d\n"
771 " base: %d\n"
772 " glyph_cache: %d entries\n"
773 " string_cache: %d entries (%.2f kB)\n",
774 k->path, k->size, k->dpi,
775 f->refcount, f->max_height, f->base,
776 hashmapSize(f->glyph_cache),
777 hashmapSize(f->string_cache), ((double)string_cache_size)/1024);
778
779 pthread_mutex_unlock(&f->mutex);
780
781 *total_string_cache_size += string_cache_size;
782 return true;
783}
784
785void gr_ttf_dump_stats(void)
786{
787 pthread_mutex_lock(&font_data.mutex);
788
789 printf("TrueType fonts system stats: ");
790 if(!font_data.fonts)
791 printf("no truetype fonts loaded.\n");
792 else
793 {
794 int total_string_cache_size = 0;
795 printf("%d fonts loaded.\n", hashmapSize(font_data.fonts));
796 hashmapForEach(font_data.fonts, gr_ttf_dump_stats_font, &total_string_cache_size);
797 printf(" Total string cache size: %.2f kB\n", ((double)total_string_cache_size)/1024);
798 }
799
800 pthread_mutex_unlock(&font_data.mutex);
801}