使用各种嵌入式GUI时,总会遇到“汉字显示”“字体”这些关卡。
阅读本文前,最好已经了解Uincode,UTF-8,UTF-16,GBK,GB2312相关知识,不懂最好网络搜索相关知识。
littlevGL内置了好几种字体。在lv_conf.h中开关相关字体
/*==================
* FONT USAGE
*===================*/
/* More info about fonts: https://littlevgl.com/basics#fonts
* To enable a built-in font use 1,2,4 or 8 values
* which will determine the bit-per-pixel */
#define USE_LV_FONT_DEJAVU_10 0
#define USE_LV_FONT_DEJAVU_10_LATIN_SUP 0
#define USE_LV_FONT_DEJAVU_10_CYRILLIC 0
#define USE_LV_FONT_SYMBOL_10 0
#define USE_LV_FONT_DEJAVU_20 4
#define USE_LV_FONT_DEJAVU_20_LATIN_SUP 0
#define USE_LV_FONT_DEJAVU_20_CYRILLIC 0
#define USE_LV_FONT_SYMBOL_20 4
#define USE_LV_FONT_DEJAVU_30 0
#define USE_LV_FONT_DEJAVU_30_LATIN_SUP 0
#define USE_LV_FONT_DEJAVU_30_CYRILLIC 0
#define USE_LV_FONT_SYMBOL_30 0
#define USE_LV_FONT_DEJAVU_40 0
#define USE_LV_FONT_DEJAVU_40_LATIN_SUP 0
#define USE_LV_FONT_DEJAVU_40_CYRILLIC 0
#define USE_LV_FONT_SYMBOL_40 0
#define USE_LV_FONT_MONOSPACE_8 0
其中0代表不使用,1,2,4,8使能并设置抗锯齿值。
字体文件在lv_fonts文件夹下。
littlevGL支持UTF-8。在lv_conf.h中开启UTF8
/*Text settings*/
#define LV_TXT_UTF8 1 /*Enable UTF-8 coded Unicode character usage */
littlevGL支持自定义字体。
官方字体生成网站:https://littlevgl.com/ttf-font-to-c-array
不知为何作者没有发布离线字体生成工具。我打算过段时间有空就自己用C#写一个。
生成的字体文件类似于lv_fonts文件夹下面的各种字体文件,使用方式也一样。
使用之前需要把源文件转为UTF8格式。
//myfont是自定义的字体
void lv_tutorial_fonts(void)
{
static lv_style_t style1;
char *str="我的祖国\nis 好好的!";
/*Create a style and use the new font*/
lv_style_copy(&style1, &lv_style_plain);
style1.text.font = &myfont;
/*Create a label and set new text*/
lv_obj_t * label = lv_label_create(lv_scr_act(), NULL);
lv_obj_set_pos(label, 100, 10);
lv_label_set_style(label, &style1);
lv_label_set_text(label, str );
}
上面的方法只适合字体文件比较小的情况,要是字体太大,放不进MCU,那就需要更进一步。
对源文件的显示字体源码分析后,发现关键在于字体结构
typedef struct _lv_font_struct
{
uint32_t unicode_first;
uint32_t unicode_last;
const uint8_t * glyph_bitmap;
const lv_font_glyph_dsc_t * glyph_dsc;
const uint32_t * unicode_list;
const uint8_t * (*get_bitmap)(const struct _lv_font_struct *,uint32_t); /*Get a glyph's bitmap from a font*/
int16_t (*get_width)(const struct _lv_font_struct *,uint32_t); /*Get a glyph's with with a given font*/
struct _lv_font_struct * next_page; /*Pointer to a font extension*/
uint32_t h_px :8;
uint32_t bpp :4; /*Bit per pixel: 1, 2 or 4*/
uint32_t monospace :8; /*Fix width (0: normal width)*/
uint16_t glyph_cnt; /*Number of glyphs (letters) in the font*/
} lv_font_t;
打开一个字体文件,其中字体定义
lv_font_t lv_font_dejavu_20 = {
.unicode_first = 32, /*First Unicode letter in this font*/
.unicode_last = 126, /*Last Unicode letter in this font*/
.h_px = 20, /*Font height in pixels*/
.glyph_bitmap = lv_font_dejavu_20_glyph_bitmap, /*Bitmap of glyphs*/
.glyph_dsc = lv_font_dejavu_20_glyph_dsc, /*Description of glyphs*/
.glyph_cnt = 95, /*Number of glyphs in the font*/
.unicode_list = NULL, /*Every character in the font from 'unicode_first' to 'unicode_last'*/
.get_bitmap = lv_font_get_bitmap_continuous, /*Function pointer to get glyph's bitmap*/
.get_width = lv_font_get_width_continuous, /*Function pointer to get glyph's width*/
#if USE_LV_FONT_DEJAVU_20 == 1
.bpp = 1, /*Bit per pixel*/
#elif USE_LV_FONT_DEJAVU_20 == 2
.bpp = 2, /*Bit per pixel*/
#elif USE_LV_FONT_DEJAVU_20 == 4
.bpp = 4, /*Bit per pixel*/
#elif USE_LV_FONT_DEJAVU_20 == 8
.bpp = 8, /*Bit per pixel*/
#endif
.monospace = 0,
.next_page = NULL, /*Pointer to a font extension*/
};
上面代码,lv_font_get_bitmap_continuous()函数为字体点阵获取函数,返回的是对应字符在点阵数组的位置。
另外还有些字体使用的是lv_font_get_bitmap_sparse();这两个函数功能是一样的。
由此,我们可以仿照此函数,想办法从外部存储器获得某个字符的点阵数据,并且保存在一个静态数组里,最后返回此数组首地址即可。
要完成这些,前提是1.把字符的点阵数组转化为BIN文件(去原子的论坛搜索下载C2B 1.1版本,2.0版本有BUG);2.把此文件弄到外置存储器。
对于第2点。有很多方法。比如通过写一个PC串口助手把文件发送烧写进SPI flash(W25QXX之类);或者直接使用专门的烧录工具写进SPI flash;或者偷懒,直接放在SD卡里让MCU调用。
这里我使用偷懒的方法,把字体文件放到SD卡里,以文件的形式读取数据。必须要说明,此方法频繁地进行文件操作,速度不咋地(最好把堆栈调大些,否则可能溢出)。
内置的字体点阵获取函数
/**
* 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_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;
uint32_t i;
for(i = 0; font->unicode_list[i] != 0; i++) {
if(font->unicode_list[i] == unicode_letter) {
return &font->glyph_bitmap[font->glyph_dsc[i].glyph_index];
}
}
return NULL;
}
仿照上面,自定义的函数
//look above
const uint8_t * ex_lv_font_get_bitmap_sparse(const lv_font_t * font, uint32_t unicode_letter)
{
uint8_t * pval=NULL;
uint32_t i;
/*Check the range*/
if(unicode_letter < font->unicode_first || unicode_letter > font->unicode_last){ return NULL;}
for(i = 0; font->unicode_list[i] != 0; i++)
{
if(font->unicode_list[i] == unicode_letter)
{
FIL Binfile;
FRESULT res;
uint32_t br,fsize;
i=font->glyph_dsc[i].glyph_index;
res=f_open(&Binfile, (const TCHAR*)font->glyph_bitmap ,FA_OPEN_EXISTING|FA_READ);
if(res != FR_OK) { return NULL;}
fsize = Binfile.fsize ;
if(i+LetterSIZE <= fsize)
{
f_lseek(&Binfile,i );
res = f_read(&Binfile, letterBuff ,LetterSIZE ,&br);
if( res == FR_OK && ( br == LetterSIZE || br == LetterSIZE/2 ) || br==0 )
{
pval=letterBuff;
}
}
f_close(&Binfile);
return pval;
}
}
return NULL;
}
然后要把字体的点阵数组删除,在字体定义里修改.glyph_bitmap和.glyph_bitmap成员。如下
lv_font_t myfont =
{
.unicode_first = 32, /*First Unicode letter in this font*/
.unicode_last = 40664, /*First Unicode letter in this font*/
.h_px = 37, /*Font height in pixels*/
.glyph_bitmap = "0:/FONT/HZ1A.bin", /*Bitmap of glyphs*/
.glyph_dsc = glyph_dsc, /*Description of glyphs*/
.unicode_list = unicode_list, /*List of unicode characters*/
.get_bitmap = ex_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*/
.bpp = 1, /*Bit per pixel*/
.next_page = NULL, /*Pointer to a font extension*/
};
"0:/FONT/HZ1A.bin"是字体文件路径。
再次说明,此方法不应该在实际项目中使用,效率不好。建议把字体文件按地址烧录进SPI flash,需要的时候再按照地址直接读取。上面的".glyph_bitmap"可赋值为该地址。原理都是一样的。