字体

在 LVGL 中,字体是用于渲染单个字母(字形)图像的位图和其他信息的集合。 字体存储在一个 lv_font_t 变量中,可以通过样式的 text_font 字段进行设置。例如:

lv_style_set_text_font(&my_style, &lv_font_montserrat_28);  /*设置一个更大的字体*/

字体具有一个 bpp(每像素位数) 属性。它表示用于描述字体中一个像素的位数。存储在像素中的值决定了像素的不透明度。 通过更高的 bpp,字母的边缘可以更加平滑。可能的 bpp 值为 1、2、4 和 8(值越高质量越好)。

bpp 属性还会影响存储字体所需的内存量。例如,bpp = 4 的字体比 bpp = 1 的字体大约需要四倍的内存。

Unicode 支持

LVGL 支持 UTF-8 编码的 Unicode 字符。 需要将编辑器配置为将代码/文本保存为 UTF-8(通常这是默认设置),并确保在 lv_conf.h 中将 LV_TXT_ENC 设置为 LV_TXT_ENC_UTF8。(这是默认值)

测试方法如下:

lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);
lv_label_set_text(label1, LV_SYMBOL_OK);

如果一切正常,应显示一个 ✓ 字符。

内置字体

有几种不同大小的内置字体,可以在 lv_conf.h 中通过 LV_FONT_... 定义启用。

普通字体

包含所有 ASCII 字符、度数符号 (U+00B0)、项目符号 (U+2022) 和内置符号(见下文)。

  • LV_FONT_MONTSERRAT_12 12 px 字体

  • LV_FONT_MONTSERRAT_14 14 px 字体

  • LV_FONT_MONTSERRAT_16 16 px 字体

  • LV_FONT_MONTSERRAT_18 18 px 字体

  • LV_FONT_MONTSERRAT_20 20 px 字体

  • LV_FONT_MONTSERRAT_22 22 px 字体

  • LV_FONT_MONTSERRAT_24 24 px 字体

  • LV_FONT_MONTSERRAT_26 26 px 字体

  • LV_FONT_MONTSERRAT_28 28 px 字体

  • LV_FONT_MONTSERRAT_30 30 px 字体

  • LV_FONT_MONTSERRAT_32 32 px 字体

  • LV_FONT_MONTSERRAT_34 34 px 字体

  • LV_FONT_MONTSERRAT_36 36 px 字体

  • LV_FONT_MONTSERRAT_38 38 px 字体

  • LV_FONT_MONTSERRAT_40 40 px 字体

  • LV_FONT_MONTSERRAT_42 42 px 字体

  • LV_FONT_MONTSERRAT_44 44 px 字体

  • LV_FONT_MONTSERRAT_46 46 px 字体

  • LV_FONT_MONTSERRAT_48 48 px 字体

特殊字体

  • LV_FONT_MONTSERRAT_12_SUBPX 与普通 12 px 字体相同,但具有子像素渲染

  • LV_FONT_MONTSERRAT_28_COMPRESSED 与普通 28 px 字体相同,但以 3 bpp 存储为压缩字体

  • LV_FONT_DEJAVU_16_PERSIAN_HEBREW 16 px 字体,具有普通范围 + 希伯来语、阿拉伯语、波斯语字母及其所有形式

  • LV_FONT_SIMSUN_16_CJK16 px 字体,具有普通范围加上 1000 个最常见的 CJK 偏旁部首

  • LV_FONT_UNSCII_8 8 px 像素完美字体,仅包含 ASCII 字符

  • LV_FONT_UNSCII_16 16 px 像素完美字体,仅包含 ASCII 字符

内置字体是具有类似 lv_font_montserrat_16 名称的 全局变量,表示 16 px 高度字体。要在样式中使用它们,只需像上面显示的那样添加指向字体变量的指针。

bpp = 4 的内置字体包含 ASCII 字符,并使用 Montserrat 字体。

除了 ASCII 范围外,还从 FontAwesome 字体中添加了以下符号到内置字体中。

../_images/symbols.png

符号可以单独使用:

lv_label_set_text(my_label, LV_SYMBOL_OK);

或者与字符串一起使用(编译时字符串连接):

lv_label_set_text(my_label, LV_SYMBOL_OK "Apply");

或者多个符号一起使用:

lv_label_set_text(my_label, LV_SYMBOL_OK LV_SYMBOL_WIFI LV_SYMBOL_PLAY);

特殊功能

双向支持

大多数语言使用从左到右(简称 LTR)的书写方向,但某些语言(如希伯来语、波斯语或阿拉伯语)使用从右到左(简称 RTL)的方向。

LVGL 不仅支持 RTL 文本,还支持混合(即双向,BiDi)文本渲染。一些示例:

../_images/bidi.png

通过在 lv_conf.h 中启用 LV_USE_BIDI 来支持 BiDi。

所有文本都有一个基本方向(LTR 或 RTL),它决定了一些渲染规则和文本的默认对齐方式(左对齐或右对齐)。 然而,在 LVGL 中,基本方向不仅适用于标签。它是一个可以为每个对象设置的通用属性。 如果未设置,则会从父对象继承。 这意味着只需设置屏幕的基本方向,每个对象都会继承它。

可以通过在 lv_conf.h 中设置 LV_BIDI_BASE_DIR_DEF 来设置屏幕的默认基本方向,其他对象从其父对象继承基本方向。

要设置对象的基本方向,请使用 lv_obj_set_base_dir(obj, base_dir)。可能的基本方向有:

  • LV_BIDI_DIR_LTR:从左到右的基本方向

  • LV_BIDI_DIR_RTL:从右到左的基本方向

  • LV_BIDI_DIR_AUTO:自动检测基本方向

  • LV_BIDI_DIR_INHERIT:从父对象继承基本方向(或非屏幕对象的默认值)

以下列表总结了 RTL 基本方向对对象的影响:

  • 默认在右侧创建对象

  • lv_tabview:从右到左显示选项卡

  • lv_checkbox:在右侧显示复选框

  • lv_btnmatrix:从右到左显示按钮

  • lv_list:在右侧显示图标

  • lv_dropdown:将选项对齐到右侧

  • lv_tablelv_btnmatrixlv_keyboardlv_tabviewlv_dropdownlv_roller 中的文本经过“双向处理”以正确显示

阿拉伯语和波斯语支持

显示阿拉伯语和波斯语字符有一些特殊规则:字符的形式取决于其在文本中的位置。 当字符是孤立的、在开头、中间或结尾位置时,需要使用不同形式的同一字母。此外,还需要考虑一些连接规则。

如果启用了 LV_USE_ARABIC_PERSIAN_CHARS,LVGL 支持这些规则。

然而,有一些限制:

  • 仅支持显示文本(例如标签上的文本),文本输入(例如文本区域)不支持此功能。

  • 静态文本(即 const)不会被处理。例如,通过 lv_label_set_text() 设置的文本将被“阿拉伯语处理”,但通过 lv_label_set_text_static() 设置的文本不会。

  • 文本获取函数(例如 lv_label_get_text())将返回处理后的文本。

子像素渲染

子像素渲染通过在红、绿和蓝通道上渲染抗锯齿边缘而不是像素级粒度来使水平分辨率提高三倍。这利用了每个像素的物理颜色通道的位置,从而提高了字母抗锯齿的质量。了解更多 这里

对于子像素渲染,字体需要使用特殊设置生成:

  • 在在线转换器中勾选 Subpixel 选项

  • 在命令行工具中使用 --lcd 标志。请注意,生成的字体需要大约三倍的内存。

子像素渲染仅在像素的颜色通道具有水平布局时有效。即 R、G、B 通道彼此相邻,而不是彼此之上。 颜色通道的顺序还需要与库设置匹配。默认情况下,LVGL 假定 RGB 顺序,但可以通过在 lv_conf.h 中设置 LV_SUBPX_BGR  1 来交换。

压缩字体

字体的位图可以通过以下方式压缩:

  • 在在线转换器中勾选 Compressed 复选框

  • 不传递离线转换器的 --no-compress 标志(默认情况下会应用压缩)

对于较大的字体和更高的 bpp,压缩更有效。然而,渲染压缩字体大约慢 30%。 因此,建议仅压缩用户界面中最大的字体,因为

  • 它们需要最多的内存

  • 它们可以更好地压缩

  • 并且它们可能比中等大小的字体使用频率更低,因此性能成本较小。

添加新字体

有几种方法可以将新字体添加到项目中:

  1. 最简单的方法是使用在线字体转换器。只需设置参数,点击 Convert 按钮,将字体复制到项目中并使用它。请务必仔细阅读该网站上提供的步骤,否则在转换时会出错。

  2. 使用离线字体转换器。(需要安装 Node.js)

  3. 如果您想创建类似于内置字体(Montserrat 字体和符号)的内容,但具有不同的大小和/或范围,可以使用 lvgl/scripts/built_in_font 文件夹中的 built_in_font_gen.py 脚本。 (这需要安装 Python 和 lv_font_conv

要在文件中声明字体,请使用 LV_FONT_DECLARE(my_font_name)

要使字体全局可用(如内置字体),请将它们添加到 lv_conf.h 中的 LV_FONT_CUSTOM_DECLARE

添加新符号

内置符号是从 FontAwesome 字体创建的。

  1. https://fontawesome.com 上搜索符号。例如 USB 符号。复制其 Unicode ID,例如 0xf287

  2. 打开在线字体转换器。添加 FontAwesome.woff

  3. 设置参数,例如名称、大小、BPP。您将在代码中使用此名称来声明和使用字体。

  4. 将符号的 Unicode ID 添加到范围字段。例如,USB 符号的 0xf287。可以用 , 枚举更多符号。

  5. 转换字体并将生成的源代码复制到项目中。确保编译字体的 .c 文件。

  6. 使用 extern lv_font_t my_font_name; 声明字体,或者简单地使用 LV_FONT_DECLARE(my_font_name);

使用符号

  1. 将 Unicode 值转换为 UTF8,例如在此网站上。对于 0xf287Hex UTF-8 bytesEF 8A 87

  2. 从 UTF8 值创建一个 define 字符串:#define MY_USB_SYMBOL "\xEF\x8A\x87"

  3. 创建一个标签并设置文本。例如 lv_label_set_text(label, MY_USB_SYMBOL)

注意 - lv_label_set_text(label, MY_USB_SYMBOL)style.text.font 属性中定义的字体中搜索此符号。要使用符号,您可能需要更改它。例如 style.text.font = my_font_name

运行时加载字体

可以使用 lv_font_load 从文件加载字体。字体需要具有特殊的二进制格式。(不是 TTF 或 WOFF)。 使用带有 --format bin 选项的 lv_font_conv 生成 LVGL 兼容的字体文件。

请注意,要加载字体,需要启用 LVGL 的文件系统 并添加驱动程序。

示例

lv_font_t * my_font;
my_font = lv_font_load(X/path/to/my_font.bin);

/*使用字体*/

/*如果不再需要字体,请释放它*/
lv_font_free(my_font);

添加新的字体引擎

LVGL 的字体接口设计得非常灵活,但即便如此,您仍然可以在 LVGL 的内部字体引擎中添加自己的字体引擎。 例如,您可以使用 FreeType 从 TTF 字体实时渲染字形,或者使用外部闪存存储字体的位图,并在库需要时读取它们。

一个可用的 FreeType 示例可以在 lv_freetype 仓库中找到。

为此,需要创建一个自定义的 lv_font_t 变量:

/*描述字体的属性*/
lv_font_t my_font;
my_font.get_glyph_dsc = my_get_glyph_dsc_cb;        /*设置回调以获取字形信息*/
my_font.get_glyph_bitmap = my_get_glyph_bitmap_cb;  /*设置回调以获取字形的位图*/
my_font.line_height = height;                       /*任何文本适合的实际行高*/
my_font.base_line = base_line;                      /*从 line_height 顶部测量的基线*/
my_font.dsc = something_required;                   /*在此处存储任何实现特定的数据*/
my_font.user_data = user_data;                      /*可选的一些额外用户数据*/

...

/* 获取 `unicode_letter` 在 `font` 字体中的字形信息。
 * 将结果存储在 `dsc_out` 中。
 * 下一个字母 (`unicode_letter_next`) 可能用于计算此字形所需的宽度(字距调整)
 */
bool my_get_glyph_dsc_cb(const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, uint32_t unicode_letter, uint32_t unicode_letter_next)
{
    /*您的代码*/

    /* 存储结果。
     * 例如 ...
     */
    dsc_out->adv_w = 12;        /*字形所需的水平空间 [px]*/
    dsc_out->box_h = 8;         /*位图的高度 [px]*/
    dsc_out->box_w = 6;         /*位图的宽度 [px]*/
    dsc_out->ofs_x = 0;         /*位图的 X 偏移量 [pf]*/
    dsc_out->ofs_y = 3;         /*从 as 线测量的位图的 Y 偏移量*/
    dsc_out->bpp   = 2;         /*每像素位数:1/2/4/8*/

    return true;                /*true:找到字形;false:未找到字形*/
}


/* 从 `font` 获取 `unicode_letter` 的位图。 */
const uint8_t * my_get_glyph_bitmap_cb(const lv_font_t * font, uint32_t unicode_letter)
{
    /* 您的代码 */

    /* 位图应为连续的位流,其中
     * 每个像素由 `bpp` 位表示 */

    return bitmap;    /*如果未找到,则返回 NULL*/
}

使用字体回退

您可以在 lv_font_t 中指定 fallback 以提供字体回退。当字体无法找到字母的字形时,它将尝试让 fallback 中的字体处理。

fallback 可以链接,因此它会尝试解决,直到没有设置 fallback

/* Roboto 字体不支持 CJK 字形 */
lv_font_t *roboto = my_font_load_function();
/* Droid Sans Fallback 有更多的字形,但其字体样式看起来不如 Roboto */
lv_font_t *droid_sans_fallback = my_font_load_function();
/* 因此,我们可以为支持的字符显示 Roboto,同时具有更广泛的字符集支持 */
roboto->fallback = droid_sans_fallback;