summaryrefslogtreecommitdiffstats
path: root/minuitwrp
diff options
context:
space:
mode:
Diffstat (limited to 'minuitwrp')
-rw-r--r--minuitwrp/Android.mk12
-rw-r--r--minuitwrp/graphics.c101
-rw-r--r--minuitwrp/minui.h21
-rw-r--r--minuitwrp/truetype.c731
4 files changed, 801 insertions, 64 deletions
diff --git a/minuitwrp/Android.mk b/minuitwrp/Android.mk
index ba81f2723..bc4e054b3 100644
--- a/minuitwrp/Android.mk
+++ b/minuitwrp/Android.mk
@@ -107,8 +107,16 @@ ifneq ($(TW_WHITELIST_INPUT),)
LOCAL_CFLAGS += -DWHITELIST_INPUT=$(TW_WHITELIST_INPUT)
endif
-LOCAL_SHARED_LIBRARIES += libz libc libcutils libjpeg
-LOCAL_STATIC_LIBRARIES += libpng libpixelflinger_static
+ifeq ($(TW_DISABLE_TTF), true)
+ LOCAL_CFLAGS += -DTW_DISABLE_TTF
+else
+ LOCAL_SHARED_LIBRARIES += libft2
+ LOCAL_C_INCLUDES += external/freetype/include
+ LOCAL_SRC_FILES += truetype.c
+endif
+
+LOCAL_SHARED_LIBRARIES += libz libc libcutils libjpeg libpng
+LOCAL_STATIC_LIBRARIES += libpixelflinger_static
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE := libminuitwrp
diff --git a/minuitwrp/graphics.c b/minuitwrp/graphics.c
index 79b1e9aa5..9926904ef 100644
--- a/minuitwrp/graphics.c
+++ b/minuitwrp/graphics.c
@@ -58,6 +58,7 @@
// #define PRINT_SCREENINFO 1 // Enables printing of screen info to log
typedef struct {
+ int type;
GGLSurface texture;
unsigned offset[97];
unsigned cheight;
@@ -392,6 +393,11 @@ int gr_measureEx(const char *s, void* font)
if (!fnt) fnt = gr_font;
+#ifndef TW_DISABLE_TTF
+ if(fnt->type == FONT_TYPE_TTF)
+ return gr_ttf_measureEx(s, font);
+#endif
+
while ((off = *s++))
{
off -= 32;
@@ -410,6 +416,11 @@ int gr_maxExW(const char *s, void* font, int max_width)
if (!fnt) fnt = gr_font;
+#ifndef TW_DISABLE_TTF
+ if(fnt->type == FONT_TYPE_TTF)
+ return gr_ttf_maxExW(s, font, max_width);
+#endif
+
while ((off = *s++))
{
off -= 32;
@@ -425,21 +436,6 @@ int gr_maxExW(const char *s, void* font, int max_width)
return total;
}
-unsigned character_width(const char *s, void* pFont)
-{
- GRFont *font = (GRFont*) pFont;
- unsigned off;
-
- /* Handle default font */
- if (!font) font = gr_font;
-
- off = *s - 32;
- if (off == 0)
- return 0;
-
- return font->offset[off+1] - font->offset[off];
-}
-
int gr_textEx(int x, int y, const char *s, void* pFont)
{
GGLContext *gl = gr_context;
@@ -450,6 +446,11 @@ int gr_textEx(int x, int y, const char *s, void* pFont)
/* Handle default font */
if (!font) font = gr_font;
+#ifndef TW_DISABLE_TTF
+ if(font->type == FONT_TYPE_TTF)
+ return gr_ttf_textExWH(gl, x, y, s, pFont, -1, -1);
+#endif
+
gl->bindTexture(gl, &font->texture);
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);
@@ -480,6 +481,11 @@ int gr_textExW(int x, int y, const char *s, void* pFont, int max_width)
/* Handle default font */
if (!font) font = gr_font;
+#ifndef TW_DISABLE_TTF
+ if(font->type == FONT_TYPE_TTF)
+ return gr_ttf_textExWH(gl, x, y, s, pFont, max_width, -1);
+#endif
+
gl->bindTexture(gl, &font->texture);
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);
@@ -518,6 +524,11 @@ int gr_textExWH(int x, int y, const char *s, void* pFont, int max_width, int max
/* Handle default font */
if (!font) font = gr_font;
+#ifndef TW_DISABLE_TTF
+ if(font->type == FONT_TYPE_TTF)
+ return gr_ttf_textExWH(gl, x, y, s, pFont, max_width, max_height);
+#endif
+
gl->bindTexture(gl, &font->texture);
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);
@@ -549,34 +560,6 @@ int gr_textExWH(int x, int y, const char *s, void* pFont, int max_width, int max
return x;
}
-int twgr_text(int x, int y, const char *s)
-{
- GGLContext *gl = gr_context;
- GRFont *font = gr_font;
- unsigned off;
- unsigned cwidth = 0;
-
- y -= font->ascent;
-
- gl->bindTexture(gl, &font->texture);
- 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);
-
- while((off = *s++)) {
- off -= 32;
- if (off < 96) {
- cwidth = font->offset[off+1] - font->offset[off];
- gl->texCoord2i(gl, (off * cwidth) - x, 0 - y);
- gl->recti(gl, x, y, x + cwidth, y + font->cheight);
- }
- x += cwidth;
- }
-
- return x;
-}
-
void gr_fill(int x, int y, int w, int h)
{
GGLContext *gl = gr_context;
@@ -682,33 +665,32 @@ void* gr_loadFont(const char* fontName)
ftex->stride = width;
ftex->data = (void*) bits;
ftex->format = GGL_PIXEL_FORMAT_A_8;
+ font->type = FONT_TYPE_TWRP;
font->cheight = height;
font->ascent = height - 2;
return (void*) font;
}
-int gr_getFontDetails(void* font, unsigned* cheight, unsigned* maxwidth)
+void gr_freeFont(void *font)
+{
+ GRFont *f = font;
+ free(f->texture.data);
+ free(f);
+}
+
+int gr_getMaxFontHeight(void *font)
{
GRFont *fnt = (GRFont*) font;
if (!fnt) fnt = gr_font;
if (!fnt) return -1;
- if (cheight) *cheight = fnt->cheight;
- if (maxwidth)
- {
- int pos;
- *maxwidth = 0;
- for (pos = 0; pos < 96; pos++)
- {
- unsigned int width = fnt->offset[pos+1] - fnt->offset[pos];
- if (width > *maxwidth)
- {
- *maxwidth = width;
- }
- }
- }
- return 0;
+#ifndef TW_DISABLE_TTF
+ if(fnt->type == FONT_TYPE_TTF)
+ return gr_ttf_getMaxFontHeight(font);
+#endif
+
+ return fnt->cheight;
}
static void gr_init_font(void)
@@ -746,6 +728,7 @@ static void gr_init_font(void)
ftex->stride = width;
ftex->data = (void*) bits;
ftex->format = GGL_PIXEL_FORMAT_A_8;
+ gr_font->type = FONT_TYPE_TWRP;
gr_font->cheight = height;
gr_font->ascent = height - 2;
return;
diff --git a/minuitwrp/minui.h b/minuitwrp/minui.h
index f04f518b5..cb9f8a385 100644
--- a/minuitwrp/minui.h
+++ b/minuitwrp/minui.h
@@ -20,6 +20,12 @@
typedef void* gr_surface;
typedef unsigned short gr_pixel;
+#define FONT_TYPE_TWRP 0
+
+#ifndef TW_DISABLE_TTF
+#define FONT_TYPE_TTF 1
+#endif
+
int gr_init(void);
void gr_exit(void);
@@ -35,16 +41,25 @@ void gr_fill(int x, int y, int w, int h);
int gr_textEx(int x, int y, const char *s, void* font);
int gr_textExW(int x, int y, const char *s, void* font, int max_width);
int gr_textExWH(int x, int y, const char *s, void* pFont, int max_width, int max_height);
-int twgr_text(int x, int y, const char *s);
static inline int gr_text(int x, int y, const char *s) { return gr_textEx(x, y, s, NULL); }
int gr_measureEx(const char *s, void* font);
static inline int gr_measure(const char *s) { return gr_measureEx(s, NULL); }
int gr_maxExW(const char *s, void* font, int max_width);
-int gr_getFontDetails(void* font, unsigned* cheight, unsigned* maxwidth);
-static inline void gr_font_size(int *x, int *y) { gr_getFontDetails(NULL, (unsigned*) y, (unsigned*) x); }
+int gr_getMaxFontHeight(void *font);
void* gr_loadFont(const char* fontName);
+void gr_freeFont(void *font);
+
+#ifndef TW_DISABLE_TTF
+void *gr_ttf_loadFont(const char *filename, int size, int dpi);
+void gr_ttf_freeFont(void *font);
+int gr_ttf_textExWH(void *context, int x, int y, const char *s, void *pFont, int max_width, int max_height);
+int gr_ttf_measureEx(const char *s, void *font);
+int gr_ttf_maxExW(const char *s, void *font, int max_width);
+int gr_ttf_getMaxFontHeight(void *font);
+void gr_ttf_dump_stats(void);
+#endif
void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy);
unsigned int gr_get_width(gr_surface surface);
diff --git a/minuitwrp/truetype.c b/minuitwrp/truetype.c
new file mode 100644
index 000000000..8e0df42ea
--- /dev/null
+++ b/minuitwrp/truetype.c
@@ -0,0 +1,731 @@
+#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>
+
+#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_len;
+ 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 = data;
+ uint32_t *d32 = 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;
+}
+
+static bool gr_ttf_string_cache_equals(void *keyA, void *keyB)
+{
+ StringCacheKey *a = keyA;
+ StringCacheKey *b = 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 = key;
+ return fnv_hash(k->text, strlen(k->text));
+}
+
+static bool gr_ttf_font_cache_equals(void *keyA, void *keyB)
+{
+ TrueTypeFontKey *a = keyA;
+ TrueTypeFontKey *b = 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 = 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;
+
+ pthread_mutex_lock(&font_data.mutex);
+
+ if(font_data.fonts)
+ {
+ TrueTypeFontKey k = {
+ .size = size,
+ .dpi = dpi,
+ .path = (char*)filename
+ };
+
+ res = 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 = 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);
+
+ TrueTypeFontKey *key = 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;
+}
+
+static bool gr_ttf_freeFontCache(void *key, void *value, void *context)
+{
+ TrueTypeCacheEntry *e = 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)
+{
+ StringCacheKey *k = key;
+ free(k->text);
+ free(k);
+
+ StringCacheEntry *e = value;
+ free(e->surface.data);
+ free(e);
+ return true;
+}
+
+void gr_ttf_freeFont(void *font)
+{
+ pthread_mutex_lock(&font_data.mutex);
+
+ TrueTypeFont *d = 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 hashmapGet(font->glyph_cache, &char_index);
+}
+
+static TrueTypeCacheEntry *gr_ttf_glyph_cache_get(TrueTypeFont *font, int char_index)
+{
+ TrueTypeCacheEntry *res = 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 = 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 = 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)
+{
+ int 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);
+
+ 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 int gr_ttf_render_text(TrueTypeFont *font, GGLSurface *surface, const char *text, int max_width)
+{
+ TrueTypeFont *f = font;
+ TrueTypeCacheEntry *ent;
+ int max_len = 0, total_w = 0;
+ char c;
+ int i, x, diff, char_idx, prev_idx = 0;
+ int height, base;
+ FT_Vector delta;
+ uint8_t *data = NULL;
+ const char *text_itr = text;
+
+ while((c = *text_itr++))
+ {
+ char_idx = FT_Get_Char_Index(f->face, c);
+ 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;
+ ++max_len;
+ }
+
+ if(font->max_height == -1)
+ gr_ttf_getMaxFontHeight(font);
+
+ if(font->max_height == -1)
+ return -1;
+
+ height = font->max_height;
+
+ data = 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 = (void*)data;
+ surface->format = GGL_PIXEL_FORMAT_A_8;
+
+ for(i = 0; i < max_len; ++i)
+ {
+ char_idx = FT_Get_Char_Index(f->face, text[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;
+ }
+
+ return max_len;
+}
+
+static StringCacheEntry *gr_ttf_string_cache_peek(TrueTypeFont *font, const char *text, int max_width)
+{
+ StringCacheEntry *res;
+ StringCacheKey k = {
+ .text = (char*)text,
+ .max_width = max_width
+ };
+
+ return 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 = hashmapGet(font->string_cache, &k);
+ if(!res)
+ {
+ res = malloc(sizeof(StringCacheEntry));
+ memset(res, 0, sizeof(StringCacheEntry));
+ res->rendered_len = gr_ttf_render_text(font, &res->surface, text, max_width);
+ if(res->rendered_len < 0)
+ {
+ free(res);
+ return NULL;
+ }
+
+ StringCacheKey *new_key = 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 = font;
+ int res = -1;
+
+ pthread_mutex_lock(&f->mutex);
+ StringCacheEntry *e = gr_ttf_string_cache_get(font, 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 = font;
+ TrueTypeCacheEntry *ent;
+ int max_len = 0, total_w = 0;
+ char c;
+ int char_idx, prev_idx = 0;
+ FT_Vector delta;
+ StringCacheEntry *e;
+
+ pthread_mutex_lock(&f->mutex);
+
+ e = gr_ttf_string_cache_peek(font, s, max_width);
+ if(e)
+ {
+ max_len = e->rendered_len;
+ pthread_mutex_unlock(&f->mutex);
+ return max_len;
+ }
+
+ for(; (c = *s++); ++max_len)
+ {
+ char_idx = FT_Get_Char_Index(f->face, c);
+ 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)
+ break;
+
+ ent = gr_ttf_glyph_cache_get(f, char_idx);
+ if(!ent)
+ continue;
+
+ total_w += ent->glyph->root.advance.x >> 16;
+ }
+ pthread_mutex_unlock(&f->mutex);
+ return max_len > 0 ? max_len - 1 : 0;
+}
+
+int gr_ttf_textExWH(void *context, int x, int y, const char *s, void *pFont, int max_width, int max_height)
+{
+ GGLContext *gl = context;
+ TrueTypeFont *font = pFont;
+
+ // 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;
+ }
+
+ int y_bottom = y + e->surface.height;
+ int res = e->rendered_len;
+
+ if(max_height != -1 && max_height < y_bottom)
+ {
+ y_bottom = max_height;
+ if(y_bottom <= y)
+ {
+ pthread_mutex_unlock(&font->mutex);
+ return 0;
+ }
+ }
+
+ gl->bindTexture(gl, &e->surface);
+ 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, -x, -y);
+ gl->recti(gl, x, y, x + e->surface.width, y_bottom);
+
+ pthread_mutex_unlock(&font->mutex);
+ return res;
+}
+
+int gr_ttf_getMaxFontHeight(void *font)
+{
+ int res;
+ TrueTypeFont *f = font;
+
+ pthread_mutex_lock(&f->mutex);
+
+ if(f->max_height == -1)
+ {
+ 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;
+ }
+
+ res = f->max_height;
+
+ pthread_mutex_unlock(&f->mutex);
+ return res;
+}
+
+static bool gr_ttf_dump_stats_count_string_cache(void *key, void *value, void *context)
+{
+ int *string_cache_size = context;
+ StringCacheEntry *e = 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 = key;
+ TrueTypeFont *f = value;
+ int *total_string_cache_size = 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: %d entries\n"
+ " string_cache: %d 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("%d 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);
+}