blob: afe5d4c2816ffdc5428197ead2024f82d5538755 [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;
60 int rendered_len;
61 StringCacheKey *key;
62 struct StringCacheEntry *prev;
63 struct StringCacheEntry *next;
64};
65
66typedef struct StringCacheEntry StringCacheEntry;
67
68typedef struct
69{
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{
123 if(pIn == NULL || pOut == NULL)
124 return 0;
125
126 int utf_bytes = 1;
127 unsigned int unicode = 0;
128 unsigned char tmp;
129 tmp = *pIn++;
130 if (tmp < 0x80)
131 {
132 *pOut = tmp;
133 }
134 else
135 {
136 unsigned int high_bit_mask = 0x3F;
137 unsigned int high_bit_shift = 0;
138 int total_bits = 0;
139 while((tmp & 0xC0) == 0xC0)
140 {
141 utf_bytes ++;
142 if(utf_bytes > 6) return 0;
143 tmp = 0xFF & (tmp << 1);
144 total_bits += 6;
145 high_bit_mask >>= 1;
146 high_bit_shift++;
147 unicode <<= 6;
148 unicode |= (*pIn++) & 0x3F;
149 }
150 unicode |= ((tmp >> high_bit_shift) & high_bit_mask) << total_bits;
151 *pOut = unicode;
152 }
153
154 return utf_bytes;
155}
156
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200157static bool gr_ttf_string_cache_equals(void *keyA, void *keyB)
158{
159 StringCacheKey *a = keyA;
160 StringCacheKey *b = keyB;
161 return a->max_width == b->max_width && strcmp(a->text, b->text) == 0;
162}
163
164static int gr_ttf_string_cache_hash(void *key)
165{
166 StringCacheKey *k = key;
167 return fnv_hash(k->text, strlen(k->text));
168}
169
170static bool gr_ttf_font_cache_equals(void *keyA, void *keyB)
171{
172 TrueTypeFontKey *a = keyA;
173 TrueTypeFontKey *b = keyB;
174 return (a->size == b->size) && (a->dpi == b->dpi) && !strcmp(a->path, b->path);
175}
176
177static int gr_ttf_font_cache_hash(void *key)
178{
179 TrueTypeFontKey *k = key;
180
181 uint32_t hash = fnv_hash(k->path, strlen(k->path));
182 hash = fnv_hash_add(hash, k->size);
183 hash = fnv_hash_add(hash, k->dpi);
184 return hash;
185}
186
187void *gr_ttf_loadFont(const char *filename, int size, int dpi)
188{
189 int error;
190 TrueTypeFont *res = NULL;
191
192 pthread_mutex_lock(&font_data.mutex);
193
194 if(font_data.fonts)
195 {
196 TrueTypeFontKey k = {
197 .size = size,
198 .dpi = dpi,
199 .path = (char*)filename
200 };
201
202 res = hashmapGet(font_data.fonts, &k);
203 if(res)
204 {
205 ++res->refcount;
206 goto exit;
207 }
208 }
209
210 if(!font_data.ft_library)
211 {
212 error = FT_Init_FreeType(&font_data.ft_library);
213 if(error)
214 {
215 fprintf(stderr, "Failed to init libfreetype! %d\n", error);
216 goto exit;
217 }
218 }
219
220 FT_Face face;
221 error = FT_New_Face(font_data.ft_library, filename, 0, &face);
222 if(error)
223 {
224 fprintf(stderr, "Failed to load truetype face %s: %d\n", filename, error);
225 goto exit;
226 }
227
228 error = FT_Set_Char_Size(face, 0, size*16, dpi, dpi);
229 if(error)
230 {
231 fprintf(stderr, "Failed to set truetype face size to %d, dpi %d: %d\n", size, dpi, error);
232 FT_Done_Face(face);
233 goto exit;
234 }
235
236 res = malloc(sizeof(TrueTypeFont));
237 memset(res, 0, sizeof(TrueTypeFont));
238 res->type = FONT_TYPE_TTF;
239 res->size = size;
240 res->dpi = dpi;
241 res->face = face;
242 res->max_height = -1;
243 res->base = -1;
244 res->refcount = 1;
245 res->glyph_cache = hashmapCreate(32, hashmapIntHash, hashmapIntEquals);
246 res->string_cache = hashmapCreate(128, gr_ttf_string_cache_hash, gr_ttf_string_cache_equals);
247 pthread_mutex_init(&res->mutex, 0);
248
249 if(!font_data.fonts)
250 font_data.fonts = hashmapCreate(4, gr_ttf_font_cache_hash, gr_ttf_font_cache_equals);
251
252 TrueTypeFontKey *key = malloc(sizeof(TrueTypeFontKey));
253 memset(key, 0, sizeof(TrueTypeFontKey));
254 key->path = strdup(filename);
255 key->size = size;
256 key->dpi = dpi;
257
258 res->key = key;
259
260 hashmapPut(font_data.fonts, key, res);
261
262exit:
263 pthread_mutex_unlock(&font_data.mutex);
264 return res;
265}
266
267static bool gr_ttf_freeFontCache(void *key, void *value, void *context)
268{
269 TrueTypeCacheEntry *e = value;
270 FT_Done_Glyph((FT_Glyph)e->glyph);
271 free(e);
272 free(key);
273 return true;
274}
275
276static bool gr_ttf_freeStringCache(void *key, void *value, void *context)
277{
278 StringCacheKey *k = key;
279 free(k->text);
280 free(k);
281
282 StringCacheEntry *e = value;
283 free(e->surface.data);
284 free(e);
285 return true;
286}
287
288void gr_ttf_freeFont(void *font)
289{
290 pthread_mutex_lock(&font_data.mutex);
291
292 TrueTypeFont *d = font;
293
294 if(--d->refcount == 0)
295 {
296 hashmapRemove(font_data.fonts, d->key);
297
298 if(hashmapSize(font_data.fonts) == 0)
299 {
300 hashmapFree(font_data.fonts);
301 font_data.fonts = NULL;
302 }
303
304 free(d->key->path);
305 free(d->key);
306
307 FT_Done_Face(d->face);
308 hashmapForEach(d->string_cache, gr_ttf_freeStringCache, NULL);
309 hashmapFree(d->string_cache);
310 hashmapForEach(d->glyph_cache, gr_ttf_freeFontCache, NULL);
311 hashmapFree(d->glyph_cache);
312 pthread_mutex_destroy(&d->mutex);
313 free(d);
314 }
315
316 pthread_mutex_unlock(&font_data.mutex);
317}
318
319static TrueTypeCacheEntry *gr_ttf_glyph_cache_peek(TrueTypeFont *font, int char_index)
320{
321 return hashmapGet(font->glyph_cache, &char_index);
322}
323
324static TrueTypeCacheEntry *gr_ttf_glyph_cache_get(TrueTypeFont *font, int char_index)
325{
326 TrueTypeCacheEntry *res = hashmapGet(font->glyph_cache, &char_index);
327 if(!res)
328 {
329 int error = FT_Load_Glyph(font->face, char_index, FT_LOAD_RENDER);
330 if(error)
331 {
332 fprintf(stderr, "Failed to load glyph idx %d: %d\n", char_index, error);
333 return NULL;
334 }
335
336 FT_BitmapGlyph glyph;
337 error = FT_Get_Glyph(font->face->glyph, (FT_Glyph*)&glyph);
338 if(error)
339 {
340 fprintf(stderr, "Failed to copy glyph %d: %d\n", char_index, error);
341 return NULL;
342 }
343
344 res = malloc(sizeof(TrueTypeCacheEntry));
345 memset(res, 0, sizeof(TrueTypeCacheEntry));
346 res->glyph = glyph;
347 FT_Glyph_Get_CBox((FT_Glyph)glyph, FT_GLYPH_BBOX_PIXELS, &res->bbox);
348
349 int *key = malloc(sizeof(int));
350 *key = char_index;
351
352 hashmapPut(font->glyph_cache, key, res);
353 }
354
355 return res;
356}
357
358static int gr_ttf_copy_glyph_to_surface(GGLSurface *dest, FT_BitmapGlyph glyph, int offX, int offY, int base)
359{
360 int y;
361 uint8_t *src_itr = glyph->bitmap.buffer;
362 uint8_t *dest_itr = dest->data;
363
364 if(glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
365 {
366 fprintf(stderr, "Unsupported pixel mode in FT_BitmapGlyph %d\n", glyph->bitmap.pixel_mode);
367 return -1;
368 }
369
370 dest_itr += (offY + base - glyph->top)*dest->stride + (offX + glyph->left);
371
372 for(y = 0; y < glyph->bitmap.rows; ++y)
373 {
374 memcpy(dest_itr, src_itr, glyph->bitmap.width);
375 src_itr += glyph->bitmap.pitch;
376 dest_itr += dest->stride;
377 }
378 return 0;
379}
380
381static int gr_ttf_render_text(TrueTypeFont *font, GGLSurface *surface, const char *text, int max_width)
382{
383 TrueTypeFont *f = font;
384 TrueTypeCacheEntry *ent;
385 int max_len = 0, total_w = 0;
soyud5a7c0e2014-11-28 14:33:53 +0800386 int utf_bytes = 0;
387 unsigned int unicode = 0;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200388 int i, x, diff, char_idx, prev_idx = 0;
389 int height, base;
390 FT_Vector delta;
391 uint8_t *data = NULL;
392 const char *text_itr = text;
soyud5a7c0e2014-11-28 14:33:53 +0800393 int *char_idxs;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200394
soyud5a7c0e2014-11-28 14:33:53 +0800395 char_idxs = (int*)malloc(strlen(text) * sizeof(int));
396 while(*text_itr)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200397 {
soyud5a7c0e2014-11-28 14:33:53 +0800398 utf_bytes = utf8_to_unicode(text_itr, &unicode);
399 text_itr += (utf_bytes == 0)? 1: utf_bytes;
400 char_idx = FT_Get_Char_Index(f->face, unicode);
401 char_idxs[max_len] = char_idx;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200402 ent = gr_ttf_glyph_cache_get(f, char_idx);
403 if(ent)
404 {
405 diff = ent->glyph->root.advance.x >> 16;
406
407 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
408 {
409 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
410 diff += delta.x >> 6;
411 }
412
413 if(max_width != -1 && total_w + diff > max_width)
414 break;
415
416 total_w += diff;
417 }
418 prev_idx = char_idx;
419 ++max_len;
420 }
421
422 if(font->max_height == -1)
423 gr_ttf_getMaxFontHeight(font);
424
425 if(font->max_height == -1)
soyud5a7c0e2014-11-28 14:33:53 +0800426 {
427 free(char_idxs);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200428 return -1;
soyud5a7c0e2014-11-28 14:33:53 +0800429 }
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200430
431 height = font->max_height;
432
433 data = malloc(total_w*height);
434 memset(data, 0, total_w*height);
435 x = 0;
436 prev_idx = 0;
437
438 surface->version = sizeof(*surface);
439 surface->width = total_w;
440 surface->height = height;
441 surface->stride = total_w;
442 surface->data = (void*)data;
443 surface->format = GGL_PIXEL_FORMAT_A_8;
444
445 for(i = 0; i < max_len; ++i)
446 {
soyud5a7c0e2014-11-28 14:33:53 +0800447 char_idx = char_idxs[i];
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200448 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
449 {
450 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
451 x += delta.x >> 6;
452 }
453
454 ent = gr_ttf_glyph_cache_get(f, char_idx);
455 if(ent)
456 {
457 gr_ttf_copy_glyph_to_surface(surface, ent->glyph, x, 0, font->base);
458 x += ent->glyph->root.advance.x >> 16;
459 }
460
461 prev_idx = char_idx;
462 }
463
soyud5a7c0e2014-11-28 14:33:53 +0800464 free(char_idxs);
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200465 return max_len;
466}
467
468static StringCacheEntry *gr_ttf_string_cache_peek(TrueTypeFont *font, const char *text, int max_width)
469{
470 StringCacheEntry *res;
471 StringCacheKey k = {
472 .text = (char*)text,
473 .max_width = max_width
474 };
475
476 return hashmapGet(font->string_cache, &k);
477}
478
479static StringCacheEntry *gr_ttf_string_cache_get(TrueTypeFont *font, const char *text, int max_width)
480{
481 StringCacheEntry *res;
482 StringCacheKey k = {
483 .text = (char*)text,
484 .max_width = max_width
485 };
486
487 res = hashmapGet(font->string_cache, &k);
488 if(!res)
489 {
490 res = malloc(sizeof(StringCacheEntry));
491 memset(res, 0, sizeof(StringCacheEntry));
492 res->rendered_len = gr_ttf_render_text(font, &res->surface, text, max_width);
493 if(res->rendered_len < 0)
494 {
495 free(res);
496 return NULL;
497 }
498
499 StringCacheKey *new_key = malloc(sizeof(StringCacheKey));
500 memset(new_key, 0, sizeof(StringCacheKey));
501 new_key->max_width = max_width;
502 new_key->text = strdup(text);
503
504 res->key = new_key;
505
506 if(font->string_cache_tail)
507 {
508 res->prev = font->string_cache_tail;
509 res->prev->next = res;
510 }
511 else
512 font->string_cache_head = res;
513 font->string_cache_tail = res;
514
515 hashmapPut(font->string_cache, new_key, res);
516 }
517 else if(res->next)
518 {
519 // move this entry to the tail of the linked list
520 // if it isn't already there
521 if(res->prev)
522 res->prev->next = res->next;
523
524 res->next->prev = res->prev;
525
526 if(!res->prev)
527 font->string_cache_head = res->next;
528
529 res->next = NULL;
530 res->prev = font->string_cache_tail;
531 res->prev->next = res;
532 font->string_cache_tail = res;
533
534 // truncate old entries
535 if(hashmapSize(font->string_cache) >= STRING_CACHE_MAX_ENTRIES)
536 {
537 printf("Truncating string cache entries.\n");
538 int i;
539 StringCacheEntry *ent;
540 for(i = 0; i < STRING_CACHE_TRUNCATE_ENTRIES; ++i)
541 {
542 ent = font->string_cache_head;
543 font->string_cache_head = ent->next;
544 font->string_cache_head->prev = NULL;
545
546 hashmapRemove(font->string_cache, ent->key);
547
548 gr_ttf_freeStringCache(ent->key, ent, NULL);
549 }
550 }
551 }
552 return res;
553}
554
555int gr_ttf_measureEx(const char *s, void *font)
556{
557 TrueTypeFont *f = font;
558 int res = -1;
559
560 pthread_mutex_lock(&f->mutex);
561 StringCacheEntry *e = gr_ttf_string_cache_get(font, s, -1);
562 if(e)
563 res = e->surface.width;
564 pthread_mutex_unlock(&f->mutex);
565
566 return res;
567}
568
569int gr_ttf_maxExW(const char *s, void *font, int max_width)
570{
571 TrueTypeFont *f = font;
572 TrueTypeCacheEntry *ent;
573 int max_len = 0, total_w = 0;
soyud5a7c0e2014-11-28 14:33:53 +0800574 int utf_bytes = 0;
575 unsigned int unicode;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200576 int char_idx, prev_idx = 0;
577 FT_Vector delta;
578 StringCacheEntry *e;
579
580 pthread_mutex_lock(&f->mutex);
581
582 e = gr_ttf_string_cache_peek(font, s, max_width);
583 if(e)
584 {
585 max_len = e->rendered_len;
586 pthread_mutex_unlock(&f->mutex);
587 return max_len;
588 }
589
soyud5a7c0e2014-11-28 14:33:53 +0800590 for(; *s; ++max_len)
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200591 {
soyud5a7c0e2014-11-28 14:33:53 +0800592 utf_bytes = utf8_to_unicode(s, &unicode);
593 s += (utf_bytes == 0)? 1: utf_bytes;
Vojtech Bocek76ee9032014-09-07 15:01:56 +0200594 if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
595 {
596 FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
597 total_w += delta.x >> 6;
598 }
599 prev_idx = char_idx;
600
601 if(total_w > max_width)
602 break;
603
604 ent = gr_ttf_glyph_cache_get(f, char_idx);
605 if(!ent)
606 continue;
607
608 total_w += ent->glyph->root.advance.x >> 16;
609 }
610 pthread_mutex_unlock(&f->mutex);
611 return max_len > 0 ? max_len - 1 : 0;
612}
613
614int gr_ttf_textExWH(void *context, int x, int y, const char *s, void *pFont, int max_width, int max_height)
615{
616 GGLContext *gl = context;
617 TrueTypeFont *font = pFont;
618
619 // not actualy max width, but max_width + x
620 if(max_width != -1)
621 {
622 max_width -= x;
623 if(max_width <= 0)
624 return 0;
625 }
626
627 pthread_mutex_lock(&font->mutex);
628
629 StringCacheEntry *e = gr_ttf_string_cache_get(font, s, max_width);
630 if(!e)
631 {
632 pthread_mutex_unlock(&font->mutex);
633 return -1;
634 }
635
636 int y_bottom = y + e->surface.height;
637 int res = e->rendered_len;
638
639 if(max_height != -1 && max_height < y_bottom)
640 {
641 y_bottom = max_height;
642 if(y_bottom <= y)
643 {
644 pthread_mutex_unlock(&font->mutex);
645 return 0;
646 }
647 }
648
649 gl->bindTexture(gl, &e->surface);
650 gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
651 gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
652 gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
653 gl->enable(gl, GGL_TEXTURE_2D);
654
655 gl->texCoord2i(gl, -x, -y);
656 gl->recti(gl, x, y, x + e->surface.width, y_bottom);
657
658 pthread_mutex_unlock(&font->mutex);
659 return res;
660}
661
662int gr_ttf_getMaxFontHeight(void *font)
663{
664 int res;
665 TrueTypeFont *f = font;
666
667 pthread_mutex_lock(&f->mutex);
668
669 if(f->max_height == -1)
670 {
671 char c;
672 int char_idx;
673 int error;
674 FT_Glyph glyph;
675 FT_BBox bbox;
676 FT_BBox bbox_glyph;
677 TrueTypeCacheEntry *ent;
678
679 bbox.yMin = bbox_glyph.yMin = LONG_MAX;
680 bbox.yMax = bbox_glyph.yMax = LONG_MIN;
681
682 for(c = '!'; c <= '~'; ++c)
683 {
684 char_idx = FT_Get_Char_Index(f->face, c);
685 ent = gr_ttf_glyph_cache_peek(f, char_idx);
686 if(ent)
687 {
688 bbox.yMin = MIN(bbox.yMin, ent->bbox.yMin);
689 bbox.yMax = MAX(bbox.yMax, ent->bbox.yMax);
690 }
691 else
692 {
693 error = FT_Load_Glyph(f->face, char_idx, 0);
694 if(error)
695 continue;
696
697 error = FT_Get_Glyph(f->face->glyph, &glyph);
698 if(error)
699 continue;
700
701 FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox_glyph);
702 bbox.yMin = MIN(bbox.yMin, bbox_glyph.yMin);
703 bbox.yMax = MAX(bbox.yMax, bbox_glyph.yMax);
704
705 FT_Done_Glyph(glyph);
706 }
707 }
708
709 if(bbox.yMin > bbox.yMax)
710 bbox.yMin = bbox.yMax = 0;
711
712 f->max_height = bbox.yMax - bbox.yMin;
713 f->base = bbox.yMax;
714
715 // FIXME: twrp fonts have some padding on top, I'll add it here
716 // Should be fixed in the themes
717 f->max_height += f->size / 4;
718 f->base += f->size / 4;
719 }
720
721 res = f->max_height;
722
723 pthread_mutex_unlock(&f->mutex);
724 return res;
725}
726
727static bool gr_ttf_dump_stats_count_string_cache(void *key, void *value, void *context)
728{
729 int *string_cache_size = context;
730 StringCacheEntry *e = value;
731 *string_cache_size += e->surface.height*e->surface.width + sizeof(StringCacheEntry);
732 return true;
733}
734
735static bool gr_ttf_dump_stats_font(void *key, void *value, void *context)
736{
737 TrueTypeFontKey *k = key;
738 TrueTypeFont *f = value;
739 int *total_string_cache_size = context;
740 int string_cache_size = 0;
741
742 pthread_mutex_lock(&f->mutex);
743
744 hashmapForEach(f->string_cache, gr_ttf_dump_stats_count_string_cache, &string_cache_size);
745
746 printf(" Font %s (size %d, dpi %d):\n"
747 " refcount: %d\n"
748 " max_height: %d\n"
749 " base: %d\n"
750 " glyph_cache: %d entries\n"
751 " string_cache: %d entries (%.2f kB)\n",
752 k->path, k->size, k->dpi,
753 f->refcount, f->max_height, f->base,
754 hashmapSize(f->glyph_cache),
755 hashmapSize(f->string_cache), ((double)string_cache_size)/1024);
756
757 pthread_mutex_unlock(&f->mutex);
758
759 *total_string_cache_size += string_cache_size;
760 return true;
761}
762
763void gr_ttf_dump_stats(void)
764{
765 pthread_mutex_lock(&font_data.mutex);
766
767 printf("TrueType fonts system stats: ");
768 if(!font_data.fonts)
769 printf("no truetype fonts loaded.\n");
770 else
771 {
772 int total_string_cache_size = 0;
773 printf("%d fonts loaded.\n", hashmapSize(font_data.fonts));
774 hashmapForEach(font_data.fonts, gr_ttf_dump_stats_font, &total_string_cache_size);
775 printf(" Total string cache size: %.2f kB\n", ((double)total_string_cache_size)/1024);
776 }
777
778 pthread_mutex_unlock(&font_data.mutex);
779}