忙碌的死龙 发表于 2025-9-25 00:43:04

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

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



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





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

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



lvgl使用的这种字体数据结构,灵活性很强,但是这样就有个问题,就是AI这边没办法很好理解这种数据结构,简单的描述不能让AI生成合理的程序。我喂了之前我写好的lvgl字体绘制函数给AI,结果它生成的代码是有问题的。如下图。


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


使用我自己写的代码,输出是这样的。


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

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

比如,我想自己生成一个抽取指定字符的中文字体文件,又不清楚怎样去做,可以这样问AI,我有一个XXX字体文件(路径xxx),想生成一个12px,最小化,包含XXXXX字符的字体文件,应该怎样做。



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




调整好的代码如下

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.range_start;
      // 检查当前字符是否在该映射表范围内
      if (unicode_char >= cmap_start &&
            (unicode_char <= (cmap_start + font_set->dsc->cmaps.range_length))) {
          lv_font_fmt_txt_glyph_dsc_t glyph_dsc = {0};
         
          // 如果映射表是连续的(没有稀疏映射),使用直接索引查找
          if (font_set->dsc->cmaps.list_length == 0) {
            // 直接通过码点计算字形描述的索引
            uint32_t glyph_dsc_index =
                unicode_char - font_set->dsc->cmaps.range_start +
                font_set->dsc->cmaps.glyph_id_start;
            glyph_dsc = font_set->dsc->glyph_dsc;
            // 设置英文字符的间距较小
            char_spacing = 6;
          } else {
            // 如果是稀疏映射,遍历Unicode列表查找匹配的字符
            for (int unicode_index = 0;
               unicode_index < font_set->dsc->cmaps.list_length;
               unicode_index++) {
            if (font_set->dsc->cmaps.unicode_list ==
                  (unicode_char - font_set->dsc->cmaps.range_start)) {
                glyph_dsc =
                  font_set->dsc->glyph_dsc[unicode_index +
                                             font_set->dsc->cmaps.glyph_id_start];
            }
            }
          }
         
          // 获取字符的位图数据指针
          const uint8_t *bitmap_ptr = &font_set->dsc->glyph_bitmap;
         
          // 检查是否需要换行(当前字符会超出屏幕边界)
          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;// 找到字符后退出映射表循环
      }
      }
    }
}
}

页: [1]
查看完整版本: [ESP8266/ESP32]qwen code帮我写代码--在屏幕上输出中文字体