lvgl字体转换及使用详解

准备工作

通过网盘分享的文件: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,加载时会有乱码
    • 分别转换 0x00-0x024f ,0x400-0x4ff ,0x4e00-0x9fff ,0xFF00-0xFFEF 和0x3000-0x30ff,0xac00-0xd7af ,转换命令如下

 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!谢谢!ありがとう!감사합니다!");

  • 运行效果
    在这里插入图片描述
  • c文件方式加载过程解析

    • 字体结构初时化
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,                        // 基线位置
};
  • 字体加载调用流程

    步骤1: 字体设置
// 应用字体到对象
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

  • 性能优化特点

  1. 按需加载:只在需要渲染时获取字符数据

  2. 缓存友好:频繁使用的字符可能被缓存

  3. 内存效率:只加载使用的字符,减少内存占用

  4. 快速查找:通过映射表快速定位字符数据

<方式4>

lvglFontTool_V0.4工具转换bin文件加载字体

  • 字体声明

LV_FONT_DECLARE(sys_font);
  • 接口适配,适配原则,是从bin文件中偏移offset位置读取size的大小的内容到__g_font_bu并返回,这里使用文件系统方式适配,频繁开关文件系统效率会很低,这里仅作测试字体使用的适配

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 字节

性能瓶颈分析

在您的实现中,主要耗时在
// 每次字符绘制都需要:

  1. __user_font_getdata(index_offset, 4) // 读索引
  2. __user_font_getdata(glyph_pos, sizeof(glyph_dsc_t)) // 读描述符
  3. __user_font_getdata(bitmap_offset, bitmap_size) // 读位图数据

所有方式运行效果图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仙剑情缘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值