准备工作
-
lvgl VS2022仿真环境搭建 lvgl vs2022仿真环境
-
lvgl官网在线字体转换工具链接 Font Converter
-
lv_font_conv离线字体转换工具
(链接1) lv_font_conv
(链接2)引用于博客路过人间本尊 -
LvglFontTool_V0.4字体转换工具 LvglFontTool_V0.4
-
LvglFontTool_V0.5字体转换工具(测试版) LvglFontTool_V0.5测试版
-
完整例程下载
通过网盘分享的文件:lvgl830_font_example.zip
链接: https://pan.baidu.com/s/1wG52EMDcdHsOKeHAdorjuQ 提取码: 3td3
各种加载字体方式说明
<方式1>
通过读取外部字体bin到ram方式加载字体,lv_font_conv离线转换工具或官网在线字体转换工具转换(字体转换 unicode范围0x00-0x024f, 0x400-0x4ff,0x4e00-0x9fff ,0xFF00-0xFFEF,0x3000-0x30ff, 0xac00-0xd7af,字体SourceHanSansSC-Bold.otf,bpp1,font size 18,输出bin格式)
- 需要分2次转换,使用字体回滚方式链接起来,一次转换生成的bin,加载时会有乱码
lv_font_conv --no-compress --format bin --font SourceHanSansSC-Bold.otf -o sys_font_one.bin --bpp 1 --size 18 -r 0x00-0x024f -r 0x400-0x4ff -r 0x4e00-0x9fff -r 0xFF00-0xFFEF
lv_font_conv --no-compress --format bin --font SourceHanSansSC-Bold.otf -o sys_font_two.bin --bpp 1 --size 18 -r 0x3000-0x30ff -r 0xac00-0xd7af
- 字体加载使用lv_font_load_w函数加载,此函数在lvgl里新增的,新增如下函数,具体实现参考例程l链接下载工程中的v_font_loader.c的实现
static bit_iterator_t init_bit_iterator_w(void);
static bool lvgl_load_font_w(uint8_t* p_font, lv_font_t* font);
static int read_label_w(uint8_t* p_font, uint32_t* index, int start, const char* label);
static int32_t load_cmaps_w(uint8_t* p_font, uint32_t* index, lv_font_fmt_txt_dsc_t* font_dsc, uint32_t cmaps_start);
static bool load_cmaps_tables_w(uint8_t* p_font, uint32_t* index, lv_font_fmt_txt_dsc_t* font_dsc,
uint32_t cmaps_start, cmap_table_bin_t* cmap_table);
static unsigned int read_bits_w(bit_iterator_t* it, int n_bits, lv_fs_res_t* res, uint8_t* p_font, uint32_t* index);
static int read_bits_signed_w(bit_iterator_t* it, int n_bits, lv_fs_res_t* res, uint8_t* p_font, uint32_t* index);
int32_t load_kern_w(uint8_t* p_font, uint32_t* index, lv_font_fmt_txt_dsc_t* font_dsc, uint8_t format, uint32_t start);
lv_font_t* lv_font_load_w(const char* font_data);
- 加载字体,这里使用文件系统将字体内容读到ram中,然后通过lv_font_load_w函数去加载字体,本示例字体文件分2次加载,通过回滚字体方式链接起来
static uint8_t* load_font_bin_data(const char* file_name)
{
lv_fs_file_t file;
lv_fs_res_t res = lv_fs_open(&file, file_name, LV_FS_MODE_RD);
uint32_t size;
uint32_t len;
char* pdata = NULL;
if (res == LV_FS_RES_OK) {
res = lv_fs_seek(&file, 0, LV_FS_SEEK_END);
if (res == LV_FS_RES_OK) {
res = lv_fs_tell(&file, &size);
if (res == LV_FS_RES_OK) {
printf(" File size: %lu bytes\n", size);
}
else {
printf(" Failed to get file size via tell (error: %d)\n", res);
}
}
else {
printf(" Failed to seek to end of file (error: %d)\n", res);
}
lv_fs_seek(&file, 0, LV_FS_SEEK_SET);
pdata = lv_mem_alloc(size);
lv_fs_read(&file, pdata, size, &len);
lv_fs_close(&file);
}
else {
printf(" Error code: %d\n", res);
}
return pdata;
}
/**
* @brieaf lv_font_load_w函数在lv_font_loader中新增的,lvgl原生的没有此函数
* @note 字体使用了回滚,官网在线字体转换工具和lv_font_conv离线转换工具,
* 转换0x00-0x024f,0x400-0x4ff,0x4e00-0x9fff,0xFF00-0xFFEF,0x3000-0x30ff,0xac00-0xd7af范图图书卡
*/
lv_font_t* get_font_handler_by_ram(void)
{
uint8_t *pData = load_font_bin_data((const char*)"S:/fonts/sys_font_one.bin");
lv_font_t* ft1 = lv_font_load_w(pData);
if (pData != NULL)
{
lv_mem_free(pData);
}
pData = load_font_bin_data("S:/fonts/sys_font_two.bin");
lv_font_t* ft2 = lv_font_load_w(pData);
if (pData != NULL)
{
lv_mem_free(pData);
}
if (ft1 != NULL && ft2 != NULL)
{
ft1->fallback = ft2;
return ft1;
}
return NULL;
}
- 字体使用
// 方法1,通过读取外部字体bin到ram方式加载字体,lv_font_conv离线转换工具或官网在线字体转换工具转换
lv_font_t* font = get_font_handler_by_ram();
if (font != NULL)
{
lv_obj_t* obj = lv_obj_create(lv_scr_act());
lv_obj_t* label = lv_label_create(obj);
lv_obj_set_size(obj, 800, 120);
lv_obj_set_style_text_font(label, font, 0);
lv_obj_set_style_text_color(label, lv_color_make(255, 0, 0), 0);
lv_label_set_text(label, "欢迎来到Multilingual测试.This is a mixed language test.日本語\nと 한국어 and 中文 are all included.\nThank you for testing!谢谢!ありがとう!감사합니다!");
}
- 运行效果

<方式2>
通过lvgl提供的lv_font_load原生接口加载,字体bin文件同上
- 加载字体
lv_font_t* get_font_handler(void)
{
lv_font_t* ft1 = lv_font_load("S:/fonts/sys_font_one.bin");
lv_font_t* ft2 = lv_font_load("S:/fonts/sys_font_two.bin");
if (ft1 != NULL && ft2 != NULL)
{
ft1->fallback = ft2;
return ft1;
}
return NULL;
}
- 字体使用
lv_font_t* font = get_font_handler();
if (font != NULL)
{
lv_obj_t* obj = lv_obj_create(lv_scr_act());
lv_obj_t* label = lv_label_create(obj);
lv_obj_set_size(obj, 800, 120);
lv_obj_set_pos(obj, 0, 120);
lv_obj_set_style_text_color(label, lv_color_make(0, 255, 0), 0);
lv_obj_set_style_text_font(label, font, 0);
lv_label_set_text(label, "欢迎来到Multilingual测试.This is a mixed language test.日本語\nと 한국어 and 中文 are all included.\nThank you for testing!谢谢!ありがとう!감사합니다!");
}
- 运行效果

<方式3>
通过加载C方式,lv_font_conv离线转换工具转换或者lvglFontTool_V0.4工具,lvglFontTool_V0.5测试版工具转换C文件加载字体
- 生成.c文件,包含字体结构信息
// lv_font_conv离线转换工具转换
/*Initialize a public general font descriptor*/
#if LV_VERSION_CHECK(8, 0, 0)
const lv_font_t test_font_conv = {
#else
lv_font_t test_font_conv = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
.line_height = 25, /*The maximum line height required by the font*/
.base_line = 6, /*Baseline measured from the bottom of the line*/
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
.subpx = LV_FONT_SUBPX_NONE,
#endif
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
.underline_position = -2,
.underline_thickness = 1,
#endif
.dsc = &font_dsc /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
};
// lvglFontTool_V0.4工具,lvglFontTool_V0.5测试版工具转换
//字模高度:29
//内部字体
//使用排序和二分查表
const lv_font_t test_font_c = {
.dsc = &font_dsc,
.get_glyph_bitmap = __user_font_get_bitmap,
.get_glyph_dsc = __user_font_get_glyph_dsc,
.line_height = 29,
.base_line = 0,
};
- 字体声明
LV_FONT_DECLARE(test_font_c);
LV_FONT_DECLARE(test_font_conv);
- 字体使用
// 方法3 通过加载C方式,lv_font_conv离线转换工具转换
lv_obj_t* obj = lv_obj_create(lv_scr_act());
lv_obj_t* label = lv_label_create(obj);
lv_obj_set_size(obj, 800, 120);
lv_obj_set_pos(obj, 0, 240);
lv_obj_set_style_text_font(label, &test_font_conv, 0);
lv_obj_set_style_text_color(label, lv_color_make(0, 0, 255), 0);
lv_label_set_text(label, "欢迎来到Multilingual测试.This is a mixed language test.日本語\nと 한국어 and 中文 are all included.\nThank you for testing!谢谢!ありがとう!감사합니다!");
// 方法5 lvglFontTool 5工具转换C文件加载字体
obj = lv_obj_create(lv_scr_act());
label = lv_label_create(obj);
lv_obj_set_size(obj, 800, 130);
lv_obj_set_pos(obj, 0, 480);
lv_obj_set_style_text_color(label, lv_color_make(255, 0, 255), 0);
lv_obj_set_style_text_font(label, &test_font_c, 0);
lv_label_set_text(label, "欢迎来到Multilingual测试.This is a mixed language test.日本語\nと 한국어 and 中文 are all included.\nThank you for testing!谢谢!ありがとう!감사합니다!");
const lv_font_t test_font_c = {
.dsc = &font_dsc, // 指向字体描述符
.get_glyph_bitmap = __user_font_get_bitmap, // 位图获取函数
.get_glyph_dsc = __user_font_get_glyph_dsc, // 字符描述获取函数
.line_height = 46, // 行高
.base_line = 0, // 基线位置
};
// 应用字体到对象
lv_obj_set_style_text_font(obj, &test_font_c, LV_PART_MAIN);
步骤2: 文本渲染时的字符查找,当 LVGL 需要渲染文本时:
// LVGL 内部调用流程
lv_draw_label(...)
→ lv_font_get_glyph_dsc(&test_font_c, &glyph_dsc, letter, next_letter)
→ test_font_c.get_glyph_dsc(&test_font_c, &glyph_dsc, letter, next_letter)
→ __user_font_get_glyph_dsc(font, dsc, unicode, unicode_next)
步骤3: 获取字符描述,_user_font_get_glyph_dsc 函数的工作:
static bool __user_font_get_glyph_dsc(const lv_font_t * font,
lv_font_glyph_dsc_t * dsc,
uint32_t unicode,
uint32_t unicode_next) {
// 1. 在字体中查找字符
// 2. 填充字符信息:
dsc->adv_w = 25; // 字符宽度
dsc->box_w = 20; // 边界框宽度
dsc->box_h = 30; // 边界框高度
dsc->ofs_x = 0; // X偏移
dsc->ofs_y = 0; // Y偏移
dsc->bpp = 1; // 位深度(1-bit 单色)
return found; // 返回是否找到字符
}
步骤4: 获取位图数据
// LVGL 内部调用
lv_font_get_glyph_bitmap(&test_font_c, letter)
→ test_font_c.get_glyph_bitmap(&test_font_c, letter)
→ __user_font_get_bitmap(font, unicode)
__user_font_get_bitmap 函数:
static const uint8_t * __user_font_get_bitmap(const lv_font_t * font,
uint32_t unicode) {
// 返回字符对应的位图数据指针
// 位图数据通常是单色位图,每像素1位
return glyph_bitmap_data;
}
// 字体描述符 (通常由字体转换工具生成)
static const lv_font_fmt_txt_dsc_t font_dsc = {
.glyph_bitmap = glyph_bitmaps, // 位图数据数组
.glyph_dsc = glyph_descriptors, // 字符描述数组
.cmaps = unicode_mappings, // Unicode映射表
.kern_dsc = kerning_data, // 字距调整数据
.kern_scale = 0, // 字距缩放
.cmap_num = 1, // 映射表数量
.bpp = 1, // 位深度
.bitmap_format = LV_FONT_FMT_TXT_PLAIN,
};
// 字符描述数组
static const lv_font_fmt_txt_glyph_dsc_t glyph_descriptors[] = {
{.bitmap_index = 0, .adv_w = 25, .box_w = 20, .box_h = 30, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 75, .adv_w = 28, .box_w = 23, .box_h = 32, .ofs_x = 1, .ofs_y = -1},
// ... 更多字符
};
// 位图数据数组
static const uint8_t glyph_bitmaps[] = {
0x00, 0x00, 0x00, 0x00, 0x00, // 字符1的位图数据
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 字符2的位图数据
// ... 更多位图数据
};
test_font_c (lv_font_t)
├── dsc → font_dsc (lv_font_fmt_txt_dsc_t)
│ ├── glyph_bitmap → glyph_bitmaps[]
│ ├── glyph_dsc → glyph_descriptors[]
│ └── cmaps → unicode_mappings[]
├── get_glyph_bitmap → __user_font_get_bitmap
├── get_glyph_dsc → __user_font_get_glyph_dsc
├── line_height = 46
└── base_line = 0
-
按需加载:只在需要渲染时获取字符数据
-
缓存友好:频繁使用的字符可能被缓存
-
内存效率:只加载使用的字符,减少内存占用
-
快速查找:通过映射表快速定位字符数据
<方式4>
lvglFontTool_V0.4工具转换bin文件加载字体
LV_FONT_DECLARE(sys_font);
static uint8_t __g_font_buf[319];//如bin文件存在SPI FLASH可使用此buff
static uint8_t* __user_font_getdata(int offset, int size) {
lv_fs_file_t file;
lv_fs_res_t res = lv_fs_open(&file, "S:/fonts/sys_font.bin", LV_FS_MODE_RD);
if (res != LV_FS_RES_OK)
{
return NULL;
}
res = lv_fs_seek(&file, offset, LV_FS_SEEK_SET);
uint32_t len;
lv_fs_read(&file, __g_font_buf, size, &len);
lv_fs_close(&file);
return __g_font_buf;
}
-
内部flash方式适配,假如读内部flash函数原型为int32_t bsp_flash_data_read(uint32_t addr,uint8_t *data ,uint32_t length);则适配如下
static uint8_t __g_font_buf[319];//如bin文件存在SPI FLASH可使用此buff
static uint8_t* __user_font_getdata(int offset, int size) {
if(bsp_flash_data_read(offset,__g_font_buf,size) != 0)
{
return NULL;
}
return __g_font_buf;
}
-
SD卡方式适配,假如读SD卡函数原型为int32_t bsp_sd_card_read_blocks(uint8_t *data, uint32_t block_addr, uint32_t block_num);则适配如下:
static uint8_t __g_font_buf[1024]; //由于SD读取最小单位是块,一个块512字节,有可能跨块读取,因此这里大小要1024字节,2个块大小
static uint8_t *__user_font_getdata(int offset, int size){
uint32_t start_block = offset >> 9;
uint32_t block_offset = offset & 0x1ff;
uint32_t blocks_to_read = (size + block_offset + 511) >> 9;
int32_t ret = bsp_sd_card_read_blocks(__g_font_buf, start_block, blocks_to_read);
if (ret != 0) {
return NULL;
}
return &__g_font_buf[block_offset];
}
// 方法4 lvglFontTool 4工具转换bin文件加载字体
lv_obj_t* obj = lv_obj_create(lv_scr_act());
lv_obj_t* label = lv_label_create(obj);
lv_obj_set_size(obj, 800, 120);
lv_obj_set_pos(obj, 0, 360);
lv_obj_set_style_text_font(label, &sys_font, 0);
lv_obj_set_style_text_color(label, lv_color_make(0, 255, 255), 0);
lv_label_set_text(label, "欢迎来到Multilingual测试.This is a mixed language test.日本語\nと 한국어 and 中文 are all included.\nThank you for testing!谢谢!ありがとう!감사합니다!");
- 运行效果

bin文件字体加载过程
- 字体结构体解析
lv_font_t sys_font = {
.get_glyph_bitmap = __user_font_get_bitmap, // 获取字形位图数据
.get_glyph_dsc = __user_font_get_glyph_dsc, // 获取字形描述信息
.line_height = 29, // 字体行高
.base_line = 0, // 基线位置
};
步骤1: 文本布局计算
// LVGL内部调用流程
void lv_draw_label(lv_draw_ctx_t * draw_ctx, const lv_draw_label_dsc_t * dsc, const lv_area_t * coords) {
// 遍历每个字符
for(each character in text) {
// 调用 get_glyph_dsc 获取字形信息
font->get_glyph_dsc(font, &g_dsc, unicode, unicode_next);
// 计算字符位置
pos_x += letter_space;
draw_pos.x = pos_x + g_dsc.ofs_x;
draw_pos.y = pos_y + g_dsc.ofs_y;
// 调用 get_glyph_bitmap 获取位图并绘制
bitmap = font->get_glyph_bitmap(font, unicode);
lv_draw_letter(draw_ctx, &g_dsc, draw_pos, bitmap);
pos_x += g_dsc.adv_w;
}
}
步骤2: 字形描述符获取 (get_glyph_dsc)
static bool __user_font_get_glyph_dsc(const lv_font_t* font,
lv_font_glyph_dsc_t* dsc_out,
uint32_t unicode_letter,
uint32_t unicode_letter_next) {
// 1. 检查Unicode范围
if (unicode_letter < __g_xbf_hd.min || unicode_letter > __g_xbf_hd.max)
return false;
// 2. 计算索引表位置
uint32_t index_offset = sizeof(x_header_t) + (unicode_letter - __g_xbf_hd.min) * 4;
// 3. 读取字形位置指针
uint32_t glyph_pos = *(uint32_t*)__user_font_getdata(index_offset, 4);
if (glyph_pos == 0) return false;
// 4. 读取字形描述符
glyph_dsc_t* gdsc = (glyph_dsc_t*)__user_font_getdata(glyph_pos, sizeof(glyph_dsc_t));
// 5. 填充LVGL字形描述符
dsc_out->adv_w = gdsc->adv_w; // 字宽(步进)
dsc_out->box_w = gdsc->box_w; // 位图宽度
dsc_out->box_h = gdsc->box_h; // 位图高度
dsc_out->ofs_x = gdsc->ofs_x; // X偏移
dsc_out->ofs_y = gdsc->ofs_y; // Y偏移
dsc_out->bpp = __g_xbf_hd.bpp; // 位深度
return true;
}
步骤3: 字形位图获取 (get_glyph_bitmap)
static const uint8_t* __user_font_get_bitmap(const lv_font_t* font, uint32_t unicode_letter) {
// 1-3. 与get_glyph_dsc相同的查找逻辑
// ...
// 4. 计算位图数据位置和大小
uint32_t bitmap_offset = glyph_pos + sizeof(glyph_dsc_t);
uint32_t bitmap_size = gdsc->box_w * gdsc->box_h * __g_xbf_hd.bpp / 8;
// 5. 返回位图数据指针
return __user_font_getdata(bitmap_offset, bitmap_size);
}
数据结构关系
lv_font_t
├── get_glyph_dsc (回调)
├── get_glyph_bitmap (回调)
├── line_height
└── base_line
↓
XBF字体文件结构:
x_header_t (min, max, bpp)
├── 索引表[x_table_t] (每个Unicode对应glyph位置)
│ ├── glyph_dsc_t (adv_w, box_w, box_h, ofs_x, ofs_y)
│ │ └── 位图数据[box_w * box_h * bpp/8]
│ └── …
└── …
实际绘制过程示例
假设绘制文本"Hello":
// 对于字符 'H'
1. get_glyph_dsc(&font, &dsc, 'H', 'e')
→ 返回: adv_w=15, box_w=12, box_h=20, ofs_x=1, ofs_y=-2
2. get_glyph_bitmap(&font, 'H')
→ 返回: 12×20 位图数据指针
3. LVGL在位置 (x+1, y-2) 绘制12×20的位图
4. x坐标增加15 (adv_w),准备下一个字符
// 重复过程绘制 'e'、'l'、'l'、'o'
关键参数说明
line_height vs box_h
line_height=29: 整行文字的高度(包含上下间距)
box_h=20: 实际字形位图的高度
差值用于行间距和字符上下留白
坐标系统
(0,0) ┌─────────────┐
│ │
│ ofs_y │ ← 字符可能向上偏移
│ ↑ │
│ ┌─────────┐ │
│ │ 位图 │ │ box_h
│ │ │ │
│ └─────────┘ │
│ ofs_x → │
│ │
└─────────────┘ line_height
│── adv_w ───│ ← 下一个字符起始位置
BPP(位深度)影响
bpp=4: 每个像素用4位表示,16级灰度
位图大小 = box_w * box_h * bpp / 8 字节
性能瓶颈分析
在您的实现中,主要耗时在
// 每次字符绘制都需要:
- __user_font_getdata(index_offset, 4) // 读索引
- __user_font_getdata(glyph_pos, sizeof(glyph_dsc_t)) // 读描述符
- __user_font_getdata(bitmap_offset, bitmap_size) // 读位图数据
所有方式运行效果图


1824

被折叠的 条评论
为什么被折叠?



