日前,笔者在学习simplegui,制作了一套适用于simplegui的自定义字库——UTF8版。
在和群友交流的时候感觉自己讲不清楚,经过分析是自己还没能深入理解相关知识!!!
为了方便交流和提高自己,编写本文。
simplegui是一个面向单色显示屏的开源GUI接口库。作者是Polarix。目前主要在gitee上维护。源码地址:https://gitee.com/Polarix/simplegui。
作者的设计初衷是制作轻量级的仅仅用在单色屏的GUI。毕竟现在主流的GUI,比如emWin、Qt等都太过庞大。在单片机作为主控的小型项目中,有很多不能支持emWin这种GUI需要的RAM和ROM,比如笔者经常使用M0内核单片机,RAM16K,ROM120K都不能支持,更别说STM32F030系列中仅有8K的RAM,64K的ROM。
关于字库和字符编码就不详细介绍了。可以参考simplegui作者Polarix的文档https://gitee.com/Polarix/simplegui/blob/Develope/Documents/How to create font data.md
或者群友的博客https://blog.csdn.net/weixin_43614541/article/details/104581473
同时上述两个文档也都介绍了如何在simplegui中自定义字库。
理论内容看两位的文档即可,关于UTF8的实际测试。范例如下:
//文档编码格式必须是UTF8,同时代码中的汉字不乱码
#include "stdio.h"
char c_chinese_buf[] = "123456请输入密码";
int main(int argc, char const *argv[])
{
for (int i = 0; i < sizeof(c_chinese_buf); i++)
{
printf("%02x ", c_chinese_buf[i]);
}
return 0;
}
/**
得到结果如下:
e8 af b7 请
e8 be 93 输
e5 85 a5 入
e5 af 86 密
e7 a0 81 码
00
*/
如果你的系统是64位,可能得到的结果是 0xffffffe8、af、0xffffffb7 这样的结果。自动忽略前置 f 即可。
如此就可以清晰的看到一个汉字(此处仅包含简单常用汉字)是由三个字节组成。
simplegui采用结构的方式调用字库。
typedef struct
{
SGUI_INT iHalfWidth;
SGUI_INT iFullWidth;
SGUI_INT iHeight;
SGUI_FN_IF_GET_CHAR_INDEX fnGetIndex;
SGUI_FN_IF_GET_DATA fnGetData;
SGUI_FN_IF_STEP_NEXT fnStepNext;
SGUI_FN_IF_IS_FULL_WIDTH fnIsFullWidth;
}SGUI_FONT_RES;
重点是后面四个函数的用法。我用我的语言描述下个人理解:
名称 | 描述 |
---|---|
iHalfWidth | 半字宽度,一般用来显示 ASCII 字符 |
iFullWidth | 全字宽度,一般用来做汉字 |
iHeight | 字高度 |
fnGetIndex | 获取字符索引,实际是根据指针获取以半字宽为单位的字库中的地址索引 |
fnGetData | 获取字库数据,根据上一个函数获取的地址索引而计算的地址作为传入参数 |
fnStepNext | 下一个字符偏移,实际是获取本字符的编码,返回下个字符的地址 |
fnIsFullWidth | 判断是否全字符宽度,就是fnGetData中的那个计算方式 |
可能比较难理解,下面有举例说明,此处不再赘述。
此处也参看上文引用的两篇文章即可。如果基础不够可以百度学习下。
我的学习经验告诉我,不是不会做字库,是不懂字符编码。
我的定义如下:
const SGUI_FONT_RES SGUI_DEFAULT_FONT_chinese =
{
/*SGUI_INT iHalfWidth*/
8,
/*SGUI_INT iFullWidth*/
16,
/*SGUI_INT iHeight*/
16,
/*SGUI_FN_IF_GET_CHAR_INDEX fnGetIndex*/
SGUI_Resource_GetCharIndex_chinese,
/*SGUI_FN_IF_GET_DATA fnGetData*/
SGUI_Resource_GetFontData_chinese,
/*SGUI_FN_IF_STEP_NEXT fnStepNext*/
SGUI_Resource_StepNext_chinese,
/*SGUI_FN_IF_IS_FULL_WIDTH fnIsFullWidth*/
SGUI_Resource_IsFullWidth_chinese};
后面详细介绍每个移植的细节,同时解释上文中关于这个结构体的描述。
我们不按照结构体定义的顺序介绍,而是simplegui底层库的调用顺序,准确的说是SGUI_Text_DrawText()函数的调用顺序
SGUI_CSZSTR SGUI_Resource_StepNext_chinese(SGUI_CSZSTR cszSrc,
SGUI_UINT32 *puiCode)
{
/*----------------------------------*/
/* Variable Declaration */
/*----------------------------------*/
const SGUI_CHAR *pcNextChar;
/*----------------------------------*/
/* Initialize */
/*----------------------------------*/
pcNextChar = cszSrc;
/*----------------------------------*/
/* Process */
/*----------------------------------*/
if (NULL != pcNextChar)
{
//字符是ASCII
if (*pcNextChar < 0x80)
{
*puiCode = *pcNextChar; //ASCII字符则直接赋值
pcNextChar++; //ASCII字符则直接指向下一个字符的地址
}
//字符是汉字
else
{
//UTF8的汉字编码是三个byte表示,做一个组合表示一个汉字
*puiCode = ((*(pcNextChar + 0)) << 16) +
((*(pcNextChar + 1)) << 8) +
((*(pcNextChar + 2)) << 0);
pcNextChar += 3; //此处是指向的下一个汉字的地址而不是索引值
}
}
return pcNextChar;
}
SGUI_BOOL SGUI_Resource_IsFullWidth_chinese(SGUI_UINT32 uiCode)
{
//字符是ASCII
if (uiCode < 0x80)
{
return SGUI_FALSE;
}
//字符是汉字
else
{
return SGUI_TRUE;
}
}
SGUI_INT SGUI_Resource_GetCharIndex_chinese(SGUI_UINT32 uiCode)
{
/*----------------------------------*/
/* Variable Declaration */
/*----------------------------------*/
SGUI_INT iIndex;
//为了计算方便,要求字库必须是先数字后汉字,同时下面连两个数组按顺序包含全部字符
//半字宽的目录
SGUI_CBYTE c_chinese_font_index_buf_for_halfwidth[] = "0123456789";
//汉字目录
SGUI_CBYTE c_chinese_font_index_buf[] = "请输入密码老婆我爱你";
//汉字索引
SGUI_CBYTE *pc_chinese_index = NULL;
/*----------------------------------*/
/* Initialize */
/*----------------------------------*/
// Initialize variable.
iIndex = SGUI_INVALID_INDEX;
/*----------------------------------*/
/* Process */
/*----------------------------------*/
//ASCII码
if (uiCode < 128)
{
pc_chinese_index = c_chinese_font_index_buf_for_halfwidth;
//去除汉字,和判定结束
while ((*pc_chinese_index < 0x80) &&
(*pc_chinese_index != 0x00))
{
if (*pc_chinese_index++ == uiCode)
{
iIndex = pc_chinese_index -
c_chinese_font_index_buf_for_halfwidth - 1;
break; //ASCII直接返回地址即可
}
}
}
//汉字
else
{
pc_chinese_index = c_chinese_font_index_buf;
//去除ascii
while (*pc_chinese_index < 0x80)
{
pc_chinese_index++;
}
//检查数组结束
while (*pc_chinese_index > 0x7F)
{
if ((*pc_chinese_index++ == ((uiCode >> 16) & 0xff)) &&
(*pc_chinese_index++ == ((uiCode >> 8) & 0xff)) &&
(*pc_chinese_index++ == ((uiCode >> 0) & 0xff)))
{
//获取了在纯汉字中的地址偏移
iIndex = pc_chinese_index - c_chinese_font_index_buf - 3;
//获取了在纯汉字中的索引,本文件是UTF8编码,汉字数组占3个byte
iIndex /= 3;
//汉字占用两个宽度
iIndex *= 2;
//然后加上前面的ASCII的个数
iIndex += sizeof(c_chinese_font_index_buf_for_halfwidth) - 1;
break;
}
}
}
return iIndex; //得到的指针偏移要除以每个汉字所占byte的长度
}
SGUI_SIZE SGUI_Resource_GetFontData_chinese(SGUI_SIZE sStartAddr,
SGUI_BYTE *pDataBuffer,
SGUI_SIZE sReadSize)
{
SGUI_SIZE sReadCount;
//TODO:考虑此处根据地址自动计算是否乘以2,以便实现字符和汉字统一数组的操作
/*貌似是库的问题,此处要乘以2*/
const SGUI_BYTE *pSrc = SGUI_FONT_chinese + sStartAddr;
SGUI_BYTE *pDest = pDataBuffer;
if (NULL != pDataBuffer)
{
for (sReadCount = 0; sReadCount < sReadSize; sReadCount++)
{
*pDest++ = *pSrc++;
}
}
return sReadCount;
}
void TestTxt_TestDrawTextChiness(SGUI_SCR_DEV *pstDeviceInterface)
{
SGUI_RECT stDisplayArea;
SGUI_POINT stPos; //控制位置偏移
stDisplayArea.iX = 0;
stDisplayArea.iY = 0;
stDisplayArea.iWidth = 180;
stDisplayArea.iHeight = 16;
stPos.iX = 10;
stPos.iY = 0;
SGUI_Text_DrawText(pstDeviceInterface,
"123请4输5入1密2码345",
&SGUI_DEFAULT_FONT_chinese,
&stDisplayArea,
&stPos,
SGUI_DRAW_NORMAL);
pstDeviceInterface->fnSyncBuffer();
}
这样就实现了汉字ASCII混合使用了。例如菜单中,“1.温度校准”这种使用方式。