| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #include <errno.h> |
| #include <stdio.h> |
| |
| #include "minui.h" |
| |
| #include <cutils/hashmap.h> |
| #include <ft2build.h> |
| #include FT_FREETYPE_H |
| #include FT_GLYPH_H |
| |
| #include <pixelflinger/pixelflinger.h> |
| #include <pthread.h> |
| // For std::min and std::max |
| #include <algorithm> |
| |
| #define STRING_CACHE_MAX_ENTRIES 400 |
| #define STRING_CACHE_TRUNCATE_ENTRIES 150 |
| |
| typedef struct |
| { |
| int size; |
| int dpi; |
| char *path; |
| } TrueTypeFontKey; |
| |
| typedef struct |
| { |
| int type; |
| int refcount; |
| int size; |
| int dpi; |
| int max_height; |
| int base; |
| FT_Face face; |
| Hashmap *glyph_cache; |
| Hashmap *string_cache; |
| struct StringCacheEntry *string_cache_head; |
| struct StringCacheEntry *string_cache_tail; |
| pthread_mutex_t mutex; |
| TrueTypeFontKey *key; |
| } TrueTypeFont; |
| |
| typedef struct |
| { |
| FT_BBox bbox; |
| FT_BitmapGlyph glyph; |
| } TrueTypeCacheEntry; |
| |
| typedef struct |
| { |
| char *text; |
| int max_width; |
| } StringCacheKey; |
| |
| struct StringCacheEntry |
| { |
| GGLSurface surface; |
| int rendered_bytes; // number of bytes from C string rendered, not number of UTF8 characters! |
| StringCacheKey *key; |
| struct StringCacheEntry *prev; |
| struct StringCacheEntry *next; |
| }; |
| |
| typedef struct StringCacheEntry StringCacheEntry; |
| |
| typedef struct |
| { |
| FT_Library ft_library; |
| Hashmap *fonts; |
| pthread_mutex_t mutex; |
| } FontData; |
| |
| static FontData font_data = { |
| .ft_library = NULL, |
| .fonts = NULL, |
| .mutex = PTHREAD_MUTEX_INITIALIZER, |
| }; |
| |
| #define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) |
| #define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) |
| |
| // 32bit FNV-1a hash algorithm |
| // http://isthe.com/chongo/tech/comp/fnv/#FNV-1a |
| static const uint32_t FNV_prime = 16777619U; |
| static const uint32_t offset_basis = 2166136261U; |
| |
| static uint32_t fnv_hash(void *data, uint32_t len) |
| { |
| uint8_t *d8 = (uint8_t *)data; |
| uint32_t *d32 = (uint32_t *)data; |
| uint32_t i, max; |
| uint32_t hash = offset_basis; |
| |
| max = len/4; |
| |
| // 32 bit data |
| for(i = 0; i < max; ++i) |
| { |
| hash ^= *d32++; |
| hash *= FNV_prime; |
| } |
| |
| // last bits |
| for(i *= 4; i < len; ++i) |
| { |
| hash ^= (uint32_t) d8[i]; |
| hash *= FNV_prime; |
| } |
| return hash; |
| } |
| |
| static inline uint32_t fnv_hash_add(uint32_t cur_hash, uint32_t word) |
| { |
| cur_hash ^= word; |
| cur_hash *= FNV_prime; |
| return cur_hash; |
| } |
| |
| int utf8_to_unicode(const char* pIn, unsigned int *pOut) |
| { |
| int utf_bytes = 1; |
| unsigned int unicode = 0; |
| unsigned char tmp; |
| tmp = (unsigned char)*pIn++; |
| if (tmp < 0x80) |
| { |
| *pOut = tmp; |
| } |
| else |
| { |
| unsigned int high_bit_mask = 0x3F; |
| unsigned int high_bit_shift = 0; |
| int total_bits = 0; |
| while((tmp & 0xC0) == 0xC0) |
| { |
| utf_bytes ++; |
| if(utf_bytes > 6) |
| { |
| *pOut = tmp; |
| return 1; |
| } |
| tmp = 0xFF & (tmp << 1); |
| total_bits += 6; |
| high_bit_mask >>= 1; |
| high_bit_shift++; |
| unicode <<= 6; |
| unicode |= (*pIn++) & 0x3F; |
| } |
| unicode |= ((tmp >> high_bit_shift) & high_bit_mask) << total_bits; |
| *pOut = unicode; |
| } |
| |
| return utf_bytes; |
| } |
| |
| static bool gr_ttf_string_cache_equals(void *keyA, void *keyB) |
| { |
| StringCacheKey *a = (StringCacheKey *)keyA; |
| StringCacheKey *b = (StringCacheKey *)keyB; |
| return a->max_width == b->max_width && strcmp(a->text, b->text) == 0; |
| } |
| |
| static int gr_ttf_string_cache_hash(void *key) |
| { |
| StringCacheKey *k = (StringCacheKey *)key; |
| return fnv_hash(k->text, strlen(k->text)); |
| } |
| |
| static bool gr_ttf_font_cache_equals(void *keyA, void *keyB) |
| { |
| TrueTypeFontKey *a = (TrueTypeFontKey *)keyA; |
| TrueTypeFontKey *b = (TrueTypeFontKey *)keyB; |
| return (a->size == b->size) && (a->dpi == b->dpi) && !strcmp(a->path, b->path); |
| } |
| |
| static int gr_ttf_font_cache_hash(void *key) |
| { |
| TrueTypeFontKey *k = (TrueTypeFontKey *)key; |
| |
| uint32_t hash = fnv_hash(k->path, strlen(k->path)); |
| hash = fnv_hash_add(hash, k->size); |
| hash = fnv_hash_add(hash, k->dpi); |
| return hash; |
| } |
| |
| void *gr_ttf_loadFont(const char *filename, int size, int dpi) |
| { |
| int error; |
| TrueTypeFont *res = NULL; |
| TrueTypeFontKey *key = NULL; |
| |
| pthread_mutex_lock(&font_data.mutex); |
| |
| if(font_data.fonts) |
| { |
| TrueTypeFontKey k = { |
| .size = size, |
| .dpi = dpi, |
| .path = (char*)filename |
| }; |
| |
| res = (TrueTypeFont *)hashmapGet(font_data.fonts, &k); |
| if(res) |
| { |
| ++res->refcount; |
| goto exit; |
| } |
| } |
| |
| if(!font_data.ft_library) |
| { |
| error = FT_Init_FreeType(&font_data.ft_library); |
| if(error) |
| { |
| fprintf(stderr, "Failed to init libfreetype! %d\n", error); |
| goto exit; |
| } |
| } |
| |
| FT_Face face; |
| error = FT_New_Face(font_data.ft_library, filename, 0, &face); |
| if(error) |
| { |
| fprintf(stderr, "Failed to load truetype face %s: %d\n", filename, error); |
| goto exit; |
| } |
| |
| error = FT_Set_Char_Size(face, 0, size*16, dpi, dpi); |
| if(error) |
| { |
| fprintf(stderr, "Failed to set truetype face size to %d, dpi %d: %d\n", size, dpi, error); |
| FT_Done_Face(face); |
| goto exit; |
| } |
| |
| res = (TrueTypeFont *)malloc(sizeof(TrueTypeFont)); |
| memset(res, 0, sizeof(TrueTypeFont)); |
| res->type = FONT_TYPE_TTF; |
| res->size = size; |
| res->dpi = dpi; |
| res->face = face; |
| res->max_height = -1; |
| res->base = -1; |
| res->refcount = 1; |
| res->glyph_cache = hashmapCreate(32, hashmapIntHash, hashmapIntEquals); |
| res->string_cache = hashmapCreate(128, gr_ttf_string_cache_hash, gr_ttf_string_cache_equals); |
| pthread_mutex_init(&res->mutex, 0); |
| |
| if(!font_data.fonts) |
| font_data.fonts = hashmapCreate(4, gr_ttf_font_cache_hash, gr_ttf_font_cache_equals); |
| |
| key = (TrueTypeFontKey *)malloc(sizeof(TrueTypeFontKey)); |
| memset(key, 0, sizeof(TrueTypeFontKey)); |
| key->path = strdup(filename); |
| key->size = size; |
| key->dpi = dpi; |
| |
| res->key = key; |
| |
| hashmapPut(font_data.fonts, key, res); |
| |
| exit: |
| pthread_mutex_unlock(&font_data.mutex); |
| return res; |
| } |
| |
| void *gr_ttf_scaleFont(void *font, int max_width, int measured_width) |
| { |
| if (!font) |
| return NULL; |
| |
| TrueTypeFont *f = (TrueTypeFont *)font; |
| float scale_value = (float)(max_width) / (float)(measured_width); |
| int new_size = ((int)((float)f->size * scale_value)) - 1; |
| if (new_size < 1) |
| new_size = 1; |
| const char* file = f->key->path; |
| int dpi = f->dpi; |
| return gr_ttf_loadFont(file, new_size, dpi); |
| } |
| |
| static bool gr_ttf_freeFontCache(void *key, void *value, void *context __unused) |
| { |
| TrueTypeCacheEntry *e = (TrueTypeCacheEntry *)value; |
| FT_Done_Glyph((FT_Glyph)e->glyph); |
| free(e); |
| free(key); |
| return true; |
| } |
| |
| static bool gr_ttf_freeStringCache(void *key, void *value, void *context __unused) |
| { |
| StringCacheKey *k = (StringCacheKey *)key; |
| free(k->text); |
| free(k); |
| |
| StringCacheEntry *e = (StringCacheEntry *)value; |
| free(e->surface.data); |
| free(e); |
| return true; |
| } |
| |
| void gr_ttf_freeFont(void *font) |
| { |
| pthread_mutex_lock(&font_data.mutex); |
| |
| TrueTypeFont *d = (TrueTypeFont *)font; |
| |
| if(--d->refcount == 0) |
| { |
| hashmapRemove(font_data.fonts, d->key); |
| |
| if(hashmapSize(font_data.fonts) == 0) |
| { |
| hashmapFree(font_data.fonts); |
| font_data.fonts = NULL; |
| } |
| |
| free(d->key->path); |
| free(d->key); |
| |
| FT_Done_Face(d->face); |
| hashmapForEach(d->string_cache, gr_ttf_freeStringCache, NULL); |
| hashmapFree(d->string_cache); |
| hashmapForEach(d->glyph_cache, gr_ttf_freeFontCache, NULL); |
| hashmapFree(d->glyph_cache); |
| pthread_mutex_destroy(&d->mutex); |
| free(d); |
| } |
| |
| pthread_mutex_unlock(&font_data.mutex); |
| } |
| |
| static TrueTypeCacheEntry *gr_ttf_glyph_cache_peek(TrueTypeFont *font, int char_index) |
| { |
| return (TrueTypeCacheEntry *)hashmapGet(font->glyph_cache, &char_index); |
| } |
| |
| static TrueTypeCacheEntry *gr_ttf_glyph_cache_get(TrueTypeFont *font, int char_index) |
| { |
| TrueTypeCacheEntry *res = (TrueTypeCacheEntry *)hashmapGet(font->glyph_cache, &char_index); |
| if(!res) |
| { |
| int error = FT_Load_Glyph(font->face, char_index, FT_LOAD_RENDER); |
| if(error) |
| { |
| fprintf(stderr, "Failed to load glyph idx %d: %d\n", char_index, error); |
| return NULL; |
| } |
| |
| FT_BitmapGlyph glyph; |
| error = FT_Get_Glyph(font->face->glyph, (FT_Glyph*)&glyph); |
| if(error) |
| { |
| fprintf(stderr, "Failed to copy glyph %d: %d\n", char_index, error); |
| return NULL; |
| } |
| |
| res = (TrueTypeCacheEntry *)malloc(sizeof(TrueTypeCacheEntry)); |
| memset(res, 0, sizeof(TrueTypeCacheEntry)); |
| res->glyph = glyph; |
| FT_Glyph_Get_CBox((FT_Glyph)glyph, FT_GLYPH_BBOX_PIXELS, &res->bbox); |
| |
| int *key = (int *)malloc(sizeof(int)); |
| *key = char_index; |
| |
| hashmapPut(font->glyph_cache, key, res); |
| } |
| |
| return res; |
| } |
| |
| static int gr_ttf_copy_glyph_to_surface(GGLSurface *dest, FT_BitmapGlyph glyph, int offX, int offY, int base) |
| { |
| unsigned y; |
| uint8_t *src_itr = glyph->bitmap.buffer; |
| uint8_t *dest_itr = dest->data; |
| |
| if(glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY) |
| { |
| fprintf(stderr, "Unsupported pixel mode in FT_BitmapGlyph %d\n", glyph->bitmap.pixel_mode); |
| return -1; |
| } |
| |
| dest_itr += (offY + base - glyph->top)*dest->stride + (offX + glyph->left); |
| |
| // FIXME: if glyph->left is negative and everything else is 0 (e.g. letter 'j' in Roboto-Regular), |
| // the result might end up being before the buffer - I'm not sure how to properly handle this. |
| if(dest_itr < dest->data) |
| dest_itr = dest->data; |
| |
| for(y = 0; y < glyph->bitmap.rows; ++y) |
| { |
| memcpy(dest_itr, src_itr, glyph->bitmap.width); |
| src_itr += glyph->bitmap.pitch; |
| dest_itr += dest->stride; |
| } |
| return 0; |
| } |
| |
| static void gr_ttf_calcMaxFontHeight(TrueTypeFont *f) |
| { |
| char c; |
| int char_idx; |
| int error; |
| FT_Glyph glyph; |
| FT_BBox bbox; |
| FT_BBox bbox_glyph; |
| TrueTypeCacheEntry *ent; |
| |
| bbox.yMin = bbox_glyph.yMin = LONG_MAX; |
| bbox.yMax = bbox_glyph.yMax = LONG_MIN; |
| |
| for(c = '!'; c <= '~'; ++c) |
| { |
| char_idx = FT_Get_Char_Index(f->face, c); |
| ent = gr_ttf_glyph_cache_peek(f, char_idx); |
| if(ent) |
| { |
| bbox.yMin = MIN(bbox.yMin, ent->bbox.yMin); |
| bbox.yMax = MAX(bbox.yMax, ent->bbox.yMax); |
| } |
| else |
| { |
| error = FT_Load_Glyph(f->face, char_idx, 0); |
| if(error) |
| continue; |
| |
| error = FT_Get_Glyph(f->face->glyph, &glyph); |
| if(error) |
| continue; |
| |
| FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox_glyph); |
| bbox.yMin = MIN(bbox.yMin, bbox_glyph.yMin); |
| bbox.yMax = MAX(bbox.yMax, bbox_glyph.yMax); |
| |
| FT_Done_Glyph(glyph); |
| } |
| } |
| |
| if(bbox.yMin > bbox.yMax) |
| bbox.yMin = bbox.yMax = 0; |
| |
| f->max_height = bbox.yMax - bbox.yMin; |
| f->base = bbox.yMax; |
| |
| // FIXME: twrp fonts have some padding on top, I'll add it here |
| // Should be fixed in the themes |
| f->max_height += f->size / 4; |
| f->base += f->size / 4; |
| } |
| |
| // returns number of bytes from const char *text rendered to fit max_width, not number of UTF8 characters! |
| static int gr_ttf_render_text(TrueTypeFont *font, GGLSurface *surface, const char *text, int max_width) |
| { |
| TrueTypeFont *f = font; |
| TrueTypeCacheEntry *ent; |
| int bytes_rendered = 0, total_w = 0; |
| int utf_bytes = 0; |
| unsigned int unicode = 0; |
| int i, x, diff, char_idx, prev_idx = 0; |
| int height; |
| FT_Vector delta; |
| uint8_t *data = NULL; |
| const char *text_itr = text; |
| int *char_idxs; |
| int char_idxs_len = 0; |
| |
| char_idxs = (int *)malloc(strlen(text) * sizeof(int)); |
| |
| while(*text_itr) |
| { |
| utf_bytes = utf8_to_unicode(text_itr, &unicode); |
| text_itr += utf_bytes; |
| bytes_rendered += utf_bytes; |
| |
| char_idx = FT_Get_Char_Index(f->face, unicode); |
| char_idxs[char_idxs_len] = char_idx; |
| |
| ent = gr_ttf_glyph_cache_get(f, char_idx); |
| if(ent) |
| { |
| diff = ent->glyph->root.advance.x >> 16; |
| |
| if(FT_HAS_KERNING(f->face) && prev_idx && char_idx) |
| { |
| FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta); |
| diff += delta.x >> 6; |
| } |
| |
| if(max_width != -1 && total_w + diff > max_width) |
| break; |
| |
| total_w += diff; |
| } |
| prev_idx = char_idx; |
| ++char_idxs_len; |
| } |
| |
| if(font->max_height == -1) |
| gr_ttf_calcMaxFontHeight(font); |
| |
| if(font->max_height == -1) |
| { |
| free(char_idxs); |
| return -1; |
| } |
| |
| height = font->max_height; |
| |
| data = (uint8_t *)malloc(total_w*height); |
| memset(data, 0, total_w*height); |
| x = 0; |
| prev_idx = 0; |
| |
| surface->version = sizeof(*surface); |
| surface->width = total_w; |
| surface->height = height; |
| surface->stride = total_w; |
| surface->data = (GGLubyte*)data; |
| surface->format = GGL_PIXEL_FORMAT_A_8; |
| |
| for(i = 0; i < char_idxs_len; ++i) |
| { |
| char_idx = char_idxs[i]; |
| if(FT_HAS_KERNING(f->face) && prev_idx && char_idx) |
| { |
| FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta); |
| x += delta.x >> 6; |
| } |
| |
| ent = gr_ttf_glyph_cache_get(f, char_idx); |
| if(ent) |
| { |
| gr_ttf_copy_glyph_to_surface(surface, ent->glyph, x, 0, font->base); |
| x += ent->glyph->root.advance.x >> 16; |
| } |
| |
| prev_idx = char_idx; |
| } |
| |
| free(char_idxs); |
| return bytes_rendered; |
| } |
| |
| static StringCacheEntry *gr_ttf_string_cache_peek(TrueTypeFont *font, const char *text, int max_width) |
| { |
| StringCacheKey k = { |
| .text = (char*)text, |
| .max_width = max_width |
| }; |
| |
| return (StringCacheEntry *)hashmapGet(font->string_cache, &k); |
| } |
| |
| static StringCacheEntry *gr_ttf_string_cache_get(TrueTypeFont *font, const char *text, int max_width) |
| { |
| StringCacheEntry *res; |
| StringCacheKey k = { |
| .text = (char*)text, |
| .max_width = max_width |
| }; |
| |
| res = (StringCacheEntry *)hashmapGet(font->string_cache, &k); |
| if(!res) |
| { |
| res = (StringCacheEntry *)malloc(sizeof(StringCacheEntry)); |
| memset(res, 0, sizeof(StringCacheEntry)); |
| res->rendered_bytes = gr_ttf_render_text(font, &res->surface, text, max_width); |
| if(res->rendered_bytes < 0) |
| { |
| free(res); |
| return NULL; |
| } |
| |
| StringCacheKey *new_key = (StringCacheKey *)malloc(sizeof(StringCacheKey)); |
| memset(new_key, 0, sizeof(StringCacheKey)); |
| new_key->max_width = max_width; |
| new_key->text = strdup(text); |
| |
| res->key = new_key; |
| |
| if(font->string_cache_tail) |
| { |
| res->prev = font->string_cache_tail; |
| res->prev->next = res; |
| } |
| else |
| font->string_cache_head = res; |
| font->string_cache_tail = res; |
| |
| hashmapPut(font->string_cache, new_key, res); |
| } |
| else if(res->next) |
| { |
| // move this entry to the tail of the linked list |
| // if it isn't already there |
| if(res->prev) |
| res->prev->next = res->next; |
| |
| res->next->prev = res->prev; |
| |
| if(!res->prev) |
| font->string_cache_head = res->next; |
| |
| res->next = NULL; |
| res->prev = font->string_cache_tail; |
| res->prev->next = res; |
| font->string_cache_tail = res; |
| |
| // truncate old entries |
| if(hashmapSize(font->string_cache) >= STRING_CACHE_MAX_ENTRIES) |
| { |
| printf("Truncating string cache entries.\n"); |
| int i; |
| StringCacheEntry *ent; |
| for(i = 0; i < STRING_CACHE_TRUNCATE_ENTRIES; ++i) |
| { |
| ent = font->string_cache_head; |
| font->string_cache_head = ent->next; |
| font->string_cache_head->prev = NULL; |
| |
| hashmapRemove(font->string_cache, ent->key); |
| |
| gr_ttf_freeStringCache(ent->key, ent, NULL); |
| } |
| } |
| } |
| return res; |
| } |
| |
| int gr_ttf_measureEx(const char *s, void *font) |
| { |
| TrueTypeFont *f = (TrueTypeFont *)font; |
| int res = -1; |
| |
| pthread_mutex_lock(&f->mutex); |
| StringCacheEntry *e = gr_ttf_string_cache_get(f, s, -1); |
| if(e) |
| res = e->surface.width; |
| pthread_mutex_unlock(&f->mutex); |
| |
| return res; |
| } |
| |
| int gr_ttf_maxExW(const char *s, void *font, int max_width) |
| { |
| TrueTypeFont *f = (TrueTypeFont *)font; |
| TrueTypeCacheEntry *ent; |
| int max_bytes = 0, total_w = 0; |
| int utf_bytes, prev_utf_bytes = 0; |
| unsigned int unicode = 0; |
| int char_idx, prev_idx = 0; |
| FT_Vector delta; |
| StringCacheEntry *e; |
| |
| pthread_mutex_lock(&f->mutex); |
| |
| e = gr_ttf_string_cache_peek(f, s, max_width); |
| if(e) |
| { |
| max_bytes = e->rendered_bytes; |
| pthread_mutex_unlock(&f->mutex); |
| return max_bytes; |
| } |
| |
| while(*s) |
| { |
| utf_bytes = utf8_to_unicode(s, &unicode); |
| s += utf_bytes; |
| |
| char_idx = FT_Get_Char_Index(f->face, unicode); |
| if(FT_HAS_KERNING(f->face) && prev_idx && char_idx) |
| { |
| FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta); |
| total_w += delta.x >> 6; |
| } |
| prev_idx = char_idx; |
| |
| if(total_w > max_width) |
| { |
| max_bytes -= prev_utf_bytes; |
| break; |
| } |
| prev_utf_bytes = utf_bytes; |
| |
| ent = gr_ttf_glyph_cache_get(f, char_idx); |
| if(!ent) |
| continue; |
| |
| total_w += ent->glyph->root.advance.x >> 16; |
| max_bytes += utf_bytes; |
| } |
| pthread_mutex_unlock(&f->mutex); |
| return max_bytes; |
| } |
| |
| int gr_ttf_textExWH(void *context, int x, int y, |
| const char *s, void *pFont, |
| int max_width, int max_height, |
| const gr_surface gr_draw_surface) |
| { |
| GGLContext *gl = (GGLContext *)context; |
| TrueTypeFont *font = (TrueTypeFont *)pFont; |
| const GRSurface *gr_draw = (const GRSurface*) gr_draw_surface; |
| |
| // not actualy max width, but max_width + x |
| if(max_width != -1) |
| { |
| max_width -= x; |
| if(max_width <= 0) |
| return 0; |
| } |
| |
| pthread_mutex_lock(&font->mutex); |
| |
| StringCacheEntry *e = gr_ttf_string_cache_get(font, s, max_width); |
| if(!e) |
| { |
| pthread_mutex_unlock(&font->mutex); |
| return -1; |
| } |
| |
| #if TW_ROTATION != 0 |
| // Do not perform relatively expensive operation if not needed |
| GGLSurface string_surface_rotated; |
| string_surface_rotated.version = sizeof(string_surface_rotated); |
| // Skip the **(TW_ROTATION == 0)** || (TW_ROTATION == 180) check |
| // because we are under a TW_ROTATION != 0 conditional compilation statement |
| string_surface_rotated.width = (TW_ROTATION == 180) ? e->surface.width : e->surface.height; |
| string_surface_rotated.height = (TW_ROTATION == 180) ? e->surface.height : e->surface.width; |
| string_surface_rotated.stride = string_surface_rotated.width; |
| string_surface_rotated.format = e->surface.format; |
| // e->surface.format is GGL_PIXEL_FORMAT_A_8 (grayscale) |
| string_surface_rotated.data = (GGLubyte*) malloc(string_surface_rotated.stride * string_surface_rotated.height * 1); |
| surface_ROTATION_transform((gr_surface) &string_surface_rotated, (const gr_surface) &e->surface, 1); |
| #endif |
| |
| int y_bottom = y + e->surface.height; |
| int res = e->rendered_bytes; |
| |
| if(max_height != -1 && max_height < y_bottom) |
| { |
| y_bottom = max_height; |
| if(y_bottom <= y) |
| { |
| pthread_mutex_unlock(&font->mutex); |
| return 0; |
| } |
| } |
| |
| // Figuring out display coordinates works for TW_ROTATION == 0 too, |
| // and isn't as expensive as allocating and rotating another surface, |
| // so we do this anyway. |
| int x0_disp, y0_disp, x1_disp, y1_disp; |
| int l_disp, r_disp, t_disp, b_disp; |
| |
| x0_disp = ROTATION_X_DISP(x, y, gr_draw); |
| y0_disp = ROTATION_Y_DISP(x, y, gr_draw); |
| x1_disp = ROTATION_X_DISP(x + e->surface.width, y_bottom, gr_draw); |
| y1_disp = ROTATION_Y_DISP(x + e->surface.width, y_bottom, gr_draw); |
| l_disp = std::min(x0_disp, x1_disp); |
| r_disp = std::max(x0_disp, x1_disp); |
| t_disp = std::min(y0_disp, y1_disp); |
| b_disp = std::max(y0_disp, y1_disp); |
| |
| #if TW_ROTATION != 0 |
| gl->bindTexture(gl, &string_surface_rotated); |
| #else |
| gl->bindTexture(gl, &e->surface); |
| #endif |
| gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE); |
| gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); |
| gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); |
| |
| gl->enable(gl, GGL_TEXTURE_2D); |
| gl->texCoord2i(gl, -l_disp, -t_disp); |
| gl->recti(gl, l_disp, t_disp, r_disp, b_disp); |
| gl->disable(gl, GGL_TEXTURE_2D); |
| |
| #if TW_ROTATION != 0 |
| free(string_surface_rotated.data); |
| #endif |
| |
| pthread_mutex_unlock(&font->mutex); |
| return res; |
| } |
| |
| int gr_ttf_getMaxFontHeight(void *font) |
| { |
| int res; |
| TrueTypeFont *f = (TrueTypeFont *)font; |
| |
| pthread_mutex_lock(&f->mutex); |
| |
| if(f->max_height == -1) |
| gr_ttf_calcMaxFontHeight(f); |
| res = f->max_height; |
| |
| pthread_mutex_unlock(&f->mutex); |
| return res; |
| } |
| |
| static bool gr_ttf_dump_stats_count_string_cache(void *key __unused, void *value, void *context) |
| { |
| int *string_cache_size = (int *)context; |
| StringCacheEntry *e = (StringCacheEntry *)value; |
| *string_cache_size += e->surface.height*e->surface.width + sizeof(StringCacheEntry); |
| return true; |
| } |
| |
| static bool gr_ttf_dump_stats_font(void *key, void *value, void *context) |
| { |
| TrueTypeFontKey *k = (TrueTypeFontKey *)key; |
| TrueTypeFont *f = (TrueTypeFont *)value; |
| int *total_string_cache_size = (int *)context; |
| int string_cache_size = 0; |
| |
| pthread_mutex_lock(&f->mutex); |
| |
| hashmapForEach(f->string_cache, gr_ttf_dump_stats_count_string_cache, &string_cache_size); |
| |
| printf(" Font %s (size %d, dpi %d):\n" |
| " refcount: %d\n" |
| " max_height: %d\n" |
| " base: %d\n" |
| " glyph_cache: %zu entries\n" |
| " string_cache: %zu entries (%.2f kB)\n", |
| k->path, k->size, k->dpi, |
| f->refcount, f->max_height, f->base, |
| hashmapSize(f->glyph_cache), |
| hashmapSize(f->string_cache), ((double)string_cache_size)/1024); |
| |
| pthread_mutex_unlock(&f->mutex); |
| |
| *total_string_cache_size += string_cache_size; |
| return true; |
| } |
| |
| void gr_ttf_dump_stats(void) |
| { |
| pthread_mutex_lock(&font_data.mutex); |
| |
| printf("TrueType fonts system stats: "); |
| if(!font_data.fonts) |
| printf("no truetype fonts loaded.\n"); |
| else |
| { |
| int total_string_cache_size = 0; |
| printf("%zu fonts loaded.\n", hashmapSize(font_data.fonts)); |
| hashmapForEach(font_data.fonts, gr_ttf_dump_stats_font, &total_string_cache_size); |
| printf(" Total string cache size: %.2f kB\n", ((double)total_string_cache_size)/1024); |
| } |
| |
| pthread_mutex_unlock(&font_data.mutex); |
| } |