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