diff --git a/docs/libs/index.md b/docs/libs/index.md index 8217e9121..f0eb9003a 100644 --- a/docs/libs/index.md +++ b/docs/libs/index.md @@ -12,6 +12,7 @@ png gif freetype + tiny_ttf qrcode rlottie ffmpeg diff --git a/docs/libs/tiny_ttf.md b/docs/libs/tiny_ttf.md index 1c36167d1..de31f4e10 100644 --- a/docs/libs/tiny_ttf.md +++ b/docs/libs/tiny_ttf.md @@ -12,6 +12,7 @@ However, if `LV_TINY_TTF_FILE_SUPPORT` is enabled, `lv_tiny_ttf_create_file(path After a font is created, you can change the size by using `lv_tiny_ttf_set_size(font, line_height)`. +By default, a font will use up to 4KB of cache to speed up rendering glyphs. This maximum can be changed by using `lv_tiny_ttf_create_data_ex(data, data_size, line_height, cache_size)` or `lv_tiny_ttf_create_file_ex(path, line_height, cache_size)` (when available). The cache size is indicated in bytes. ## Example ```eval_rst diff --git a/src/libs/tiny_ttf/lv_tiny_ttf.c b/src/libs/tiny_ttf/lv_tiny_ttf.c index af9facbce..02bf3ca6e 100644 --- a/src/libs/tiny_ttf/lv_tiny_ttf.c +++ b/src/libs/tiny_ttf/lv_tiny_ttf.c @@ -1,17 +1,220 @@ -#include "../../../lvgl.h" -#include "../../misc/lv_gc.h" +#include "lv_tiny_ttf.h" #if LV_USE_TINY_TTF #include -#include "lv_tiny_ttf.h" + +#ifndef LV_TINY_TTF_DEFAULT_CACHE_SIZE + #define LV_TINY_TTF_DEFAULT_CACHE_SIZE 4096 +#endif +#ifndef LV_TINY_TTF_CACHE_BUCKETS + #define LV_TINY_TTF_CACHE_BUCKETS 16 +#endif + #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)) +#define TTF_CACHE_MALLOC(x) (lv_malloc(x)) +#define TTF_CACHE_REALLOC(x,y) (lv_realloc(x,y)) +#define TTF_CACHE_FREE(x) (lv_free(x)) +#define TTF_MALLOC(x) (lv_malloc(x)) +#define TTF_FREE(x) (lv_free(x)) +typedef void * ttf_cache_handle_t; +typedef struct ttf_cache_entry { + int key; + unsigned long long age; + int size; + void * data; +} ttf_cache_entry_t; +typedef struct ttf_cache_bucket { + int capacity; + ttf_cache_entry_t * entries; +} ttf_cache_bucket_t; +typedef struct ttf_cache { + int max_size; + int bucket_size; + int total_size; + unsigned long long age; + ttf_cache_bucket_t * buckets; +} ttf_cache_t; +static unsigned long long ttf_cache_get_oldest_age(ttf_cache_handle_t * handle) +{ + ttf_cache_t * cache = (ttf_cache_t *)handle; + unsigned long long result = (unsigned long long) - 1; + for(int i = 0; i < cache->bucket_size; ++i) { + ttf_cache_bucket_t * bucket = &cache->buckets[i]; + if(bucket->entries != NULL) { + for(int j = 0; j < bucket->capacity; ++j) { + ttf_cache_entry_t * entry = &bucket->entries[j]; + if(entry->age != 0 && entry->age < result) { + result = entry->age; + } + } + } + } + if(result == (unsigned long long) - 1) { + return 0; + } + return result; +} +static ttf_cache_handle_t ttf_cache_create(int max_size, int buckets) +{ + ttf_cache_t * result = (ttf_cache_t *)TTF_CACHE_MALLOC(sizeof(ttf_cache_t)); + if(result == NULL) { + return NULL; + } + result->age = 1; + result->max_size = max_size; + result->total_size = 0; + result->bucket_size = buckets; + result->buckets = (ttf_cache_bucket_t *)TTF_CACHE_MALLOC(buckets * sizeof(ttf_cache_bucket_t)); + if(result->buckets == NULL) { + TTF_CACHE_FREE(result); + return NULL; + } + for(int i = 0; i < buckets; ++i) { + result->buckets[i].capacity = 0; + result->buckets[i].entries = NULL; + } + return result; +} +static void * ttf_cache_get(ttf_cache_handle_t handle, int key) +{ + if(handle == NULL) { + return NULL; + } + ttf_cache_t * cache = (ttf_cache_t *)handle; + int ci = key % cache->bucket_size; + ttf_cache_bucket_t * bucket = &cache->buckets[ci]; + for(int i = 0; i < bucket->capacity; ++i) { + ttf_cache_entry_t * entry = &bucket->entries[i]; + if(entry->age != 0 && entry->key == key) { + entry->age = ++cache->age; + return entry->data; + } + } + return NULL; +} +static void * ttf_cache_add(ttf_cache_handle_t handle, int key, int size) +{ + if(handle == NULL) { + return NULL; + } + ttf_cache_t * cache = (ttf_cache_t *)handle; + int ci = key % cache->bucket_size; + ttf_cache_bucket_t * bucket = &cache->buckets[ci]; + for(int i = 0; i < bucket->capacity; ++i) { + ttf_cache_entry_t * entry = &bucket->entries[i]; + if(entry->age != 0 && entry->key == key) { + TTF_CACHE_FREE(entry->data); + cache->total_size -= entry->size; + entry->age = 0; + break; + } + } + while(cache->total_size > 0 && (cache->max_size < cache->total_size + size)) { + // expire entries + unsigned long long oldest = ttf_cache_get_oldest_age(handle); + if(oldest == 0) { + break; + } + for(int i = 0; i < cache->bucket_size; ++i) { + ttf_cache_bucket_t * bucket2 = &cache->buckets[i]; + for(int j = 0; j < bucket2->capacity; ++j) { + ttf_cache_entry_t * entry = &bucket2->entries[j]; + if(entry->age == oldest) { + if(entry->data != NULL) { + TTF_CACHE_FREE(entry->data); + entry->data = NULL; + entry->age = 0; + cache->total_size -= entry->size; + entry->size = 0; + i = cache->bucket_size; + break; + } + } + } + } + } + if(bucket->entries == NULL) { + bucket->capacity = 4; + bucket->entries = (ttf_cache_entry_t *)TTF_CACHE_MALLOC(sizeof(ttf_cache_entry_t) * bucket->capacity); + if(bucket->entries == NULL) { + return NULL; + } + for(int i = 0; i < bucket->capacity; ++i) { + bucket->entries[i].age = 0; + bucket->entries[i].data = NULL; + bucket->entries[i].size = 0; + } + } + for(int i = 0; i < bucket->capacity; ++i) { + ttf_cache_entry_t * entry = &bucket->entries[i]; + if(entry->age == 0) { + entry->data = TTF_CACHE_MALLOC(size); + if(entry->data == NULL) { + return NULL; + } + entry->size = size; + entry->age = cache->age; + entry->key = key; + cache->total_size += size; + return entry->data; + } + } + int newcap = bucket->capacity * 2; + ttf_cache_entry_t * te = (ttf_cache_entry_t *)TTF_CACHE_REALLOC(bucket->entries, sizeof(ttf_cache_entry_t) * newcap); + if(te == NULL) { + return NULL; + } + bucket->entries = te; + for(int i = bucket->capacity; i < newcap; ++i) { + bucket->entries[i].age = 0; + bucket->entries[i].data = NULL; + bucket->entries[i].size = 0; + } + void * result = TTF_CACHE_MALLOC(size); + bucket->entries[bucket->capacity].data = result; + if(result == NULL) { + return NULL; + } + bucket->entries[bucket->capacity].size = size; + bucket->entries[bucket->capacity].age = cache->age; + bucket->entries[bucket->capacity].key = key; + bucket->capacity = newcap; + return result; +} + +static void ttf_cache_clear(ttf_cache_handle_t handle) +{ + if(handle == NULL) { + return; + } + ttf_cache_t * cache = (ttf_cache_t *)handle; + for(int i = 0; i < cache->bucket_size; ++i) { + ttf_cache_bucket_t * bucket = &cache->buckets[i]; + if(bucket->entries != NULL) { + for(int j = 0; j < bucket->capacity; ++j) { + ttf_cache_entry_t * entry = &bucket->entries[j]; + if(entry->age != 0 && entry->data != NULL) { + TTF_CACHE_FREE(entry->data); + } + } + TTF_CACHE_FREE(cache->buckets[i].entries); + } + } +} +static void ttf_cache_destroy(ttf_cache_handle_t handle) +{ + ttf_cache_clear(handle); + TTF_CACHE_FREE((ttf_cache_t *)handle); +} #if LV_TINY_TTF_FILE_SUPPORT !=0 // a hydra stream that can be in memory or from a file typedef struct ttf_cb_stream { @@ -63,9 +266,10 @@ typedef struct ttf_font_desc { #if LV_TINY_TTF_FILE_SUPPORT !=0 ttf_cb_stream_t stream; #else - const unsigned char * stream; + const uint8_t * stream; #endif stbtt_fontinfo info; + ttf_cache_handle_t cache; float scale; int ascent; int descent; @@ -115,53 +319,52 @@ static const uint8_t * ttf_get_glyph_bitmap_cb(const lv_font_t * font, uint32_t { ttf_font_desc_t * dsc = (ttf_font_desc_t *)font->dsc; const stbtt_fontinfo * info = (const stbtt_fontinfo *)&dsc->info; - static size_t buffer_size = 0; - int g1 = stbtt_FindGlyphIndex(info, (int)unicode_letter); - 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; - if(LV_GC_ROOT(_ttf_glyph_bitmap_buffer) == NULL) { - buffer_size = (size_t)(w * h); - LV_GC_ROOT(_ttf_glyph_bitmap_buffer) = (uint8_t *)lv_malloc(buffer_size); - if(LV_GC_ROOT(_ttf_glyph_bitmap_buffer) == NULL) { - buffer_size = 0; + uint8_t * buffer = (uint8_t *)ttf_cache_get(dsc->cache, unicode_letter); + if(buffer == NULL) { + int g1 = stbtt_FindGlyphIndex(info, (int)unicode_letter); + 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; + int buffer_size = w * h; + buffer = ttf_cache_add(dsc->cache, unicode_letter, buffer_size); + if(buffer == NULL) { return NULL; } - memset(LV_GC_ROOT(_ttf_glyph_bitmap_buffer), 0, buffer_size); + memset(buffer, 0, buffer_size); + stbtt_MakeGlyphBitmap(info, buffer, w, h, w, dsc->scale, dsc->scale, g1); } - else { - size_t s = w * h; - if(s > buffer_size) { - buffer_size = s; - LV_GC_ROOT(_ttf_glyph_bitmap_buffer) = (uint8_t *)lv_realloc(LV_GC_ROOT(_ttf_glyph_bitmap_buffer), buffer_size); - if(LV_GC_ROOT(_ttf_glyph_bitmap_buffer) == NULL) { - buffer_size = 0; - return NULL; - } - memset(LV_GC_ROOT(_ttf_glyph_bitmap_buffer), 0, buffer_size); - } - } - stbtt_MakeGlyphBitmap(info, LV_GC_ROOT(_ttf_glyph_bitmap_buffer), w, h, w, dsc->scale, dsc->scale, g1); - return LV_GC_ROOT(_ttf_glyph_bitmap_buffer); /*Or NULL if not found*/ + return buffer; /*Or NULL if not found*/ } -static lv_font_t * lv_tiny_ttf_create(const char * path, const void * data, size_t data_size, lv_coord_t line_height) +static lv_font_t * lv_tiny_ttf_create(const char * path, const void * data, size_t data_size, lv_coord_t line_height, + size_t cache_size) { - + LV_UNUSED(data_size); if((path == NULL && data == NULL) || 0 >= line_height) { LV_LOG_ERROR("tiny_ttf: invalid argument\n"); return NULL; } - ttf_font_desc_t * dsc = (ttf_font_desc_t *)lv_malloc(sizeof(ttf_font_desc_t)); + if(cache_size < 1) { + cache_size = LV_TINY_TTF_DEFAULT_CACHE_SIZE; + } + ttf_font_desc_t * dsc = (ttf_font_desc_t *)TTF_MALLOC(sizeof(ttf_font_desc_t)); if(dsc == NULL) { LV_LOG_ERROR("tiny_ttf: out of memory\n"); return NULL; } + dsc->cache = ttf_cache_create(cache_size, LV_TINY_TTF_CACHE_BUCKETS); + if(dsc->cache == NULL) { + LV_LOG_ERROR("tiny_ttf: out of memory\n"); + TTF_FREE(dsc); + 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)) { + ttf_cache_destroy(dsc->cache); + TTF_FREE(dsc); LV_LOG_ERROR("tiny_ttf: unable to open %s\n", path); return NULL; } @@ -174,25 +377,29 @@ static lv_font_t * lv_tiny_ttf_create(const char * path, const void * data, size dsc->stream.position = 0; } if(0 == stbtt_InitFont(&dsc->info, &dsc->stream, stbtt_GetFontOffsetForIndex(&dsc->stream, 0))) { - lv_free(dsc); + ttf_cache_destroy(dsc->cache); + TTF_FREE(dsc); + LV_LOG_ERROR("tiny_ttf: init failed\n"); return NULL; } #else - LV_UNUSED(data_size); dsc->stream = (const uint8_t *)data; if(0 == stbtt_InitFont(&dsc->info, dsc->stream, stbtt_GetFontOffsetForIndex(dsc->stream, 0))) { - lv_free(dsc); + ttf_cache_destroy(dsc->cache); + TTF_FREE(dsc); LV_LOG_ERROR("tiny_ttf: init failed\n"); return NULL; } + #endif float scale = stbtt_ScaleForPixelHeight(&dsc->info, line_height); - lv_font_t * out_font = (lv_font_t *)lv_malloc(sizeof(lv_font_t)); + lv_font_t * out_font = (lv_font_t *)TTF_MALLOC(sizeof(lv_font_t)); if(out_font == NULL) { - lv_free(dsc); + ttf_cache_destroy(dsc->cache); + TTF_FREE(dsc); LV_LOG_ERROR("tiny_ttf: out of memory\n"); return NULL; } @@ -211,14 +418,22 @@ static lv_font_t * lv_tiny_ttf_create(const char * path, const void * data, size return out_font; } #if LV_TINY_TTF_FILE_SUPPORT !=0 +lv_font_t * lv_tiny_ttf_create_file_ex(const char * path, lv_coord_t line_height, size_t cache_size) +{ + return lv_tiny_ttf_create(path, NULL, 0, line_height, cache_size); +} lv_font_t * lv_tiny_ttf_create_file(const char * path, lv_coord_t line_height) { - return lv_tiny_ttf_create(path, NULL, 0, line_height); + return lv_tiny_ttf_create(path, NULL, 0, line_height, 0); } #endif +lv_font_t * lv_tiny_ttf_create_data_ex(const void * data, size_t data_size, lv_coord_t line_height, size_t cache_size) +{ + return lv_tiny_ttf_create(NULL, data, data_size, line_height, cache_size); +} lv_font_t * lv_tiny_ttf_create_data(const void * data, size_t data_size, lv_coord_t line_height) { - return lv_tiny_ttf_create(NULL, data, data_size, line_height); + return lv_tiny_ttf_create(NULL, data, data_size, line_height, 0); } void lv_tiny_ttf_set_size(lv_font_t * font, lv_coord_t line_height) { @@ -228,6 +443,7 @@ void lv_tiny_ttf_set_size(lv_font_t * font, lv_coord_t line_height) dsc->scale = stbtt_ScaleForPixelHeight(&dsc->info, line_height); font->base_line = line_height - (lv_coord_t)(dsc->ascent * dsc->scale); font->underline_position = (uint8_t)line_height - dsc->descent; + ttf_cache_clear(dsc->cache); } } void lv_tiny_ttf_destroy(lv_font_t * font) @@ -235,14 +451,15 @@ void lv_tiny_ttf_destroy(lv_font_t * font) if(font != NULL) { if(font->dsc != NULL) { ttf_font_desc_t * ttf = (ttf_font_desc_t *)font->dsc; + ttf_cache_destroy(ttf->cache); #if LV_TINY_TTF_FILE_SUPPORT !=0 if(ttf->stream.file != NULL) { lv_fs_close(&ttf->file); } #endif - lv_free(ttf); + TTF_FREE(ttf); } - lv_free(font); + TTF_FREE(font); } } #endif diff --git a/src/libs/tiny_ttf/lv_tiny_ttf.h b/src/libs/tiny_ttf/lv_tiny_ttf.h index 98fae03c7..42f32c073 100644 --- a/src/libs/tiny_ttf/lv_tiny_ttf.h +++ b/src/libs/tiny_ttf/lv_tiny_ttf.h @@ -31,11 +31,17 @@ extern "C" { #if LV_TINY_TTF_FILE_SUPPORT !=0 // create a font from the specified file or path with the specified line height. lv_font_t * lv_tiny_ttf_create_file(const char * path, lv_coord_t line_height); -#endif // LV_TINY_TTF_FILE_SUPPORT !=0 + +// create a font from the specified file or path with the specified line height with the specified cache size. +lv_font_t * lv_tiny_ttf_create_file_ex(const char * path, lv_coord_t line_height, size_t cache_size); +#endif // create a font from the specified data pointer with the specified line height. lv_font_t * lv_tiny_ttf_create_data(const void * data, size_t data_size, lv_coord_t line_height); +// create a font from the specified data pointer with the specified line height and the specified cache size. +lv_font_t * lv_tiny_ttf_create_data_ex(const void * data, size_t data_size, lv_coord_t line_height, size_t cache_size); + // set the size of the font to a new line_height void lv_tiny_ttf_set_size(lv_font_t * font, lv_coord_t line_height); diff --git a/src/libs/tiny_ttf/stb_rect_pack.h b/src/libs/tiny_ttf/stb_rect_pack.h index e08e73fc8..2aadf535c 100644 --- a/src/libs/tiny_ttf/stb_rect_pack.h +++ b/src/libs/tiny_ttf/stb_rect_pack.h @@ -87,6 +87,12 @@ typedef int stbrp_coord; #define STBRP__MAXVAL 0x7fffffff // Mostly for internal use, but this is the maximum supported coordinate value. +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + + STBRP_DEF int stbrp_pack_rects(stbrp_context * context, stbrp_rect * rects, int num_rects); // Assign packed locations to rectangles. The rectangles are of type // 'stbrp_rect' defined below, stored in the array 'rects', and there @@ -579,6 +585,10 @@ STBRP_DEF int stbrp_pack_rects(stbrp_context * context, stbrp_rect * rects, int } #endif +#if defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic pop +#endif + /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. diff --git a/src/libs/tiny_ttf/stb_truetype_htcw.h b/src/libs/tiny_ttf/stb_truetype_htcw.h index 584cd4c24..f66e28f70 100644 --- a/src/libs/tiny_ttf/stb_truetype_htcw.h +++ b/src/libs/tiny_ttf/stb_truetype_htcw.h @@ -553,6 +553,11 @@ typedef struct { int size; } stbtt__buf; +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + ////////////////////////////////////////////////////////////////////////////// // // TEXTURE BAKING API @@ -5443,6 +5448,7 @@ STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char * s1, int len1, cons #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop + #pragma GCC diagnostic pop #endif #endif // STB_TRUETYPE_IMPLEMENTATION diff --git a/tests/ref_imgs/tiny_ttf_1.png b/tests/ref_imgs/tiny_ttf_1.png index 756ad82a5..9e1981f99 100644 Binary files a/tests/ref_imgs/tiny_ttf_1.png and b/tests/ref_imgs/tiny_ttf_1.png differ