[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]