前言 不知道多久前看到了MIUI更新了”动态字体系统 “功能,不过当时没太在意(毕竟我用的也不是MIUI,哈哈哈,不过确实挺方便的),演示视频里面展示了随意调节字体粗细的功能,后来知道这个参数叫做字重(zhong第四声).
然后又有一次去Material.io 时候,看见了首页的Material You概念视频 手机解锁以后,系统时间的字体由细变粗.和上面MIUI动态字体系统调节字重的时候效果十分相似.
老早就听说了LVGL的大名,但是一直没有行动起来.
后来看见稚晖君 的Peak 和FASTSHIFT 的X-Track , 羡慕极了. 于是决定这次一定要试试LVGL,看看用起来到底是啥感觉.
之前自己搁那瞎捣鼓过一阵子的GUI,结果嘛,结果就是就弃坑了.
这次打算直接上LVGL这种成熟的GUI方案了,而恰好LVGL是支持FreeType的,借助FreeType就可以相对轻松的实现上面的字重动画.
什么是FreeType FreeType 库是一个完全免费(开源)的、高质量的且可移植的字体引擎
目前主流的屏幕均都是由像素点构成,不能直接显示矢量图,所以就需要字体引擎将字体的矢量数据转换为位图数据,然后在屏幕上显示 “点灯” 出来.
Variable Font又是什么 储存轮廓变化数据的可变字体,在初始字形轮廓的基础上自动生成丰富的变化造型,使用户可以自由调整文字的外观。 枯燥的描述不如直接上手体验一下,V-Fonts 是一个在线体验可变字体的网站,拖动滑块就可以修改字体在对应轴上的值,即上述的自由调整文字的外观.
准备工作 准备工作的准备工作之先把Visual Studio装了再说
下载LVGL模拟器 本打算再开一篇文章说模拟器安装的,重装的时候才发现原来一键就能安装.
(可能需要科学上网) 直接复制git命令就完事了,下完点击LVGL.Simulator.sln 直接启动.
1 git clone --recurse-submodules https://github.com/lvgl/lv_sim_visual_studio.git
启动以后看到lvgl自带的**lv_demo_widgets()**运行效果.
为LVGL配置FreeType 得益于lv_sim_visual_studio 的完整性,刚才git clone –recurse-submodules 时候freetype被一并下载了. 所以现在暂时不需要额外配置什么内容,但是在其他情况下还是需要手动的配置一下LVGL的FreeType支持.(比如在板子上跑freetype的时候)
LVGL内置的FreeType Demo 将LVGL.Simulator.cpp内
1 2 3 4 5 6 7 8 9 10 11 12 13 lv_demo_widgets ();
修改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 lv_example_freetype_1 ();
如果一切顺利,点击运行.会看见下图
下面则是lv_example_freetype_1的内容,我已经为他添加了详luo细suo的中文注释
lv_example_freetype_1.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 void lv_example_freetype_1 (void ) { static lv_ft_info_t info; info.name = "./lvgl/examples/libs/freetype/arial.ttf" ; info.weight = 24 ; info.style = FT_FONT_STYLE_NORMAL; info.mem = NULL ; if (!lv_ft_font_init(&info)) { LV_LOG_ERROR("create failed." ); } static lv_style_t style; lv_style_init(&style); lv_style_set_text_font(&style, info.font); lv_style_set_text_align(&style, LV_TEXT_ALIGN_CENTER); lv_obj_t * label = lv_label_create(lv_scr_act()); lv_obj_add_style(label, &style, 0 ); lv_label_set_text(label, "Hello world\nI'm a font created with FreeType" ); lv_obj_center(label); }
迫于arial.ttf不是可变字体,所以我们趁此机会修改一下lv_example_freetype_1中用到的字体,熟悉下使用其他字体的方式.
**Archivo-VF.ttf **是一款Open Font License 的字体点击这里可以下载Archivo-VF.ttf
将其复制到arial.ttf同级目录,然后修改
1 info.name = "./lvgl/examples/libs/freetype/arial.ttf" ;
为
1 info.name = "./lvgl/examples/libs/freetype/Archivo-VF.ttf" ;
运行结果如下
字体在线展示 点击这里在线体验ArchivoVF字体的可变属性
ArchivoVF的可变属性如下
最小字重100
最大字重900
最小宽度62
最大宽度125
拓展LVGL的FreeType支持 由于LVGL目前版本(8.10-dev)还没有内置可变字体参数控制,所以需要我们手动的为其添加这部分内容.
添加支持前,速览下修改可变参数的方式及其啰嗦的注释 (需要使用可变字体进行操作,否则无法正常运行(包括不限于崩溃以及崩溃),测试用的字体文件为Archivo-VF,可在前面一节末尾获取到)
foo.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 FT_Error error; FT_MM_Var *amaster = nullptr; error = FT_Get_MM_Var(face, &amaster); printf ("error %d\n" , error);printf ("amaster->axis=%d\n" , (amaster)->num_axis);printf ("amaster->axis->strid=%u\n" , amaster->axis->strid);printf ("amaster->axis->tag=%lu\n" , amaster->axis->tag);printf ("amaster->axis->name=%s\n" , amaster->axis->name);printf ("amaster->axis->def=%ld\n" , amaster->axis->def);printf ("amaster->axis->maximum=%ld\n" , amaster->axis->maximum);printf ("amaster->axis->minimum=%ld\n" , amaster->axis->minimum);printf ("amaster->num_namedstyles=%d\n" , amaster->num_namedstyles); printf ("amaster->namedstyle->coords=%ld\n" , (signed long ) amaster->namedstyle->coords);FT_Fixed coords[2 ] = { (amaster->axis)->maximum ,(amaster->axis + 1 )->maximum}; printf ("amaster->(axis)->maximum=%ld\n" , (amaster->axis)->maximum /65536 ); printf ("amaster->(axis+1)->maximum=%ld\n" , (amaster->axis+1 )->maximum /65536 ); error = FT_Set_Var_Design_Coordinates(face, 2 , coords); printf ("FT_Set_Var_Design_Coordinates error %d\n" , error);printf ("face num_faces: %ld\n" , face->num_faces);FT_Done_MM_Var(m_library, amaster);
由于目前版本的lvgl(8.1.0-dev)还没有提供相应的API,所以需要手动修改一些地方. 打开lv_freetype.h,修改lv_ft_info_t内容,如下所示
lv_freetype.h 1 2 3 4 5 6 7 typedef struct { const char * name; lv_font_t * font; uint16_t weight; uint16_t height; uint16_t style; } lv_ft_info_t ;
请注意:
lv_ft_info_t本身就有一个名为weight的uint16_t属性,但是后续被用到了字体的宽高尺寸上,所以现在我们需要添加height取代原来的weight,让weight成为名副其实的weight
推荐先把weight重命名为height,再把后续用到weight的地方改成height,当lv_example_freetype_1又能够成功运行的时候再添加”uint16_t weight;”
lv_font_fmt_ft_dsc_t也要添加字重(weight) 打开lv_freetype.c,修改lv_font_fmt_ft_dsc_t内容,如下所示
lv_freetype.c 1 2 3 4 5 6 7 8 9 10 11 typedef struct {#if LV_FREETYPE_CACHE_SIZE >= 0 void *face_id; #else FT_Size size; #endif lv_font_t *font; uint16_t style; uint16_t height; uint16_t weight; } lv_font_fmt_ft_dsc_t ;
这里直接添加”uint16_t weight;”即可
将修改字重的代码添加到lv_freetype.c内get_glyph_dsc_cb_cache中
lv_freetype.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 static bool get_glyph_dsc_cb_cache (const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, uint32_t unicode_letter, uint32_t unicode_letter_next) { LV_UNUSED(unicode_letter_next); if (unicode_letter < 0x20 ) { dsc_out->adv_w = 0 ; dsc_out->box_h = 0 ; dsc_out->box_w = 0 ; dsc_out->ofs_x = 0 ; dsc_out->ofs_y = 0 ; dsc_out->bpp = 0 ; return true ; } lv_font_fmt_ft_dsc_t * dsc = (lv_font_fmt_ft_dsc_t *)(font->dsc); FTC_FaceID face_id = (FTC_FaceID)dsc->face_id; FT_Size face_size; struct FTC_ScalerRec_ scaler ; scaler.face_id = face_id; scaler.width = dsc->height; scaler.height = dsc->height; scaler.pixel = 1 ; if (FTC_Manager_LookupSize(cache_manager, &scaler, &face_size) != 0 ) { return false ; } FT_Face face = face_size->face; FT_MM_Var* amaster = NULL ; FT_Error err = FT_Get_MM_Var(face, &amaster); if (err) { LV_LOG_ERROR("FT_Get_MM_Var error:%d\n" , err); return err; } FT_Fixed coords[1 ] = { dsc->weight<<16 }; err = FT_Set_Var_Design_Coordinates(face, 1 , coords); if (err) { LV_LOG_ERROR("FT_Set_Var_Design_Coordinates error:%d\n" , err); return err; } FT_Done_MM_Var(library, amaster); FT_UInt charmap_index = FT_Get_Charmap_Index(face->charmap); FT_UInt glyph_index = FTC_CMapCache_Lookup(cmap_cache, face_id, charmap_index, unicode_letter); dsc_out->is_placeholder = glyph_index == 0 ; if (dsc->style & FT_FONT_STYLE_ITALIC) { FT_Matrix italic_matrix; italic_matrix.xx = 1 << 16 ; italic_matrix.xy = 0x5800 ; italic_matrix.yx = 0 ; italic_matrix.yy = 1 << 16 ; FT_Set_Transform(face, &italic_matrix, NULL ); } if (dsc->style & FT_FONT_STYLE_BOLD) { current_face = face; if (!get_bold_glyph(font, face, glyph_index, dsc_out)) { current_face = NULL ; return false ; } goto end; } FTC_ImageTypeRec desc_type; desc_type.face_id = face_id; desc_type.flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL; desc_type.height = dsc->height; desc_type.width = dsc->height; #if LV_FREETYPE_SBIT_CACHE FT_Error error = FTC_SBitCache_Lookup(sbit_cache, &desc_type, glyph_index, &sbit, NULL ); if (error) { LV_LOG_ERROR("SBitCache_Lookup error" ); return false ; } dsc_out->adv_w = sbit->xadvance; dsc_out->box_h = sbit->height; dsc_out->box_w = sbit->width; dsc_out->ofs_x = sbit->left; dsc_out->ofs_y = sbit->top - sbit->height; dsc_out->bpp = 8 ; #else FT_Error error = FTC_ImageCache_Lookup(image_cache, &desc_type, glyph_index, &image_glyph, NULL ); if (error) { LV_LOG_ERROR("ImageCache_Lookup error" ); return false ; } if (image_glyph->format != FT_GLYPH_FORMAT_BITMAP) { LV_LOG_ERROR("Glyph_To_Bitmap error" ); return false ; } FT_BitmapGlyph glyph_bitmap = (FT_BitmapGlyph)image_glyph; dsc_out->adv_w = (glyph_bitmap->root.advance.x >> 16 ); dsc_out->box_h = glyph_bitmap->bitmap.rows; dsc_out->box_w = glyph_bitmap->bitmap.width; dsc_out->ofs_x = glyph_bitmap->left; dsc_out->ofs_y = glyph_bitmap->top - glyph_bitmap->bitmap.rows; dsc_out->bpp = 8 ; #endif end: if ((dsc->style & FT_FONT_STYLE_ITALIC) && (unicode_letter_next == '\0' )) { dsc_out->adv_w = dsc_out->box_w + dsc_out->ofs_x; } return true ; }
初始化的时候,别忘了lv_ft_font_init_cache时候传递字重信息
lv_freetype.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 static bool lv_ft_font_init_cache (lv_ft_info_t * info) { lv_font_fmt_ft_dsc_t * dsc = lv_mem_alloc(sizeof (lv_font_fmt_ft_dsc_t )); if (dsc == NULL ) return false ; dsc->font = lv_mem_alloc(sizeof (lv_font_t )); if (dsc->font == NULL ) { lv_mem_free(dsc); return false ; } lv_memset_00(dsc->font, sizeof (lv_font_t )); lv_face_info_t * face_info = NULL ; face_info = lv_mem_alloc(sizeof (lv_face_info_t ) + strlen (info->name) + 1 ); if (face_info == NULL ) { goto Fail; } face_info->mem = info->mem; face_info->size = info->mem_size; face_info->name = ((char *)face_info) + sizeof (lv_face_info_t ); strcpy (face_info->name, info->name); dsc->face_id = face_info; dsc->height = info->height; dsc->weight = info->weight; dsc->style = info->style; FT_Size face_size; struct FTC_ScalerRec_ scaler ; scaler.face_id = (FTC_FaceID)dsc->face_id; scaler.width = info->height; scaler.height = info->height; scaler.pixel = 1 ; FT_Error error = FTC_Manager_LookupSize(cache_manager, &scaler, &face_size); if (error) { lv_mem_free(face_info); LV_LOG_ERROR("Failed to LookupSize" ); goto Fail; } lv_font_t * font = dsc->font; font->dsc = dsc; font->get_glyph_dsc = get_glyph_dsc_cb_cache; font->get_glyph_bitmap = get_glyph_bitmap_cb_cache; font->subpx = LV_FONT_SUBPX_NONE; font->line_height = (face_size->face->size->metrics.height >> 6 ); font->base_line = -(face_size->face->size->metrics.descender >> 6 ); FT_Fixed scale = face_size->face->size->metrics.y_scale; int8_t thickness = FT_MulFix(scale, face_size->face->underline_thickness) >> 6 ; font->underline_position = FT_MulFix(scale, face_size->face->underline_position) >> 6 ; font->underline_thickness = thickness < 1 ? 1 : thickness; info->font = font; return true ; Fail: lv_mem_free(dsc->font); lv_mem_free(dsc); return false ; }
效果预览 不出意外的话,需要修改的地方已经修改完了,现在回到lv_example_freetype_1上来.初始化info的时候把weight也给初始化了.
1 2 // 由于已经知道了要使用的Archivo-VF最大字重900; info.weight = 900 ;
然后运行就可以看到字重900的Archivo-VF
下图是在LVGL模拟器上的运行效果。
下集预告
环境 1 2 3 4 Windows 10 Pro 18363.1556 Microsoft Visual Studio Community 2019 16.6.2 LVGL 8.1.1-dev FreeType 2.11.0
参考资料
附件