字体文件中主要存储两种信息,一种是字符点阵数组另一种是对应的unicode编码数组数据。单片机解析字体时是根据unicode编码来找到对应字符点阵数组的。
LVGL 字体格式生成的字体文件中的点阵数据不像我们平常在单片机中使用的字模。
首先每个字符的点阵数据所占字节数不相等,比如单引号",“和”@"字符,他们所占的字节数是不相等的,这样做是为了节省空间将字符中空的行和列的地方不转化为点阵。这样的话就需要其他信息来控制字符的显示位置。因此字模数据中除了字符点阵数组、unicode数组外还有有个重要的数组 glyph_dsc[]。
LVGL字库结构
uint8_t glyph_bitmap[]; //字形屏幕像素映射(字符点阵数组,将字符中空的行和列的地方不转化为点阵)
lv_font_fmt_txt_glyph_dsc_t glyph_dsc; //描述unicode偏移、字形大小、位置偏移
uint16_t unicode_list_0; //unicode 列表(字符映射)
lv_font_fmt_txt_cmap_t cmaps; //cmaps收集 unicode 列表和字符偏移
lv_font_fmt_txt_dsc_t font_dsc; //字体描述,用于将上面的信息收集打包
lv_font_t ch_word; //程序中 label 等组件可以使用的字库。
解析字体文件
程序会先得到待显示字符的 UTF-8 编码,然后将 UTF-8 转为 uncode 编码,再在字体文件中的 unicode 数组中查找对应的 unicode 编码的索引 ,再由索引查得 glyph_dsc 中相应字符的数据。由于 unicode 是按照升序排列可以使用中值查找法提高查找速度。
像素深度bpp
⼀个像素的颜⾊在计算机中由多少位数据来描述。计算机中⽤⼆进制位来表⽰⼀个像素的数据,⽤来表⽰⼀个像素的数据位越多,则这个像素的颜⾊值更加丰富、分的更细,颜⾊深度就更深。
像素深度有这么⼏种:1位、2位、4位、8位、16位、24位、32位。
对于字体的bpp可以设置为1,2,4,选择越大的 bpp 值时,要求的 flash 存储资源也是成倍的增加的,但是bpp 值越大,绘制出来的字体边缘越平滑,没有毛刺,使我们产品的 UI 界面看上去更高大上。
对于1bpp的字体:用1位表示1个像素点,每个像素只有亮灭两种状态,1个汉字占用空间256/8=32Byte字节
对于2bpp的字体:用2位表示1个像素点,实现亚像素渲染,1个汉字占用空间2562/8=64Byte字节
对于4bpp的字体:用4位表示1个像素点,实现亚像素渲染,1个汉字占用空间2564/8=128Byte字节
字形屏幕像素映射
//存储符号的图像
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
/* U+56DE "回" */
0x0, 0x11, 0xfb, 0xc1, 0x40, 0x29, 0xe5, 0x24,
0xa4, 0x94, 0xb2, 0x98, 0x50, 0xa, 0x1, 0x7f,
0xa8, 0xc, 0x0, 0x0,
/* U+6D41 "流" */
0x1, 0x0, 0x1, 0x0, 0xc0, 0x0, 0x3f, 0xc0,
0x8, 0x0, 0x24, 0x13, 0xfc, 0x8, 0x0, 0x12,
0xa0, 0x25, 0x40, 0x92, 0x81, 0x25, 0x26, 0x4a,
0x45, 0x14, 0x84, 0xf, 0x80,
/* U+96EA "雪" */
0x0, 0x60, 0x7e, 0x4, 0x20, 0x9f, 0xfe, 0x82,
0x20, 0xeb, 0x0, 0x2e, 0xe, 0x80, 0x1, 0xe0,
0x78, 0x80, 0xa, 0x7, 0xd0, 0x0, 0x40, 0xff,
0x0,
/* U+98CE "风" */
0x0, 0x0, 0x23, 0xc0, 0x78, 0x80, 0x85, 0x1,
0xa, 0x2, 0xa4, 0x4, 0xc8, 0x8, 0x90, 0x13,
0x20, 0x49, 0x20, 0xa2, 0x49, 0x80, 0xa4, 0x0,
0xc0, 0x0, 0x80, 0x0, 0x0
};
观察字形屏幕像素映射数组可以得出
“回”,占用20Byte
“流”,占用29Byte
“雪”,占用25Byte
“风”,占用29Byte
字形描述
//这描述了一个字形
typedef struct {
#if LV_FONT_FMT_TXT_LARGE == 0
uint32_t bitmap_index : 20; /**< 字符对应的字模数据索引 Start index of the bitmap. A font can be max 1 MB.*/
uint32_t adv_w : 12; /**< 字符宽度,在此宽度之后绘制下一个字形。8.4 format(存储real_value * 16)。 Draw the next glyph after this width. 8.4 format (real_value * 16 is stored).*/
uint8_t box_w; /**< 字模宽度 Width of the glyph's bounding box*/
uint8_t box_h; /**< 字模高度 Height of the glyph's bounding box*/
int8_t ofs_x; /**< 字模水平方向偏移(右边为正向) x offset of the bounding box*/
int8_t ofs_y; /**< 字模竖直方向偏移(上边为正向)(当字符需要在基线以下显示时使用这个参数让字模下沉)y offset of the bounding box. Measured from the top of the line*/
#else
uint32_t bitmap_index; /**< Start index of the bitmap. A font can be max 4 GB.*/
uint32_t adv_w; /**< Draw the next glyph after this width. 28.4 format (real_value * 16 is stored).*/
uint16_t box_w; /**< Width of the glyph's bounding box*/
uint16_t box_h; /**< Height of the glyph's bounding box*/
int16_t ofs_x; /**< x offset of the bounding box*/
int16_t ofs_y; /**< y offset of the bounding box. Measured from the top of the line*/
#endif
} lv_font_fmt_txt_glyph_dsc_t;
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
{.bitmap_index = 0, .adv_w = 256, .box_w = 11, .box_h = 14, .ofs_x = 3, .ofs_y = -1}, //回
{.bitmap_index = 20, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, //流
{.bitmap_index = 49, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = -1}, //雪
{.bitmap_index = 74, .adv_w = 256, .box_w = 15, .box_h = 15, .ofs_x = 1, .ofs_y = -2} //风
};
- 成员bitmap_index:
从数组glyph_dsc中同样可以得出汉字字形屏幕像素映射占用字节:
“回”的字符对应的字模数据索引bitmap_index=0
“流”的字符对应的字模数据索引bitmap_index=20 ,所以“回”占用20个字节
作用:根据该数据可以定位每个汉字在字形描述数组中的偏移 - 成员adv_w :
表示字符宽度,在此宽度之后绘制下一个字形。
因为选用的16*16的字体(256个像素点),所以绘制完256个像素点之后,就是绘制下一个字形的起始像素点 - 成员box_w 和box_h
字模宽度和字模高度
cmaps结构体
cmaps收集 unicode 列表和字符偏移
unicode 列表
static const uint16_t unicode_list_0[] = {
0x0, 0x1663, 0x400c, 0x41f0
};
/* U+56DE “回” /、/ U+6D41 “流” /、/ U+96EA “雪” /、/ U+98CE “风” */
“回”的Unicode编码最小 0x56DE =22238
“流”的Unicode编码最小 0x6D41 =27969
“雪”的Unicode编码最小 0x96EA =38634
“风”的Unicode编码最大0x98CE =39118
unicode_list_0数组存放Unicode相对于首字符Unicode的偏移
- unicode_list_0[0] =0x0, 表示“回”的Unicode相对于“回”偏移为0
- unicode_list_0[1] =0x1663, 表示“流”的Unicode相对于“回”偏移为0x1663
- unicode_list_0[2] =0x400c, 表示“雪”的Unicode相对于“回”偏移为0x400c
- unicode_list_0[3] =0x41f0, 表示“风”的Unicode相对于“回”偏移为0x41f0
结构体lv_font_fmt_txt_cmap_t cmaps 成员分析
static const lv_font_fmt_txt_cmap_t cmaps[] =
{
{
.range_start = 22238,
.range_length = 16881,
.glyph_id_start = 1,
.unicode_list = unicode_list_0,
.glyph_id_ofs_list = NULL,
.list_length = 4,
.type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
}
};
-
成员uint32_t range_start
此范围的第一个Unicode字符,因为 “回”是第一个字符,所以.range_start = 22238, -
成员uint16_t range_length
与此范围相关的Unicode字符数。
“回”是第一个字符22238 , “风”是最后一个字符39118 ,最后一个Unicode字符= range_start + range_length - 1
所以.range_length = 39118 - 22238 +1 = 16881; -
成员uint16_t glyph_id_start
这个范围的第一个字形ID (’ glyph_dsc '的数组索引),因为glyph_dsc 数组索引保留(id = 0 reserved),所以glyph_id_start从1开始。 -
成员uint16_t *unicode_list
指针指向unicode_list_0。 -
成员void * glyph_id_ofs_list
-
成员uint16_t list_length
’ unicode_list ‘和/或’ glyph_id_ofs_list '的长度 -
成员lv_font_fmt_txt_cmap_type_t type
这个字符映射的类型
font_dsc结构体
lv_font_fmt_txt_dsc_t font_dsc; //字体描述,用于将上面的信息收集打包
//描述存储字体的附加数据
#if LV_VERSION_CHECK(8, 0, 0)
/*存储所有自定义的字体数据*/
static lv_font_fmt_txt_glyph_cache_t cache;
static const lv_font_fmt_txt_dsc_t font_dsc = {
#else
static lv_font_fmt_txt_dsc_t font_dsc = {
#endif
.glyph_bitmap = glyph_bitmap, //所有字形的位图
.glyph_dsc = glyph_dsc, //描述符号
.cmaps = cmaps, //将字形映射到Unicode字符。' lv_font_cmap_fmt_txt_t '变量的数组
//存储字距调整值
.kern_dsc = NULL, //可以是' lv_font_fmt_txt_kern_pair_t *或' lv_font_kern_classes_fmt_txt_t * '根据“kern_classes”
.kern_scale = 0, //按12.4格式缩放kern值
.cmap_num = 1, //cmap表的个数
.bpp = 1, //像素深度bpp: 1, 2, 4
.kern_classes = 0, //“kern dsc”类型
.bitmap_format = 0, //位图的存储格式为' lv_font_fmt_txt_bitmap_format_t '
#if LV_VERSION_CHECK(8, 0, 0)
.cache = &cache //缓存最后一个字母和字形id
#endif
};
字库ch_word结构体
/*初始化公共通用字体描述符*/
#if LV_VERSION_CHECK(8, 0, 0)
const lv_font_t ch_word = {
#else
lv_font_t ch_word = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*从自定义字体数据中获取字形数据的函数指针*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*从自定义字体数据中获取字形位图的函数指针*/
.line_height = 17, /*字体所需的最大行高*/
.base_line = 3, /*从底线开始测量基线*/
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
.subpx = LV_FONT_SUBPX_NONE, /*位图可以放大3倍以实现亚像素渲染*/
#endif
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
.underline_position = -4, /*下划线顶部与基线之间的距离(< 0表示基线以下)*/
.underline_thickness = 1, /*下划线的厚度*/
#endif
.dsc = &font_dsc /*自定义字体数据。将通过' get_glyph_bitmap/dsc '访问*/
};
字库的使用
字库使用之前需要声明
LV_FONT_DECLARE(ch_word); //声明ch_word字体
static lv_style_t style;
lv_style_set_text_font(&style,&ch_word); //设置样式字体