121浏览
查看: 121|回复: 0

[活动] [ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体

[复制链接]
想要在屏幕上输出中文字体,最常见的就是使用一些软件把中文字体转换成点阵,然后很多做法是在字体点阵数据里,添加这个字作为搜索索引。
这种方式是最简单的,但是遇到字体数量比较多的时候,就需要花不少时间去遍历。同时因为字体存储的点阵数据是固定长度,大量字体的情况下,会占用比较多的空间。

[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图1

而lvgl的字体存储方式,是比较灵活的。

[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图2



可以看出这种字体点阵的存储方式是更为简单的,就是搜索对应点阵需要先将utf8字符转换成unicode码点值,判断是合法的utf8字符串后,然后再去字符映射表里查找。

查到后就可以获取到字体的描述符。字体描述符如下图所示,使用这种结构,可以微调字体的水平位置和垂直位置,非常灵活。

[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图3

lvgl使用的这种字体数据结构,灵活性很强,但是这样就有个问题,就是AI这边没办法很好理解这种数据结构,简单的描述不能让AI生成合理的程序。我喂了之前我写好的lvgl字体绘制函数给AI,结果它生成的代码是有问题的。如下图。
[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图4

给出一些提示后,AI还是没办法改出一个满意的函数。比如原本AI优化生成的函数,在绘制中划线-,标点符号,中文一,这些字符的时候,要么写到下一行,要么偏下。
让AI修改后,AI就改成上图的一个情况。
重新修改回我原有的函数,再让AI分析,这回AI明白它到底错在哪里了。
[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图5

使用我自己写的代码,输出是这样的。
[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图6

在使用qwen code编写代码的这一周多时间,我发现,如果想要它帮你写好嵌入式的代码,需要给它喂正确的数据,例如具体算法的数据结构解析(如果它不明白的话),甚至给出对应算法的计算步骤等等。想让它完全帮你完成嵌入式的工作,有点不大现实

可能目前AI最大的用处,还是帮忙从数据量非常多的东西,抽取出我们想要的内容,例如把屏幕芯片的对应指令从规格书,或者网络上获取下来(建议多方验证)。还有帮忙添加注释,按现有的代码和数据结构、函数风格,快速把其他平台的一些代码转换过来。以及快速的了解一个命令行程序,如果想要获得对应的结果,应该使用什么参数,对应的参数是什么意思。

比如,我想自己生成一个抽取指定字符的中文字体文件,又不清楚怎样去做,可以这样问AI,我有一个XXX字体文件(路径xxx),想生成一个12px,最小化,包含XXXXX字符的字体文件,应该怎样做。
[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图7


最后,我觉得lvgl的这种灵活字体形式,还是非常棒的,同时也让我学习到了,如何将字符点阵,正确的输出到屏幕上。自己写对应的函数,也可以很方便的调整,例如可以将英文字符的间距缩小,中文字符的间距设置成普通的就行。效果如下图。

[ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体图8


调整好的代码如下
  1. void print_font(uint8_t x, uint8_t y, const char *str) {
  2.   // 当前行字符的x坐标偏移,相对于当前行的起始位置
  3.   uint8_t current_x = 0;
  4.   // 当前行字符的y坐标偏移,相对于整体显示区域
  5.   // 初始值3作为顶部边距,避免字符贴近顶部边缘
  6.   uint8_t current_y = 3;
  7.   // 字符间距,用于调整字符间的距离
  8.   uint8_t char_spacing = 4;
  9.   // 当前处理的Unicode字符码点
  10.   int32_t unicode_char;
  11.   // 使用中文字体zpix12_cn
  12.   const modify_lv_font_t *font_set = &zpix12_cn;
  13.   // 遍历输入字符串的每个UTF-8字符
  14.   while (*str) {
  15.     // 将UTF-8编码的字符转换为Unicode码点
  16.     int len = utf8_to_unicode(&str, &unicode_char);
  17.     if (len > 0) {
  18.       // 遍历字体的字符映射表,查找当前字符的字形描述
  19.       for (int cmap_index = 0; cmap_index < font_set->dsc->cmap_num; cmap_index++) {
  20.         // 获取当前映射表的起始字符码点
  21.         uint16_t cmap_start = font_set->dsc->cmaps[cmap_index].range_start;
  22.         // 检查当前字符是否在该映射表范围内
  23.         if (unicode_char >= cmap_start &&
  24.             (unicode_char <= (cmap_start + font_set->dsc->cmaps[cmap_index].range_length))) {
  25.           lv_font_fmt_txt_glyph_dsc_t glyph_dsc = {0};
  26.          
  27.           // 如果映射表是连续的(没有稀疏映射),使用直接索引查找
  28.           if (font_set->dsc->cmaps[cmap_index].list_length == 0) {
  29.             // 直接通过码点计算字形描述的索引
  30.             uint32_t glyph_dsc_index =
  31.                 unicode_char - font_set->dsc->cmaps[cmap_index].range_start +
  32.                 font_set->dsc->cmaps[cmap_index].glyph_id_start;
  33.             glyph_dsc = font_set->dsc->glyph_dsc[glyph_dsc_index];
  34.             // 设置英文字符的间距较小
  35.             char_spacing = 6;
  36.           } else {
  37.             // 如果是稀疏映射,遍历Unicode列表查找匹配的字符
  38.             for (int unicode_index = 0;
  39.                  unicode_index < font_set->dsc->cmaps[cmap_index].list_length;
  40.                  unicode_index++) {
  41.               if (font_set->dsc->cmaps[cmap_index].unicode_list[unicode_index] ==
  42.                   (unicode_char - font_set->dsc->cmaps[cmap_index].range_start)) {
  43.                 glyph_dsc =
  44.                     font_set->dsc->glyph_dsc[unicode_index +
  45.                                              font_set->dsc->cmaps[cmap_index].glyph_id_start];
  46.               }
  47.             }
  48.           }
  49.          
  50.           // 获取字符的位图数据指针
  51.           const uint8_t *bitmap_ptr = &font_set->dsc->glyph_bitmap[glyph_dsc.bitmap_index];
  52.          
  53.           // 检查是否需要换行(当前字符会超出屏幕边界)
  54.           if ((current_x + glyph_dsc.box_w + glyph_dsc.ofs_x) > 250) {
  55.             current_x = 0;  // 重置x坐标到行首
  56.             current_y += font_set->line_height + 4;  // 移动到下一行,+4为行间距
  57.           }
  58.          
  59.           // 遍历字符位图,逐像素绘制字符
  60.           for (int _box_h = 0; _box_h < glyph_dsc.box_h; _box_h++) {
  61.             for (int _box_w = 0; _box_w < glyph_dsc.box_w; _box_w++) {
  62.               bool bit = get_bit(bitmap_ptr, _box_h * glyph_dsc.box_w + _box_w);
  63.               
  64.               // 计算并设置屏幕上的像素点
  65.               st7305_set_pixel(
  66.                   // X坐标:起始x + 字符内部的x偏移 + 字符的x方向偏移 + 当前行的x偏移
  67.                   x + _box_w + glyph_dsc.ofs_x + current_x,
  68.                   // Y坐标:起始y + 字符内部的y偏移 + 当前行的y偏移 + 字符垂直对齐偏移
  69.                   // 核心公式 (font_set->line_height - glyph_dsc.box_h - glyph_dsc.ofs_y)
  70.                   // 实现字符按基线对齐,这是解决"-"和"一"字符偏移问题的关键
  71.                   y + _box_h + current_y + (font_set->line_height - glyph_dsc.box_h - glyph_dsc.ofs_y),
  72.                   bit  // 像素是否点亮
  73.               );
  74.             }
  75.           }
  76.          
  77.           // 特殊字符处理:空格和回车
  78.           if (unicode_char == ' ') {
  79.             // 空格字符:仅按ofs_x调整位置
  80.             current_x += glyph_dsc.ofs_x;
  81.           } else if (unicode_char == '\r') {
  82.             // 回车符:重置到行首并换行
  83.             current_x = 0;
  84.             current_y += font_set->line_height + 4;
  85.           }
  86.          
  87.           // 更新x坐标到下一个字符的位置
  88.           // 位置增量 = 字符宽度 + 字间距(这里使用line_height/char_spacing作为字间距)
  89.           current_x += glyph_dsc.box_w + font_set->line_height / char_spacing;
  90.           break;  // 找到字符后退出映射表循环
  91.         }
  92.       }
  93.     }
  94.   }
  95. }
复制代码


ThuSeptember-202509257060..png
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail