backport lvgl fallback font to enable support for more language (via external flash font loading)
Verification
- [X] I searched for similar feature request and found none was relevant.
Pitch us your idea!
Let's backport font fallback to enable more languages and emoji
Description
Due to the small size of os storage, we can only fit a very small number of glyphs in the default font.
To support other non-English language, numerous forks have been created to support different language by adding glyphs to jetbrains_mono_bold_20 such as this one
Loading custom font from external storage is another idea that have been proposed several times. (e.g. https://github.com/InfiniTimeOrg/InfiniTime/issues/212 ) There are also watchfaces in current release that already use external flash to store fonts.
However, if a LV_LABEL is set to use a certain font. (as stated here)
lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
It can only use the font specified here and cannot fallback to system font (e.g. jetbrains_mono_bold_20) if a glyph is not in this font. This means the custom font will have to duplicate everything in jetbrains_mono_bold_20 including ascii alnum, which is not very efficient.
Starting from LVGL 8.1, a fallback font can be selected. This way, only the extended glyphs (non-english alphabet, emoji ...) needs to be put in the custom font. Unfortunately, InfiniTime is currently using LVGL 7.
I think backporting this function should be considered since previous attempt to upgrade to LVGL 8 is stalled.
If I didn't miss anything, the following patch should be enough.
diff --git a/src/lv_draw/lv_draw_label.c b/src/lv_draw/lv_draw_label.c
index 970791ff..7cbf099b 100644
--- a/src/lv_draw/lv_draw_label.c
+++ b/src/lv_draw/lv_draw_label.c
@@ -433,6 +433,10 @@ LV_ATTRIBUTE_FAST_MEM static void lv_draw_letter(const lv_point_t * pos_p, const
return;
}
+ if (g.resolved_font) {
+ font_p = g.resolved_font;
+ }
+
const uint8_t * map_p = lv_font_get_glyph_bitmap(font_p, letter);
if(map_p == NULL) {
LV_LOG_WARN("lv_draw_letter: character's bitmap not found");
diff --git a/src/lv_font/lv_font.c b/src/lv_font/lv_font.c
index 9e3ec220..49bffcbc 100644
--- a/src/lv_font/lv_font.c
+++ b/src/lv_font/lv_font.c
@@ -61,7 +61,18 @@ const uint8_t * lv_font_get_glyph_bitmap(const lv_font_t * font_p, uint32_t lett
bool lv_font_get_glyph_dsc(const lv_font_t * font_p, lv_font_glyph_dsc_t * dsc_out, uint32_t letter,
uint32_t letter_next)
{
- return font_p->get_glyph_dsc(font_p, dsc_out, letter, letter_next);
+ dsc_out->resolved_font = NULL;
+ const lv_font_t * f = font_p;
+ bool found = false;
+ while(f) {
+ found = f->get_glyph_dsc(f, dsc_out, letter, letter_next);
+ if (found) {
+ dsc_out->resolved_font = f;
+ break;
+ }
+ f = f->fallback;
+ }
+ return found;
}
/**
diff --git a/src/lv_font/lv_font.h b/src/lv_font/lv_font.h
index 26cc653b..b4353c17 100644
--- a/src/lv_font/lv_font.h
+++ b/src/lv_font/lv_font.h
@@ -34,7 +34,9 @@ extern "C" {
*-----------------*/
/** Describes the properties of a glyph. */
+struct _lv_font_struct;
typedef struct {
+ const struct _lv_font_struct *resolved_font; /**< Pointer to a font where the gylph was actually found after handling fallbacks*/
uint16_t adv_w; /**< The glyph needs this space. Draw the next glyph after this width. */
uint16_t box_w; /**< Width of the glyph's bounding box*/
uint16_t box_h; /**< Height of the glyph's bounding box*/
@@ -70,6 +72,7 @@ typedef struct _lv_font_struct {
int8_t underline_thickness; /**< Thickness of the underline*/
void * dsc; /**< Store implementation specific or run_time data or caching here*/
+ const struct _lv_font_struct * fallback; /**< Fallback font for missing glyph. Resolved recursively */
#if LV_USE_USER_DATA
lv_font_user_data_t user_data; /**< Custom user data for font. */
#endif
Thanks @Boteium, this feature from LVGL 8.1 looks really interesting!
We have not (yet) switched to lvgl8, mostly by lack of time to review the PR and fix remaining issues...
I think this would be really interesting to test this feature backported in LVGL7 and see how well it performs! Just keep in mind that loading a font from external flash is quite slow, especially with bigger fonts. Also, the whole font will be stored in RAM, which means that we won't be able to have a huuuuge font that contains all characters from all languages, emojis,... because we are limited by RAM capacity.
Oh, I didn't know that. Skimming through lvgl's source, It seems that lv_fs_open() does load the entire file into RAM just like you said. So, huge font (larger than ~20kb) can only be store in the precious system storage for now.
I think this feature can still be very useful. For example, a huge fallback font is still possible if someone write a font engine that loads the bitmap on the fly from external flash in the future. Or, custom font stored in system storage can still save some space.
We're preparing to use our own fork of LVGL. If you're interested in working on these ideas, feel free to open a PR in https://github.com/InfiniTimeOrg/lvgl
Oh, I didn't know that. Skimming through lvgl's source, It seems that lv_fs_open() does load the entire file into RAM just like you said. So, huge font (larger than ~20kb) can only be store in the precious system storage for now.
Please check: https://github.com/lvgl/lvgl/pull/4462 Maybe we can upgrade to lvgl 8.1 to use all of them