想要在屏幕上输出中文字体,最常见的就是使用一些软件把中文字体转换成点阵,然后很多做法是在字体点阵数据里,添加这个字作为搜索索引。
这种方式是最简单的,但是遇到字体数量比较多的时候,就需要花不少时间去遍历。同时因为字体存储的点阵数据是固定长度,大量字体的情况下,会占用比较多的空间。
![[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图1](https://mc.dfrobot.com.cn/data/attachment/forum/202509/25/000015j6554jzzs5y4fxi4.png)
而lvgl的字体存储方式,是比较灵活的。
![[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图2](https://mc.dfrobot.com.cn/data/attachment/forum/202509/25/000205o8r8rrxrez5ucrue.png)
可以看出这种字体点阵的存储方式是更为简单的,就是搜索对应点阵需要先将utf8字符转换成unicode码点值,判断是合法的utf8字符串后,然后再去字符映射表里查找。
查到后就可以获取到字体的描述符。字体描述符如下图所示,使用这种结构,可以微调字体的水平位置和垂直位置,非常灵活。
![[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图3](https://mc.dfrobot.com.cn/data/attachment/forum/202509/25/000756r6aa6gni5lapannv.png)
lvgl使用的这种字体数据结构,灵活性很强,但是这样就有个问题,就是AI这边没办法很好理解这种数据结构,简单的描述不能让AI生成合理的程序。我喂了之前我写好的lvgl字体绘制函数给AI,结果它生成的代码是有问题的。如下图。
![[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图4](https://mc.dfrobot.com.cn/data/attachment/forum/202509/25/001221e8m94yamhm8ay9d6.png)
给出一些提示后,AI还是没办法改出一个满意的函数。比如原本AI优化生成的函数,在绘制中划线-,标点符号,中文一,这些字符的时候,要么写到下一行,要么偏下。
让AI修改后,AI就改成上图的一个情况。
重新修改回我原有的函数,再让AI分析,这回AI明白它到底错在哪里了。
![[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图5](https://mc.dfrobot.com.cn/data/attachment/forum/202509/25/001625rzl5bm2atu55tpwz.png)
使用我自己写的代码,输出是这样的。
![[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图6](https://mc.dfrobot.com.cn/data/attachment/forum/202509/25/002417t3myp00u0w6p3w0w.png)
在使用qwen code编写代码的这一周多时间,我发现,如果想要它帮你写好嵌入式的代码,需要给它喂正确的数据,例如具体算法的数据结构解析(如果它不明白的话),甚至给出对应算法的计算步骤等等。想让它完全帮你完成嵌入式的工作,有点不大现实。
可能目前AI最大的用处,还是帮忙从数据量非常多的东西,抽取出我们想要的内容,例如把屏幕芯片的对应指令从规格书,或者网络上获取下来(建议多方验证)。还有帮忙添加注释,按现有的代码和数据结构、函数风格,快速把其他平台的一些代码转换过来。以及快速的了解一个命令行程序,如果想要获得对应的结果,应该使用什么参数,对应的参数是什么意思。
比如,我想自己生成一个抽取指定字符的中文字体文件,又不清楚怎样去做,可以这样问AI,我有一个XXX字体文件(路径xxx),想生成一个12px,最小化,包含XXXXX字符的字体文件,应该怎样做。
![[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图7](https://mc.dfrobot.com.cn/data/attachment/forum/202509/25/002637ee4bee414qz4cbb6.png)
最后,我觉得lvgl的这种灵活字体形式,还是非常棒的,同时也让我学习到了,如何将字符点阵,正确的输出到屏幕上。自己写对应的函数,也可以很方便的调整,例如可以将英文字符的间距缩小,中文字符的间距设置成普通的就行。效果如下图。
![[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图8](https://mc.dfrobot.com.cn/data/attachment/forum/202509/25/004021r688uj1k8dz3ozju.png)
调整好的代码如下
-
- void print_font(uint8_t x, uint8_t y, const char *str) {
- // 当前行字符的x坐标偏移,相对于当前行的起始位置
- uint8_t current_x = 0;
- // 当前行字符的y坐标偏移,相对于整体显示区域
- // 初始值3作为顶部边距,避免字符贴近顶部边缘
- uint8_t current_y = 3;
- // 字符间距,用于调整字符间的距离
- uint8_t char_spacing = 4;
- // 当前处理的Unicode字符码点
- int32_t unicode_char;
- // 使用中文字体zpix12_cn
- const modify_lv_font_t *font_set = &zpix12_cn;
-
- // 遍历输入字符串的每个UTF-8字符
- while (*str) {
- // 将UTF-8编码的字符转换为Unicode码点
- int len = utf8_to_unicode(&str, &unicode_char);
- if (len > 0) {
- // 遍历字体的字符映射表,查找当前字符的字形描述
- for (int cmap_index = 0; cmap_index < font_set->dsc->cmap_num; cmap_index++) {
- // 获取当前映射表的起始字符码点
- uint16_t cmap_start = font_set->dsc->cmaps[cmap_index].range_start;
- // 检查当前字符是否在该映射表范围内
- if (unicode_char >= cmap_start &&
- (unicode_char <= (cmap_start + font_set->dsc->cmaps[cmap_index].range_length))) {
- lv_font_fmt_txt_glyph_dsc_t glyph_dsc = {0};
-
- // 如果映射表是连续的(没有稀疏映射),使用直接索引查找
- if (font_set->dsc->cmaps[cmap_index].list_length == 0) {
- // 直接通过码点计算字形描述的索引
- uint32_t glyph_dsc_index =
- unicode_char - font_set->dsc->cmaps[cmap_index].range_start +
- font_set->dsc->cmaps[cmap_index].glyph_id_start;
- glyph_dsc = font_set->dsc->glyph_dsc[glyph_dsc_index];
- // 设置英文字符的间距较小
- char_spacing = 6;
- } else {
- // 如果是稀疏映射,遍历Unicode列表查找匹配的字符
- for (int unicode_index = 0;
- unicode_index < font_set->dsc->cmaps[cmap_index].list_length;
- unicode_index++) {
- if (font_set->dsc->cmaps[cmap_index].unicode_list[unicode_index] ==
- (unicode_char - font_set->dsc->cmaps[cmap_index].range_start)) {
- glyph_dsc =
- font_set->dsc->glyph_dsc[unicode_index +
- font_set->dsc->cmaps[cmap_index].glyph_id_start];
- }
- }
- }
-
- // 获取字符的位图数据指针
- const uint8_t *bitmap_ptr = &font_set->dsc->glyph_bitmap[glyph_dsc.bitmap_index];
-
- // 检查是否需要换行(当前字符会超出屏幕边界)
- if ((current_x + glyph_dsc.box_w + glyph_dsc.ofs_x) > 250) {
- current_x = 0; // 重置x坐标到行首
- current_y += font_set->line_height + 4; // 移动到下一行,+4为行间距
- }
-
- // 遍历字符位图,逐像素绘制字符
- for (int _box_h = 0; _box_h < glyph_dsc.box_h; _box_h++) {
- for (int _box_w = 0; _box_w < glyph_dsc.box_w; _box_w++) {
- bool bit = get_bit(bitmap_ptr, _box_h * glyph_dsc.box_w + _box_w);
-
- // 计算并设置屏幕上的像素点
- st7305_set_pixel(
- // X坐标:起始x + 字符内部的x偏移 + 字符的x方向偏移 + 当前行的x偏移
- x + _box_w + glyph_dsc.ofs_x + current_x,
- // Y坐标:起始y + 字符内部的y偏移 + 当前行的y偏移 + 字符垂直对齐偏移
- // 核心公式 (font_set->line_height - glyph_dsc.box_h - glyph_dsc.ofs_y)
- // 实现字符按基线对齐,这是解决"-"和"一"字符偏移问题的关键
- y + _box_h + current_y + (font_set->line_height - glyph_dsc.box_h - glyph_dsc.ofs_y),
- bit // 像素是否点亮
- );
- }
- }
-
- // 特殊字符处理:空格和回车
- if (unicode_char == ' ') {
- // 空格字符:仅按ofs_x调整位置
- current_x += glyph_dsc.ofs_x;
- } else if (unicode_char == '\r') {
- // 回车符:重置到行首并换行
- current_x = 0;
- current_y += font_set->line_height + 4;
- }
-
- // 更新x坐标到下一个字符的位置
- // 位置增量 = 字符宽度 + 字间距(这里使用line_height/char_spacing作为字间距)
- current_x += glyph_dsc.box_w + font_set->line_height / char_spacing;
- break; // 找到字符后退出映射表循环
- }
- }
- }
- }
- }
复制代码
|