InfiniTimeOrg / InfiniTime

Firmware for Pinetime smartwatch written in C++ and based on FreeRTOS
GNU General Public License v3.0
2.7k stars 923 forks source link

backport lvgl fallback font to enable support for more language (via external flash font loading) #1473

Open Boteium opened 1 year ago

Boteium commented 1 year ago

Verification

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
JF002 commented 1 year ago

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.

Boteium commented 1 year ago

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.

Boteium commented 1 year ago

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.

Riksu9000 commented 1 year ago

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

xz-dev commented 3 months ago

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