Dynamic Load Of Fonts

Hi,

It’s certainly not an issue for small systems but for the HMI I am working on it would be useful to be able to dynamically load a font from flash rather than compile them all into the build. (for a particular customer only certain font instances might be needed on their displays).

I think the offline font converter can output a binary format - has anyone been able to load that (or some other format) dynamically?

Thanks,

-Simon.

I also want to make an offline EXE program to generate a certain part of the text.

Hi,

There was a discussion about a similar topic in a few days ago here:
https://forum.lvgl.io/t/font-filesystem-support/237

I suggest continuing the discussion there.

Thanks - unfortunately that link doesn’t seem to work for me. ( Private conversation?).

It doesn’t work for me either now. It seems the user deleted himself and the post too. :confused:

So let’s discuss it here.

My only comment there was the following:

In my opinion, it’s not worth to store the whole font in a file because some metadata (e.g. width) is read a lot of times and not requires too much space. So probably it’s better to keep them in flash/RAM or cache them.

However, storing the bitmaps in files and reading them on demand (maybe with some caching) would save a lot of memory especially with huge fonts.

Hi Gábor,

Yes - totally agree. Our aim is simply do avoid having all possible fonts built into the program image.

We’d do as you suggest i.e. load into RAM and cache and even more simply just leave them in the cache. (An application would only ever use a small subset of all the possible fonts in its lifetime).

I don’t think it would be too difficult to parse the LittleVGL format .c files generated by the font converter and create a corresponding binary file that could be loaded into RAM and then ‘fix up’ the pointers that are required (both data and function) - I was just wondering if someone had perhaps already done something like that (or similar) or if maybe I was missing something obvious.

Kind Regards,

Simon.

There is a detailed documentation about the binary format of the font converter here.

So basically all we need to do is to create:

  • lv_font_get_bitmap_fmt_BINARY
  • lv_font_get_glyph_dsc_fmt_BINARY

similarly to the current “text” format readers:

And finally create an lv_font_t where the new BINARY functions are used:

What do you think?

I have done this in lvgl v5.3.

First: change you font struct data , and write you font`s bitmap array to SPIFlash

lv_font_t microsoft_yahei_chinese_16 = 
{
    .unicode_first = 32,	/*First Unicode letter in this font*/
    .unicode_last = 40863,	/*Last Unicode letter in this font*/
    .h_px = 16,				/*Font height in pixels*/
    .glyph_bitmap = microsoft_yahei_chinese_16_glyph_bitmap,	/*Bitmap of glyphs*/
    .glyph_dsc = microsoft_yahei_chinese_16_glyph_dsc,		/*Description of glyphs*/
    .glyph_cnt = 3500,			/*Number of glyphs in the font*/
    .unicode_list = microsoft_yahei_chinese_16_unicode_list,	/*List of unicode characters*/
#ifndef USE_SPI_FLASH_FONT
    .get_bitmap = lv_font_get_bitmap_sparse,	/*Function pointer to get glyph's bitmap*/
    .get_width = lv_font_get_width_sparse,	/*Function pointer to get glyph's width*/
#else
    .get_bitmap = lv_font_get_spiflash_bitmap_sparse,	/*Function pointer to get glyph's bitmap*/
    .get_width = lv_font_get_spiflash_width_sparse,	/*Function pointer to get glyph's width*/
#endif
#if USE_MICROSOFT_YAHEI_CHINESE_16 == 1
    .bpp = 1,				/*Bit per pixel*/
 #elif USE_MICROSOFT_YAHEI_CHINESE_16 == 2
    .bpp = 2,				/*Bit per pixel*/
 #elif USE_MICROSOFT_YAHEI_CHINESE_16 == 4
    .bpp = 4,				/*Bit per pixel*/
 #elif USE_MICROSOFT_YAHEI_CHINESE_16 == 8
    .bpp = 8,				/*Bit per pixel*/
#endif
    .monospace = 0,				/*Fix width (0: if not used)*/
    .next_page = NULL,		/*Pointer to a font extension*/
};

Second : implement you function to read date from SpiFlash.

#ifdef USE_SPI_FLASH_FONT

#include <SerialFlash.h>

static uint32_t glyph_dsc_size = sizeof(lv_font_glyph_dsc_t);
static lv_font_glyph_dsc_t tempGlyphDsc;
static const lv_font_t * tempDscFont = NULL;
static uint32_t TempDscLetter;

static uint8_t tempBitMap[40*40];
static const lv_font_t * tempMapFont = NULL;
static uint32_t tempMapLetter;

static void getTempGlyphDsc(const lv_font_t * font, uint32_t unicode_letter , uint32_t idx)
{
	static uint8_t * temp_addr = (uint8_t*)&tempGlyphDsc;

	if( tempDscFont != font || TempDscLetter != unicode_letter )
	{
		SerialFlash.read((uintptr_t)(font->glyph_dsc + idx) , temp_addr , glyph_dsc_size);
		tempDscFont = font;
		TempDscLetter = unicode_letter;
	}
}

static bool isTempGlyphDsc(const lv_font_t * font, uint32_t unicode_letter)
{
	return tempDscFont == font && TempDscLetter == unicode_letter;
}

static void getTempBitMap(const lv_font_t * font, uint32_t unicode_letter, lv_font_glyph_dsc_t glyph_dsc)
{
	if( tempMapFont != font || tempMapLetter != unicode_letter )
	{
		SerialFlash.read((uintptr_t)(font->glyph_bitmap + glyph_dsc.glyph_index) , tempBitMap , font->h_px * glyph_dsc.w_px);
		tempMapFont = font;
		tempMapLetter = unicode_letter;
	}
}

static bool isTempBitMap(const lv_font_t * font, uint32_t unicode_letter)
{
	return tempMapFont == font && tempMapLetter == unicode_letter;
}


/**
 * Generic bitmap get function used in 'font->get_bitmap' when the font NOT contains all characters in the range (sparse)
 * @param font pointer to font
 * @param unicode_letter an unicode letter which bitmap should be get
 * @return pointer to the bitmap or NULL if not found
 */
const uint8_t * lv_font_get_spiflash_bitmap_sparse(const lv_font_t * font, uint32_t unicode_letter)
{
    /*Check the range*/
    if(unicode_letter < font->unicode_first || unicode_letter > font->unicode_last) return NULL;

    if(isTempBitMap( font, unicode_letter))
    {
    	return tempBitMap;
    }
    else if(isTempGlyphDsc(font,unicode_letter))
    {
    	getTempBitMap(font, unicode_letter,tempGlyphDsc);
    	return tempBitMap;
    }

    uint32_t i;
    for(i = 0; font->unicode_list[i] != 0; i++) {
        if(font->unicode_list[i] == unicode_letter) {
        	getTempGlyphDsc(font, unicode_letter , i);
        	getTempBitMap(font, unicode_letter , tempGlyphDsc);
            return tempBitMap;
        }
    }

    return NULL;
}

/**
 * Generic glyph width get function used in 'font->get_bitmap' when the font NOT contains all characters in the range (sparse)
 * @param font pointer to font
 * @param unicode_letter an unicode letter which width should be get
 * @return width of the glyph or -1 if not found
 */
int16_t lv_font_get_spiflash_width_sparse(const lv_font_t * font, uint32_t unicode_letter)
{
    /*Check the range*/
    if(unicode_letter < font->unicode_first || unicode_letter > font->unicode_last) return -1;

    /* is cache ok */
    if(isTempGlyphDsc(font,unicode_letter))
    	return tempGlyphDsc.w_px;

    uint32_t i;
    for(i = 0; font->unicode_list[i] != 0; i++) {
        if(font->unicode_list[i] == unicode_letter) {
        	getTempGlyphDsc(font, unicode_letter , i);
            return tempGlyphDsc.w_px;
        }
    }

    return -1;
}
#endif

@ShenRen The font system was heavily rewritten in v6.0 so I think you would need to make quite a few changes to your code to make it work.

Also, please use Markdown C code wrappers ```c and ``` to wrap source code.

Yes, very strange. I think it’s the same person who suggested printf: https://github.com/littlevgl/lvgl/pull/1152, so they also deleted their GitHub account.

I think it’s going to take me a while (and a fair bit of caffeine) to understand what you’re suggesting in the context of the binary file generated by the converter and the relationship to the embedded fonts!

I’ll be back!

-Simon.

1 Like

Storing fonts in SPIFI.

The first difficulty is that in the lv_font_t structure definition there are function pointers which cannot be stored in external flash.

The second difficulty is that unless you link everything at once the executable does not know where the SPIFI resources are located.

  1. Convert the font in the normal way. This goes into SPIFI.
const lv_font_t font_yahei48_lv = { .. }
  1. Declare a font struct in RAM
static lv_font_t ram_font_yahei36;
  1. Declare a font pointer
lv_font_t * font_yahei36 = 0;
  1. On startup copy the SPIFI font structure into the RAM font structure, then set the function pointers in the RAM struct.
memcpy(&ram_font_yahei36, (lv_font_t *)spifi_font_address, sizeof(lv_font_t));
ram_font_yahei36.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt;
ram_font_yahei36.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt;
font_yahei36 = &ram_font_yahei36;
  1. Set the font pointer to the RAM struct, and use the font pointer in the code. eg.
#define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(* font_yahei24)
#define LV_FONT_DEFAULT        font_yahei24

There’s more to it in that you can keep an SPIFI address list from the SPIFI MAP file, so that don’t need to manually maintain a list of addresses.

Also you can use the font pointer to switch to a backup font if the SPIFI is not initialized or fails.

But this should help if you wish to do this.

We have just started a discussion about on GitHub.

1 Like

What’s the status of this topic, is it possible in newer LVGL to load glyphs from external storage?

Yes. https://docs.lvgl.io/latest/en/html/overview/font.html#load-font-in-run-time

1 Like