/** * @file lv_tiny_ttf.c * */ /********************* * INCLUDES *********************/ #include "../../lvgl.h" #if LV_USE_TINY_TTF != 0 #include "../../core/lv_global.h" #define font_draw_buf_handlers &(LV_GLOBAL_DEFAULT()->font_draw_buf_handlers) /********************* * DEFINES *********************/ #define STB_RECT_PACK_IMPLEMENTATION #define STBRP_STATIC #define STBTT_STATIC #define STB_TRUETYPE_IMPLEMENTATION #define STBTT_HEAP_FACTOR_SIZE_32 50 #define STBTT_HEAP_FACTOR_SIZE_128 20 #define STBTT_HEAP_FACTOR_SIZE_DEFAULT 10 #define STBTT_malloc(x, u) ((void)(u), lv_malloc(x)) #define STBTT_free(x, u) ((void)(u), lv_free(x)) #if LV_TINY_TTF_FILE_SUPPORT != 0 /* for stream support */ #define STBTT_STREAM_TYPE ttf_cb_stream_t * #define STBTT_STREAM_SEEK(s, x) ttf_cb_stream_seek(s, x); #define STBTT_STREAM_READ(s, x, y) ttf_cb_stream_read(s, x, y); /* a hydra stream that can be in memory or from a file*/ typedef struct ttf_cb_stream { lv_fs_file_t * file; const void * data; size_t size; size_t position; } ttf_cb_stream_t; static void ttf_cb_stream_read(ttf_cb_stream_t * stream, void * data, size_t to_read); static void ttf_cb_stream_seek(ttf_cb_stream_t * stream, size_t position); #endif #include "stb_rect_pack.h" #include "stb_truetype_htcw.h" /********************** * TYPEDEFS **********************/ typedef struct ttf_font_desc { lv_fs_file_t file; #if LV_TINY_TTF_FILE_SUPPORT != 0 ttf_cb_stream_t stream; #else const uint8_t * stream; #endif stbtt_fontinfo info; float scale; int ascent; int descent; lv_font_kerning_t kerning; int cache_size; lv_cache_t * glyph_cache; lv_cache_t * draw_data_cache; } ttf_font_desc_t; typedef struct _tiny_ttf_glyph_cache_data_t { uint32_t unicode; int adv_w; lv_font_glyph_dsc_t glyph_dsc; } tiny_ttf_glyph_cache_data_t; typedef struct _lv_tiny_ttf_cache_data_t { uint32_t glyph_index; uint32_t size; lv_draw_buf_t * draw_buf; } tiny_ttf_cache_data_t; /********************** * STATIC PROTOTYPES **********************/ static bool ttf_get_glyph_dsc_cb(const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, uint32_t unicode_letter, uint32_t unicode_letter_next); static const void * ttf_get_glyph_bitmap_cb(lv_font_glyph_dsc_t * g_dsc, lv_draw_buf_t * draw_buf); static void ttf_release_glyph_cb(const lv_font_t * font, lv_font_glyph_dsc_t * g_dsc); static lv_font_t * lv_tiny_ttf_create(const char * path, const void * data, size_t data_size, int32_t font_size, lv_font_kerning_t kerning, size_t cache_size); static bool tiny_ttf_glyph_cache_create_cb(tiny_ttf_glyph_cache_data_t * node, void * user_data); static void tiny_ttf_glyph_cache_free_cb(tiny_ttf_glyph_cache_data_t * node, void * user_data); static lv_cache_compare_res_t tiny_ttf_glyph_cache_compare_cb(const tiny_ttf_glyph_cache_data_t * lhs, const tiny_ttf_glyph_cache_data_t * rhs); static bool tiny_ttf_draw_data_cache_create_cb(tiny_ttf_cache_data_t * node, void * user_data); static void tiny_ttf_draw_data_cache_free_cb(tiny_ttf_cache_data_t * node, void * user_data); static lv_cache_compare_res_t tiny_ttf_draw_data_cache_compare_cb(const tiny_ttf_cache_data_t * lhs, const tiny_ttf_cache_data_t * rhs); static void lv_tiny_ttf_cache_create(ttf_font_desc_t * dsc); /********************** * GLOBAL VARIABLES **********************/ /********************** * STATIC VARIABLES **********************/ /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void lv_tiny_ttf_set_size(lv_font_t * font, int32_t font_size) { if(font_size <= 0) { LV_LOG_ERROR("invalid font size: %"LV_PRIx32, font_size); return; } ttf_font_desc_t * dsc = (ttf_font_desc_t *)font->dsc; dsc->scale = stbtt_ScaleForMappingEmToPixels(&dsc->info, font_size); int line_gap = 0; stbtt_GetFontVMetrics(&dsc->info, &dsc->ascent, &dsc->descent, &line_gap); font->line_height = (int32_t)(dsc->scale * (dsc->ascent - dsc->descent + line_gap)); font->base_line = (int32_t)(dsc->scale * (line_gap - dsc->descent)); /* size change means cache needs to be invalidated. */ if(dsc->glyph_cache) { lv_cache_destroy(dsc->glyph_cache, NULL); dsc->glyph_cache = NULL; } if(dsc->draw_data_cache) { lv_cache_destroy(dsc->draw_data_cache, NULL); dsc->draw_data_cache = NULL; } lv_tiny_ttf_cache_create(dsc); } void lv_tiny_ttf_destroy(lv_font_t * font) { LV_ASSERT_NULL(font); if(font->dsc != NULL) { ttf_font_desc_t * ttf = (ttf_font_desc_t *)font->dsc; #if LV_TINY_TTF_FILE_SUPPORT != 0 if(ttf->stream.file != NULL) { lv_fs_close(&ttf->file); } #endif lv_cache_destroy(ttf->glyph_cache, NULL); lv_cache_destroy(ttf->draw_data_cache, NULL); lv_free(ttf); font->dsc = NULL; } lv_free(font); } /********************** * STATIC FUNCTIONS **********************/ #if LV_TINY_TTF_FILE_SUPPORT != 0 static void ttf_cb_stream_read(ttf_cb_stream_t * stream, void * data, size_t to_read) { if(stream->file != NULL) { uint32_t br; lv_fs_read(stream->file, data, to_read, &br); } else { if(to_read + stream->position >= stream->size) { to_read = stream->size - stream->position; } memcpy(data, ((const unsigned char *)stream->data + stream->position), to_read); stream->position += to_read; } } static void ttf_cb_stream_seek(ttf_cb_stream_t * stream, size_t position) { if(stream->file != NULL) { lv_fs_seek(stream->file, position, LV_FS_SEEK_SET); } else { if(position > stream->size) { stream->position = stream->size; } else { stream->position = position; } } } #endif static bool ttf_get_glyph_dsc_cb(const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, uint32_t unicode_letter, uint32_t unicode_letter_next) { if(unicode_letter < 0x20 || unicode_letter == 0xf8ff || /*LV_SYMBOL_DUMMY*/ unicode_letter == 0x200c) { /*ZERO WIDTH NON-JOINER*/ dsc_out->box_w = 0; dsc_out->adv_w = 0; dsc_out->box_h = 0; /*height of the bitmap in [px]*/ dsc_out->ofs_x = 0; /*X offset of the bitmap in [pf]*/ dsc_out->ofs_y = 0; /*Y offset of the bitmap in [pf]*/ dsc_out->format = LV_FONT_GLYPH_FORMAT_NONE; dsc_out->is_placeholder = false; return true; } ttf_font_desc_t * dsc = (ttf_font_desc_t *)font->dsc; tiny_ttf_glyph_cache_data_t search_key = { .unicode = unicode_letter, }; int adv_w; lv_cache_entry_t * entry = lv_cache_acquire_or_create(dsc->glyph_cache, &search_key, (void *)dsc); if(entry == NULL) { if(!dsc->cache_size) { /* no cache, do everything directly */ uint32_t g1 = stbtt_FindGlyphIndex(&dsc->info, (int)unicode_letter); tiny_ttf_glyph_cache_create_cb(&search_key, dsc); *dsc_out = search_key.glyph_dsc; adv_w = search_key.adv_w; /*Kerning correction*/ if(font->kerning == LV_FONT_KERNING_NORMAL && unicode_letter_next != 0) { int g2 = stbtt_FindGlyphIndex(&dsc->info, (int)unicode_letter_next); /* not using cache, only do glyph id lookup */ if(g2) { int k = stbtt_GetGlyphKernAdvance(&dsc->info, g1, g2); dsc_out->adv_w = (uint16_t)floor((((float)adv_w + (float)k) * dsc->scale) + 0.5f); /*Horizontal space required by the glyph in [px]*/ } } dsc_out->entry = NULL; return true; } LV_LOG_ERROR("cache not allocated"); return false; } tiny_ttf_glyph_cache_data_t * data = lv_cache_entry_get_data(entry); *dsc_out = data->glyph_dsc; adv_w = data->adv_w; lv_cache_release(dsc->glyph_cache, entry, NULL); /*Kerning correction*/ if(font->kerning == LV_FONT_KERNING_NORMAL && unicode_letter_next != 0) { /* check if we need to do any kerning calculations */ uint32_t g1 = dsc_out->gid.index; int g2 = 0; search_key.unicode = unicode_letter_next; /* reuse search key */ lv_cache_entry_t * entry_next = lv_cache_acquire_or_create(dsc->glyph_cache, &search_key, (void *)dsc); if(entry_next == NULL) g2 = stbtt_FindGlyphIndex(&dsc->info, (int)unicode_letter_next); else { tiny_ttf_glyph_cache_data_t * data_next = lv_cache_entry_get_data(entry_next); g2 = data_next->glyph_dsc.gid.index; lv_cache_release(dsc->glyph_cache, entry_next, NULL); } if(g2) { int k = stbtt_GetGlyphKernAdvance(&dsc->info, g1, g2); dsc_out->adv_w = (uint16_t)floor((((float)adv_w + (float)k) * dsc->scale) + 0.5f); /*Horizontal space required by the glyph in [px]*/ } } dsc_out->entry = NULL; return true; /*true: glyph found; false: glyph was not found*/ } static const void * ttf_get_glyph_bitmap_cb(lv_font_glyph_dsc_t * g_dsc, lv_draw_buf_t * draw_buf) { LV_UNUSED(draw_buf); uint32_t glyph_index = g_dsc->gid.index; const lv_font_t * font = g_dsc->resolved_font; ttf_font_desc_t * dsc = (ttf_font_desc_t *)font->dsc; tiny_ttf_cache_data_t search_key = { .glyph_index = glyph_index, .size = font->line_height, }; lv_cache_entry_t * entry = lv_cache_acquire_or_create(dsc->draw_data_cache, &search_key, (void *)font->dsc); if(entry == NULL) { if(!dsc->cache_size) { /* no cache, do everything directly */ if(tiny_ttf_draw_data_cache_create_cb(&search_key, (void *)font->dsc)) { /* use the cache entry to store the buffer if no cache specified */ g_dsc->entry = (lv_cache_entry_t *)search_key.draw_buf; return g_dsc->entry; } else { return NULL; } } LV_LOG_ERROR("cache not allocated"); return NULL; } g_dsc->entry = entry; tiny_ttf_cache_data_t * cached_data = lv_cache_entry_get_data(entry); return cached_data->draw_buf; } static void ttf_release_glyph_cb(const lv_font_t * font, lv_font_glyph_dsc_t * g_dsc) { LV_ASSERT_NULL(font); ttf_font_desc_t * dsc = (ttf_font_desc_t *)font->dsc; if(!dsc->cache_size) { /* no cache, do everything directly */ lv_draw_buf_destroy_user(font_draw_buf_handlers, (lv_draw_buf_t *)g_dsc->entry); } else { if(g_dsc->entry == NULL) { return; } lv_cache_release(dsc->draw_data_cache, g_dsc->entry, NULL); } g_dsc->entry = NULL; } static void lv_tiny_ttf_cache_create(ttf_font_desc_t * dsc) { /*Init cache*/ dsc->glyph_cache = lv_cache_create(&lv_cache_class_lru_rb_count, sizeof(tiny_ttf_glyph_cache_data_t), dsc->cache_size, (lv_cache_ops_t) { .compare_cb = (lv_cache_compare_cb_t)tiny_ttf_glyph_cache_compare_cb, .create_cb = (lv_cache_create_cb_t)tiny_ttf_glyph_cache_create_cb, .free_cb = (lv_cache_free_cb_t)tiny_ttf_glyph_cache_free_cb }); lv_cache_set_name(dsc->glyph_cache, "TINY_TTF_GLYPH"); dsc->draw_data_cache = lv_cache_create(&lv_cache_class_lru_rb_count, sizeof(tiny_ttf_cache_data_t), dsc->cache_size, (lv_cache_ops_t) { .compare_cb = (lv_cache_compare_cb_t)tiny_ttf_draw_data_cache_compare_cb, .create_cb = (lv_cache_create_cb_t)tiny_ttf_draw_data_cache_create_cb, .free_cb = (lv_cache_free_cb_t)tiny_ttf_draw_data_cache_free_cb, }); lv_cache_set_name(dsc->draw_data_cache, "TINY_TTF_DRAW_DATA"); } static lv_font_t * lv_tiny_ttf_create(const char * path, const void * data, size_t data_size, int32_t font_size, lv_font_kerning_t kerning, size_t cache_size) { LV_UNUSED(data_size); if((path == NULL && data == NULL) || 0 >= font_size) { LV_LOG_ERROR("tiny_ttf: invalid argument"); return NULL; } ttf_font_desc_t * dsc = lv_malloc_zeroed(sizeof(ttf_font_desc_t)); if(dsc == NULL) { LV_LOG_ERROR("tiny_ttf: out of memory"); return NULL; } #if LV_TINY_TTF_FILE_SUPPORT != 0 if(path != NULL) { if(LV_FS_RES_OK != lv_fs_open(&dsc->file, path, LV_FS_MODE_RD)) { lv_free(dsc); LV_LOG_ERROR("tiny_ttf: unable to open %s\n", path); return NULL; } dsc->stream.file = &dsc->file; } else { dsc->stream.data = (const uint8_t *)data; dsc->stream.size = data_size; } if(0 == stbtt_InitFont(&dsc->info, &dsc->stream, stbtt_GetFontOffsetForIndex(&dsc->stream, 0))) { lv_free(dsc); LV_LOG_ERROR("tiny_ttf: init failed"); return NULL; } #else dsc->stream = (const uint8_t *)data; if(0 == stbtt_InitFont(&dsc->info, dsc->stream, stbtt_GetFontOffsetForIndex(dsc->stream, 0))) { lv_free(dsc); LV_LOG_ERROR("tiny_ttf: init failed"); return NULL; } #endif dsc->cache_size = cache_size; lv_font_t * out_font = lv_malloc_zeroed(sizeof(lv_font_t)); if(out_font == NULL) { lv_free(dsc); LV_LOG_ERROR("tiny_ttf: out of memory"); return NULL; } /* check if font has kerning tables to use, else disable kerning automatically. */ if(stbtt_KernTableCheck(&dsc->info) == 0) { kerning = LV_FONT_KERNING_NONE; /* disable kerning if font has no tables. */ } dsc->kerning = kerning; out_font->kerning = kerning; out_font->get_glyph_dsc = ttf_get_glyph_dsc_cb; out_font->get_glyph_bitmap = ttf_get_glyph_bitmap_cb; out_font->release_glyph = ttf_release_glyph_cb; out_font->dsc = dsc; lv_tiny_ttf_set_size(out_font, font_size); return out_font; } #if LV_TINY_TTF_FILE_SUPPORT != 0 lv_font_t * lv_tiny_ttf_create_file_ex(const char * path, int32_t font_size, lv_font_kerning_t kerning, size_t cache_size) { return lv_tiny_ttf_create(path, NULL, 0, font_size, kerning, cache_size); } lv_font_t * lv_tiny_ttf_create_file(const char * path, int32_t font_size) { return lv_tiny_ttf_create(path, NULL, 0, font_size, LV_FONT_KERNING_NORMAL, LV_TINY_TTF_CACHE_GLYPH_CNT); } #endif lv_font_t * lv_tiny_ttf_create_data_ex(const void * data, size_t data_size, int32_t font_size, lv_font_kerning_t kerning, size_t cache_size) { return lv_tiny_ttf_create(NULL, data, data_size, font_size, kerning, cache_size); } lv_font_t * lv_tiny_ttf_create_data(const void * data, size_t data_size, int32_t font_size) { return lv_tiny_ttf_create(NULL, data, data_size, font_size, LV_FONT_KERNING_NORMAL, LV_TINY_TTF_CACHE_GLYPH_CNT); } /*----------------- * Cache Callbacks *----------------*/ static bool tiny_ttf_glyph_cache_create_cb(tiny_ttf_glyph_cache_data_t * node, void * user_data) { ttf_font_desc_t * dsc = (ttf_font_desc_t *)user_data; lv_font_glyph_dsc_t * dsc_out = &node->glyph_dsc; uint32_t unicode_letter = node->unicode; int g1 = stbtt_FindGlyphIndex(&dsc->info, (int)unicode_letter); if(g1 == 0) { /* Glyph not found */ return false; } int x1, y1, x2, y2; stbtt_GetGlyphBitmapBox(&dsc->info, g1, dsc->scale, dsc->scale, &x1, &y1, &x2, &y2); int advw, lsb; stbtt_GetGlyphHMetrics(&dsc->info, g1, &advw, &lsb); if(dsc->kerning != LV_FONT_KERNING_NORMAL) { /* calculate default advance */ int k = stbtt_GetGlyphKernAdvance(&dsc->info, g1, 0); dsc_out->adv_w = (uint16_t)floor((((float)advw + (float)k) * dsc->scale) + 0.5f); /*Horizontal space required by the glyph in [px]*/ } else { dsc_out->adv_w = (uint16_t)floor(((float)advw * dsc->scale) + 0.5f); /*Horizontal space required by the glyph in [px]*/; } /* precalculate no kerning value */ node->adv_w = advw; dsc_out->box_w = (x2 - x1 + 1); /*width of the bitmap in [px]*/ dsc_out->box_h = (y2 - y1 + 1); /*height of the bitmap in [px]*/ dsc_out->ofs_x = x1; /*X offset of the bitmap in [pf]*/ dsc_out->ofs_y = -y2; /*Y offset of the bitmap measured from the as line*/ dsc_out->format = LV_FONT_GLYPH_FORMAT_A8; dsc_out->is_placeholder = false; dsc_out->gid.index = (uint32_t)g1; return true; } static void tiny_ttf_glyph_cache_free_cb(tiny_ttf_glyph_cache_data_t * node, void * user_data) { LV_UNUSED(node); LV_UNUSED(user_data); } static lv_cache_compare_res_t tiny_ttf_glyph_cache_compare_cb(const tiny_ttf_glyph_cache_data_t * lhs, const tiny_ttf_glyph_cache_data_t * rhs) { if(lhs->unicode != rhs->unicode) { return lhs->unicode > rhs->unicode ? 1 : -1; } return 0; } static bool tiny_ttf_draw_data_cache_create_cb(tiny_ttf_cache_data_t * node, void * user_data) { int g1 = (int)node->glyph_index; if(g1 == 0) { /* Glyph not found */ return false; } ttf_font_desc_t * dsc = (ttf_font_desc_t *)user_data; const stbtt_fontinfo * info = (const stbtt_fontinfo *)&dsc->info; int x1, y1, x2, y2; stbtt_GetGlyphBitmapBox(info, g1, dsc->scale, dsc->scale, &x1, &y1, &x2, &y2); int w, h; w = x2 - x1 + 1; h = y2 - y1 + 1; lv_draw_buf_t * draw_buf = lv_draw_buf_create_ex(font_draw_buf_handlers, w, h, LV_COLOR_FORMAT_A8, LV_STRIDE_AUTO); if(NULL == draw_buf) { LV_LOG_ERROR("tiny_ttf: out of memory"); return false; } lv_draw_buf_clear(draw_buf, NULL); uint32_t stride = draw_buf->header.stride; stbtt_MakeGlyphBitmap(info, draw_buf->data, w, h, stride, dsc->scale, dsc->scale, g1); node->draw_buf = draw_buf; return true; } static void tiny_ttf_draw_data_cache_free_cb(tiny_ttf_cache_data_t * node, void * user_data) { LV_UNUSED(user_data); lv_draw_buf_destroy(node->draw_buf); } static lv_cache_compare_res_t tiny_ttf_draw_data_cache_compare_cb(const tiny_ttf_cache_data_t * lhs, const tiny_ttf_cache_data_t * rhs) { if(lhs->glyph_index != rhs->glyph_index) { return lhs->glyph_index > rhs->glyph_index ? 1 : -1; } if(lhs->size != rhs->size) { return lhs->size > rhs->size ? 1 : -1; } return 0; } #endif