diff --git a/docs/others/font_manager.rst b/docs/others/font_manager.rst new file mode 100644 index 000000000..d153883aa --- /dev/null +++ b/docs/others/font_manager.rst @@ -0,0 +1,104 @@ +.. _font_manager: + +============ +Font Manager +============ + +Font Manager is a secondary encapsulation based on :ref:`_freetype`, which facilitates upper-level applications to manage and use vector fonts. +Currently supported functions include: +- Font resource reference counting (reduces repeated creation of font resources). +- Font resource concatenation (font fallback). +- Font resource recycling mechanism (buffers recently deleted font resources to reduce the time overhead of repeated creation). + +.. _font_manager_usage: + +Usage +----- + +Enable :c:macro:`LIB_FREETYPE` and `LV_USE_FONT_MANAGER` in ``lv_conf.h``. + +Configure :c:macro:`LV_FONT_MANAGER_NAME_MAX_LEN` to set the maximum length of the font name. + +Initialize Font Manager +~~~~~~~~~~~~~~~~~~~~~~~ + +Use :cpp:func:`lv_font_manager_create` to create a font manager, where the :cpp:func:`recycle_cache_size` parameter +is used to set the number of font recycling caches,which can improve font creation efficiency. + +Use :cpp:func:`lv_font_manager_add_path_static` to add a mapping between the font file path and the custom font name, +so that the application can access the font resources more conveniently. +It should be noted that if the file path is not static (assigned from a local variable), +please use :cpp:func:`lv_font_manager_add_path` to add the path. This API will copy the path content to the internal management. + +Use :cpp:func:`lv_font_manager_remove_path` to remove the font path mapping. + +.. code-block:: c + + static lv_font_manager_t * g_font_manager = NULL; + + void font_manager_init(void) + { + /* Create font manager, with 8 fonts recycling buffers */ + g_font_manager = lv_font_manager_create(8); + + /* Add font path mapping to font manager */ + lv_font_manager_add_path_static(g_font_manager, "Lato-Regular", "./lvgl/examples/libs/freetype/Lato-Regular.ttf"); + lv_font_manager_add_path_static(g_font_manager, "MyFont", "./path/to/myfont.ttf"); + } + +Create Font from Font Manager +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use :cpp:func:`lv_font_manager_create_font` to create a font. The parameters are basically the same as :cpp:func:`lv_freetype_font_create`. +The :cpp:func:`font_family` parameter can be filled with the names of multiple fonts (separated by :cpp:expr:`,`) +to achieve font concatenation (when the corresponding glyph is not found in a font file, it will automatically search from the next concatenated font). + +.. code-block:: c + + static lv_font_t * g_font = NULL; + + /* Create font from font manager */ + lv_font_t * g_font = lv_font_manager_create_font(g_font_manager, + "Lato-Regular,MyFont", + LV_FREETYPE_FONT_RENDER_MODE_BITMAP, + 24, + LV_FREETYPE_FONT_STYLE_NORMAL); + + /* Create label with the font */ + lv_obj_t * label = lv_label_create(lv_screen_active()); + lv_obj_set_style_text_font(label, g_font, 0); + lv_label_set_text(label, "Hello World!"); + +Delete Font +~~~~~~~~~~~ + +Use :cpp:func:`lv_font_manager_delete_font` to delete the font. +The font manager will mark the font resource as a recyclable font so that it can be quickly created next time. +Note that you need to delete the widget that references the font first, and then delete the font to avoid accessing wild pointers. + +.. code-block:: c + + /* Delete label and font */ + lv_obj_delete(label); + lv_font_manager_delete_font(g_font_manager, g_font); + g_font = NULL; + +Delete Font Manager +~~~~~~~~~~~~~~~~~~~ + +Use :cpp:func:`lv_font_manager_delete` to destroy the entire font manager. It should be noted that before destruction, +it is necessary to ensure that the application has deleted all fonts using :cpp:func:`lv_font_manager_delete_font`. +The font manager will check the reference status of all allocated fonts. If there are still fonts being referenced, +the font manager will fail to destroy and return false. + +.. _font_manager_example: + +Example +------- + +.. include:: ../examples/others/font_manager/index.rst + +.. _font_manager_api: + +API +--- diff --git a/examples/others/font_manager/index.rst b/examples/others/font_manager/index.rst new file mode 100644 index 000000000..e6fc1a0aa --- /dev/null +++ b/examples/others/font_manager/index.rst @@ -0,0 +1,6 @@ + +Touchpad font manager example +----------------------- + +.. lv_example:: others/monkey/lv_example_font_manager_1 + :language: c diff --git a/examples/others/font_manager/lv_example_font_manager.h b/examples/others/font_manager/lv_example_font_manager.h new file mode 100644 index 000000000..44b4e6bea --- /dev/null +++ b/examples/others/font_manager/lv_example_font_manager.h @@ -0,0 +1,38 @@ +/** + * @file lv_example_font_manager.h + * + */ + +#ifndef LV_EXAMPLE_FONT_MANAGER_H +#define LV_EXAMPLE_FONT_MANAGER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_example_font_manager_1(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_EXAMPLE_MONKEY_H*/ diff --git a/examples/others/font_manager/lv_example_font_manager_1.c b/examples/others/font_manager/lv_example_font_manager_1.c new file mode 100644 index 000000000..7b6c6e2b8 --- /dev/null +++ b/examples/others/font_manager/lv_example_font_manager_1.c @@ -0,0 +1,41 @@ +#include "../../lv_examples.h" +#if LV_USE_FREETYPE && LV_USE_FONT_MANAGER && LV_BUILD_EXAMPLES + +#if LV_FREETYPE_USE_LVGL_PORT + #define PATH_PREFIX "A:" +#else + #define PATH_PREFIX "./" +#endif + +static lv_font_manager_t * g_font_manager = NULL; + +void lv_example_font_manager_1(void) +{ + /* Create font manager, with 8 fonts recycling buffers */ + g_font_manager = lv_font_manager_create(8); + + /* Add font path mapping to font manager */ + lv_font_manager_add_path_static(g_font_manager, + "Lato-Regular", + PATH_PREFIX "lvgl/examples/libs/freetype/Lato-Regular.ttf"); + + /* Create font from font manager */ + lv_font_t * font = lv_font_manager_create_font(g_font_manager, + "Lato-Regular", + LV_FREETYPE_FONT_RENDER_MODE_BITMAP, + 24, + LV_FREETYPE_FONT_STYLE_NORMAL); + + if(!font) { + LV_LOG_ERROR("Could not create font"); + return; + } + + /* Create label with the font */ + lv_obj_t * label = lv_label_create(lv_screen_active()); + lv_obj_set_style_text_font(label, font, 0); + lv_label_set_text(label, "Hello Font Manager!"); + lv_obj_center(label); +} + +#endif diff --git a/examples/others/lv_example_others.h b/examples/others/lv_example_others.h index 281de3cca..3c48d5bc1 100644 --- a/examples/others/lv_example_others.h +++ b/examples/others/lv_example_others.h @@ -14,6 +14,7 @@ extern "C" { * INCLUDES *********************/ #include "file_explorer/lv_example_file_explorer.h" +#include "font_manager/lv_example_font_manager.h" #include "fragment/lv_example_fragment.h" #include "gridnav/lv_example_gridnav.h" #include "ime/lv_example_ime_pinyin.h" diff --git a/lvgl.h b/lvgl.h index 5b62c3f52..4cf9efbbd 100644 --- a/lvgl.h +++ b/lvgl.h @@ -91,6 +91,7 @@ extern "C" { #include "src/others/observer/lv_observer.h" #include "src/others/ime/lv_ime_pinyin.h" #include "src/others/file_explorer/lv_file_explorer.h" +#include "src/others/font_manager/lv_font_manager.h" #include "src/libs/barcode/lv_barcode.h" #include "src/libs/bin_decoder/lv_bin_decoder.h" diff --git a/src/others/font_manager/lv_font_manager.c b/src/others/font_manager/lv_font_manager.c index fcf91e6ba..854d3ef0b 100755 --- a/src/others/font_manager/lv_font_manager.c +++ b/src/others/font_manager/lv_font_manager.c @@ -199,9 +199,14 @@ lv_font_t * lv_font_manager_create_font(lv_font_manager_t * manager, const char ret_font = lv_font_manager_create_font_single(manager, &ft_info); } - /* make LV_SYMBOL displayable */ - if(ret_font) { - ret_font->fallback = LV_FONT_DEFAULT; + /* Append fallback font to make LV_SYMBOL displayable */ + lv_font_t * cur_font = ret_font; + while(cur_font) { + if(cur_font->fallback == NULL) { + cur_font->fallback = LV_FONT_DEFAULT; + break; + } + cur_font = (lv_font_t *)cur_font->fallback; } return ret_font; @@ -265,7 +270,7 @@ static bool lv_font_manager_delete_font_single(lv_font_manager_t * manager, lv_f if(!rec_node) { LV_LOG_WARN("NO record found for font: %p(%d)," " it was not created by font manager", - font, (int)font->line_height); + (void *)font, (int)font->line_height); return false; } @@ -424,7 +429,7 @@ static bool lv_font_manager_check_resource(lv_font_manager_t * manager) lv_font_rec_node_t * node; LV_LL_READ(rec_ll, node) { LV_LOG_WARN("font: %p(%d) -> ref: %s(%d)", - node, + (void *)node, (int)node->font.line_height, node->refer_node_p->ft_info.name, node->refer_node_p->ft_info.size); @@ -460,7 +465,7 @@ static lv_font_rec_node_t * lv_font_manager_search_rec_node(lv_font_manager_t * lv_font_rec_node_t * rec_node; LV_LL_READ(&manager->rec_ll, rec_node) { if(font == &rec_node->font) { - LV_LOG_INFO("font: %p(%d) matched", font, (int)font->line_height); + LV_LOG_INFO("font: %p(%d) matched", (void *)font, (int)font->line_height); return rec_node; } } diff --git a/src/others/font_manager/lv_font_manager_recycle.c b/src/others/font_manager/lv_font_manager_recycle.c index 7a2d15157..38271f321 100755 --- a/src/others/font_manager/lv_font_manager_recycle.c +++ b/src/others/font_manager/lv_font_manager_recycle.c @@ -101,7 +101,7 @@ lv_font_t * lv_font_manager_recycle_get_reuse(lv_font_manager_recycle_t * manage /* match font */ if(lv_freetype_info_is_equal(ft_info, &recycle->ft_info)) { lv_font_t * font = recycle->font; - LV_LOG_INFO("found font: %p", font); + LV_LOG_INFO("found font: %p", (void *)font); /* remove reused font */ lv_ll_remove(recycle_ll, recycle); diff --git a/tests/src/lv_test_conf_full.h b/tests/src/lv_test_conf_full.h index 081bea544..4fa8c8a61 100644 --- a/tests/src/lv_test_conf_full.h +++ b/tests/src/lv_test_conf_full.h @@ -158,6 +158,8 @@ #define LV_FREETYPE_USE_LVGL_PORT 0 #define LV_FREETYPE_CACHE_FT_GLYPH_CNT 10 +#define LV_USE_FONT_MANAGER 1 + #define LV_USE_DRAW_SW_COMPLEX_GRADIENTS 1 #endif /* LV_TEST_CONF_FULL_H */ diff --git a/tests/src/test_cases/libs/test_font_stress.c b/tests/src/test_cases/libs/test_font_stress.c index a18a9a2b4..ffbdc655d 100644 --- a/tests/src/test_cases/libs/test_font_stress.c +++ b/tests/src/test_cases/libs/test_font_stress.c @@ -4,7 +4,7 @@ #include "unity/unity.h" -#if LV_USE_FREETYPE +#if LV_USE_FREETYPE && LV_USE_FONT_MANAGER #include "rnd_unicodes/lv_rnd_unicodes.h" @@ -27,36 +27,36 @@ typedef struct { int font_cnt; int label_cnt; int loop_cnt; -} lvx_font_stress_config_t; +} font_stress_config_t; -typedef struct { +struct _font_stress_ctx_t; + +typedef lv_font_t * (*font_create_cb_t)(struct _font_stress_ctx_t * ctx, + const char * name, + lv_freetype_font_render_mode_t render_mode, + uint32_t size, + lv_freetype_font_style_t style); + +typedef void (*font_delete_cb_t)(struct _font_stress_ctx_t * ctx, lv_font_t * font); + +typedef struct _font_stress_ctx_t { + font_stress_config_t config; lv_obj_t * par; lv_obj_t ** label_arr; - lvx_font_stress_config_t config; -} stress_test_ctx_t; + lv_font_manager_t * font_manager; + font_create_cb_t font_create_cb; + font_delete_cb_t font_delete_cb; +} font_stress_ctx_t; /********************** * STATIC PROTOTYPES **********************/ -static void update_cb(void); - /********************** * STATIC VARIABLES **********************/ -static stress_test_ctx_t g_ctx = { 0 }; -static const char * font_name_arr[] = { - "./src/test_files/fonts/noto/NotoSansSC-Regular.ttf", - "../src/libs/freetype/arial.ttf", - "../demos/multilang/assets/fonts/Montserrat-Bold.ttf", - "UNKNOWN_FONT_NAME" -}; -static const uint16_t font_style[] = { - LV_FREETYPE_FONT_STYLE_NORMAL, - LV_FREETYPE_FONT_STYLE_ITALIC, - LV_FREETYPE_FONT_STYLE_BOLD, -}; +static font_stress_ctx_t g_ctx = { 0 }; /********************** * MACROS @@ -78,8 +78,15 @@ static const uint16_t font_style[] = { * STATIC FUNCTIONS **********************/ -static lv_obj_t * label_create(const char * font_name, lv_obj_t * par, int size, int x, int y) +static lv_obj_t * font_stress_label_create(font_stress_ctx_t * ctx, const char * font_name, lv_obj_t * par, int size, + int x, int y) { + static const uint16_t font_style[] = { + LV_FREETYPE_FONT_STYLE_NORMAL, + LV_FREETYPE_FONT_STYLE_ITALIC, + LV_FREETYPE_FONT_STYLE_BOLD, + }; + uint32_t index = lv_rand(0, sizeof(font_style) / sizeof(uint16_t) - 1); uint32_t r = lv_rand(0, 0xFF); uint32_t g = lv_rand(0, 0xFF); @@ -87,7 +94,7 @@ static lv_obj_t * label_create(const char * font_name, lv_obj_t * par, int size, lv_opa_t opa = lv_rand(0, LV_OPA_COVER); lv_color_t color = lv_color_make(r, g, b); - lv_font_t * font = lv_freetype_font_create(font_name, LV_FREETYPE_FONT_RENDER_MODE_BITMAP, size, font_style[index]); + lv_font_t * font = ctx->font_create_cb(ctx, font_name, LV_FREETYPE_FONT_RENDER_MODE_BITMAP, size, font_style[index]); if(!font) { return NULL; } @@ -106,26 +113,28 @@ static lv_obj_t * label_create(const char * font_name, lv_obj_t * par, int size, lv_label_set_text(label, (char *)str); return label; } -static void label_delete(lv_obj_t * label) + +static void font_stress_label_delete(font_stress_ctx_t * ctx, lv_obj_t * label) { const lv_font_t * font = lv_obj_get_style_text_font(label, 0); - LV_ASSERT_NULL(font); - lv_freetype_font_delete((lv_font_t *)font); + TEST_ASSERT_NOT_NULL(font); lv_obj_del(label); + ctx->font_delete_cb(ctx, (lv_font_t *)font); } -static void label_delete_all(stress_test_ctx_t * ctx) + +static void font_stress_label_delete_all(font_stress_ctx_t * ctx) { for(int i = 0; i < ctx->config.label_cnt; i++) { lv_obj_t * label = ctx->label_arr[i]; if(label) { - label_delete(label); + font_stress_label_delete(ctx, label); ctx->label_arr[i] = NULL; } } } -static void update_cb(void) + +static void font_stress_update(font_stress_ctx_t * ctx) { - stress_test_ctx_t * ctx = &g_ctx; uint32_t label_index = lv_rand(0, ctx->config.label_cnt - 1); uint32_t font_index = lv_rand(0, ctx->config.font_cnt - 1); uint32_t font_size = lv_rand(0, MAX_FONT_SIZE); @@ -134,13 +143,14 @@ static void update_cb(void) lv_obj_t * label = ctx->label_arr[label_index]; if(label) { - label_delete(label); + font_stress_label_delete(ctx, label); ctx->label_arr[label_index] = NULL; } else { const char * pathname = ctx->config.font_name_arr[font_index]; - LV_ASSERT_NULL(pathname); - label = label_create( + TEST_ASSERT_NOT_NULL(pathname); + label = font_stress_label_create( + ctx, pathname, ctx->par, (int)font_size, @@ -150,37 +160,74 @@ static void update_cb(void) } } +static lv_font_t * freetype_font_create_cb(font_stress_ctx_t * ctx, + const char * name, + lv_freetype_font_render_mode_t render_mode, + uint32_t size, + lv_freetype_font_style_t style) +{ + LV_UNUSED(ctx); + return lv_freetype_font_create(name, render_mode, size, style); +} + +static void freetype_font_delete_cb(font_stress_ctx_t * ctx, lv_font_t * font) +{ + LV_UNUSED(ctx); + lv_freetype_font_delete(font); +} + +static lv_font_t * font_manager_font_create_cb(font_stress_ctx_t * ctx, + const char * name, + lv_freetype_font_render_mode_t render_mode, + uint32_t size, + lv_freetype_font_style_t style) +{ + TEST_ASSERT_NOT_NULL(ctx->font_manager); + return lv_font_manager_create_font(ctx->font_manager, name, render_mode, size, style); +} + +static void font_manager_font_delete_cb(font_stress_ctx_t * ctx, lv_font_t * font) +{ + TEST_ASSERT_NOT_NULL(ctx->font_manager); + lv_font_manager_delete_font(ctx->font_manager, font); +} + void setUp(void) { lv_freetype_init(LV_FREETYPE_CACHE_FT_GLYPH_CNT); - g_ctx.par = lv_scr_act(); - - g_ctx.config.loop_cnt = MAX_LOOP_CNT; + g_ctx.par = lv_screen_active(); g_ctx.config.label_cnt = MAX_LABEL_CNT; - g_ctx.config.font_name_arr = font_name_arr; - g_ctx.config.font_cnt = sizeof(font_name_arr) / sizeof(font_name_arr[0]); - - size_t arr_size = sizeof(lv_obj_t *) * g_ctx.config.label_cnt; - g_ctx.label_arr = lv_malloc(arr_size); - LV_ASSERT_MALLOC(g_ctx.label_arr); - lv_memzero(g_ctx.label_arr, arr_size); - - lv_rand_set_seed(RND_START_SEED); + g_ctx.label_arr = lv_malloc_zeroed(sizeof(lv_obj_t *) * g_ctx.config.label_cnt); + TEST_ASSERT_NOT_NULL(g_ctx.label_arr); } void tearDown(void) { - label_delete_all(&g_ctx); lv_freetype_uninit(); - lv_free(g_ctx.label_arr); + g_ctx.label_arr = NULL; } void test_font_stress(void) { + lv_rand_set_seed(RND_START_SEED); + + static const char * font_name_arr[] = { + "./src/test_files/fonts/noto/NotoSansSC-Regular.ttf", + "../src/libs/freetype/arial.ttf", + "../demos/multilang/assets/fonts/Montserrat-Bold.ttf", + "UNKNOWN_FONT_NAME" + }; + + g_ctx.config.loop_cnt = MAX_LOOP_CNT; + g_ctx.config.font_name_arr = font_name_arr; + g_ctx.config.font_cnt = sizeof(font_name_arr) / sizeof(font_name_arr[0]); + g_ctx.font_create_cb = freetype_font_create_cb; + g_ctx.font_delete_cb = freetype_font_delete_cb; + for(uint32_t i = 0; g_ctx.config.loop_cnt > 0; g_ctx.config.loop_cnt--) { - update_cb(); + font_stress_update(&g_ctx); lv_refr_now(NULL); if(g_ctx.config.loop_cnt % CAPTURE_SKIP_FRAMES == 0) { @@ -189,6 +236,57 @@ void test_font_stress(void) i++; } } + + font_stress_label_delete_all(&g_ctx); +} + +void test_font_manager_stress(void) +{ + lv_rand_set_seed(RND_START_SEED); + + g_ctx.font_manager = lv_font_manager_create(2); + TEST_ASSERT_NOT_NULL(g_ctx.font_manager); + lv_font_manager_add_path_static(g_ctx.font_manager, "NotoSansSC-Regular", + "./src/test_files/fonts/noto/NotoSansSC-Regular.ttf"); + lv_font_manager_add_path_static(g_ctx.font_manager, "Arial", "../src/libs/freetype/arial.ttf"); + lv_font_manager_add_path(g_ctx.font_manager, "Montserrat-Bold", "../demos/multilang/assets/fonts/Montserrat-Bold.ttf"); + lv_font_manager_add_path(g_ctx.font_manager, "UNKNOWN", "UNKNOWN_FONT_PATH"); + + static const char * font_name_arr[] = { + "NotoSansSC-Regular,Arial", + "Arial", + "Montserrat-Bold", + "UNKNOWN" + }; + + g_ctx.config.loop_cnt = MAX_LOOP_CNT; + g_ctx.config.font_name_arr = font_name_arr; + g_ctx.config.font_cnt = sizeof(font_name_arr) / sizeof(font_name_arr[0]); + g_ctx.font_create_cb = font_manager_font_create_cb; + g_ctx.font_delete_cb = font_manager_font_delete_cb; + + for(uint32_t i = 0; g_ctx.config.loop_cnt > 0; g_ctx.config.loop_cnt--) { + font_stress_update(&g_ctx); + lv_refr_now(NULL); + + if(g_ctx.config.loop_cnt % CAPTURE_SKIP_FRAMES == 0) { + char buf[64]; + TEST_FREETYPE_ASSERT_EQUAL_SCREENSHOT(i); + i++; + } + } + + font_stress_label_delete_all(&g_ctx); + + bool remove_ok = lv_font_manager_remove_path(g_ctx.font_manager, "Arial"); + TEST_ASSERT_TRUE(remove_ok); + + remove_ok = lv_font_manager_remove_path(g_ctx.font_manager, "UNKNOWN"); + TEST_ASSERT_TRUE(remove_ok); + + bool success = lv_font_manager_delete(g_ctx.font_manager); + TEST_ASSERT_TRUE(success); + g_ctx.font_manager = NULL; } #else @@ -205,6 +303,10 @@ void test_font_stress(void) { } +void test_font_manager_stress(void) +{ +} + #endif #endif