[C/C++]字模的解析(视频)

Photobucket

取模是个很有意思的事情。单片机开发会用到的取模,常常就是用来把汉字字模对应的数组保存到单片机的flash之中,在显示的时候进行调用。而从字到字模之间的转换,常常是通过一个“取模软件”来实现,根据输入的文本,返回相应的数组常量。

而这些字模(点阵字体)的数据是从哪里来的呢?似乎大多数来自于Dos时代,汉字点阵字体则来源于UCDOS,或是早期的WPS。在没有图形界面的年代,在终端上进行字符的显示,感觉就是在填充一个大点阵,这个大点阵的尺寸,就是屏幕的分辨率,感觉各个方位所对应的字符,将其对应的点阵字体“贴”上去。而在网上搜了半天,也就只是找到几种点阵字体:

font name width x height unit length
ASC12 8 x 12 12
ASC16 8 x 16 16
ASC48 24 x 48 144
HZK12 12 x 12 24
HZK14 14 x 14 28
HZK16(宋简体) 16 x 16 32
HZK16F(宋繁体) 16 x 16 32
HZK16S(美术体) 16 x 16 32
HZK24F(仿宋体) 24 x 24 72
HZK24H(黑体) 24 x 24 72
HZK24K(楷体) 24 x 24 72
HZK24S(宋体) 24 x 24 72
HZK32 32 x 32 128
HZK40 40 x 40 200
HZK48 48 x 48 288

其中,ASC开头的为半角字符,使用ASCII/ANSI编码排序;HZK开头的为全角字符,以GB2312编码进行排序。

Photobucket

在简体中文windows系统下,中文默认的编码为GBK/CP936,是GB2312/区位码的扩展。文中提到的字库因为年代久远,所以也都是采用最早的GB2312编码,算是现在GBK编码的子集,所以在windows平台上的中文,默认都是以双字节的编码出现。

而在linux类平台下,以ubuntu为例,即便是简体中文的语言版本,都使用UTF-8编码进行表达,一般情况下,中文字符采用3个字节进行表示,是对unicode编码的再次转换。这也就是造成windows平台的文本文件/音乐文件的tag,在linux系统下会出现乱码的根本原因。因为其文本编辑器默认都采用UTF-8编码对其进行解码。解决方法无非两种,一种给linux系统添加GBK字符集,另一种就是转换被解码对象的编码方式。从我个人的角度出发,更倾向于后者,一方面,我不希望一个系统里面出现过多的编码方式,要确定新建文本其唯一的编码方式;另一方面,UTF-8本来就是通用的编码方式,我们的数据库、网页,大多数都采用标准更为统一的UTF-8编码进行表达。相信Mac平台的中文字符集也是UTF-8。unicode 网罗的世上几乎所有的字符,其中中文编码规则部分和GB2312也没有什么直接的换算关系,完全就是两套系统。

转码的工具有很多,毕竟随着计算机国际化的发展,这个问题在很早的时候就出现了。在程序开发的过程中,C语言的话可以使用 iconv 库(这是一个标准库)进行转码。在ruby中,String 类可以直接通过 encoding 方法进行转码。

而其实需要转码的,只是中文部分而已,相信在绝大多数的编码中,半角英文的部分都是重合的。所以为了保险起见,请尽量将您的电脑系统的文件名、卷标使用英文命名,这样不管是系统恢复,或是使用其它系统打开的时候都能正确显示,而且这样在终端/命令行中操作也更简单,不用老是切换输入法,tab补全也更快速。

Photobucket

继续说点阵字库,在BitmapFont项目中的点阵字库文件,部分进行了一定调整,偏移量是统一设置为0。这些字库的原始文件并不是这样,相信可能是为了节省空间的考虑,毕竟在这些文件诞生之初,存储的成本还是比较高。那些本来就没法显示的字符,比方说’\n’换行、’\r’回车都没有什么东西好显示,我对其缺少的部分用0x00进行填充。毕竟现在Arduino都开始用SD卡来进行存储了,存储这样几k的字库文件,根本不在话下。而在原始字库文件中,哪怕本身偏移量就是0,它也不是老老实实地用0填充,也又不少自定义的字体在里面。比方说下图中的“马”,出现在一系列象棋的棋子字模中,根据其字模位置推算出的GB2312编码中,并没有对应的字符。

Photobucket

所以的字库中,字体都根据其编码顺序依次排列,每 unit_length(单位模长) 个字节为一个单位。而这 unit_length 个字节正好可以排列出一个点阵字体。

unit_length * index = position
单位模长 * 序列号 = 文件位置

在ASC系列字库中,序列号 index 就等于其 ASCII 编码,范围是 0x00-0x7f
在HZK系列字库中,序列号 index 中,设其 GB2312 编码为HL,即区码位为H,位码为L,则 index = 94 * (H – 0xa1) + (L – 0xa1)
因为按照HZK编码规则,每个字符使用2个字节表示,高字节H表示区码,低字节L标志位码,区码和位码都从0xa1开始计数直至0xfe,当 index 为 0 时,GB2312 编码 等于 0xa1a1。

根据这样的规则,当得知字符的 index,就可以计算出其在文件中的地址,通过对文件的随机读取(区别于顺序读取),就可以很容易的把“模”给“取”出来。

视频中,演示的串口发送中文并在液晶屏上显示,大概就是这样的原理。因为默认发送的是UTF-8编码,无法对应字模的GB2312编码,所以在上位机脚本内部,将其转化为了GB2312编码,再进行串口发送。而Arduino下位机根据,接收到的 GB2312 编码,计算相应字模在字库文件中的位置,打开SD卡上存储的字库文件,找到相应位置,读取对应单位模长的数个字节,并打印到液晶屏上,倒也不是很复杂。因为 Arduino 有强大的SD库,配合我自己的dot-matrix库来驱动液晶屏,所以实现起来就很简单啦。脚本之所以用 ruby 来写,还是因为其操作串口非常简单。

这里可以发现一点,就是在一个中英文混杂的 ASCII 文件中,英文的字节肯定是小于0x7f(该字节最高位为0),而中文的两个字节都必然大于0x80(字节最高位为1),所以在这样的字节流里面,依然可以很容易就区分出哪些是英文半角字符,那些中文全角字符。UTF-8的编码方式也是使用类似的方法进行解码。

介绍一下 BitmapFont 程序,这是我用 GNU C++,建立的一个很小的 Makefile 命令行工程。其作用是将收集到的点阵字库文件,以hex代码的方式,重新生成一个UTF-8编码的大文本,如果只是需要在程序flash中预存少量字模,则可以将其中字节代码部分直接复制到程序中。区别与一般“取摸软件”,我把所有字模都放在文本中,通过文本编辑期自带的查找工具进行检索。现在的文本编辑器,扫描一个几十M的纯文本,想必也应该是很快的。程序中还有一个HackFont 程序,对整个字库文件进行,翻转、平移、取反等操作以生存新的字库文件,因为只适用于方形的字体,所以在此就不再展开。

如果大家对程序没什么兴趣,就想直接看到字库以及生成的大文本的话,可以直接下载 output.7z

视频演示:

视频中出现的,串口返回的 Arduino 程序

void setup() 
{
  Serial.begin(9600);
}

void loop() 
{
}

void serialEvent() 
{
  char s[5];

  while (Serial.available())
  {
    byte inChar = (byte)Serial.read(); 

    //    Serial.write(inChar);
    sprintf(s, "0x%02x", inChar);
    Serial.write(s);
    Serial.println();
  }
}

推荐一些编辑器:

  1. vi/vim,牛逼之选。
  2. SCITE,跨平台轻量级文本编辑器,切换编码方式很方便。
  3. EditPlusNotepad++,windows平台推荐。

特别感谢,由圣源电子提供的Arduino Mega 2560, Ethernat W5100 Shield, Sensor Shield

关于aGuegu

向着更高的逼格
此条目发表在C/C++分类目录,贴了, , , , , , , , 标签。将固定链接加入收藏夹。