uCGUI 字体研究

uCGUI 是一个被广泛使用的嵌入式界面库,其中内置的字体功能非常强大,你可以为你自己的单片机程序界面制作想要的字体。
如何制作自己的字体呢, 可以仔细阅读这篇文章,了解一下 uCGUI 字体方面的知识。

一、字体基础知识

计算机的字体,分为点阵字体和矢量字体,本文仅研究点阵字体。
实际上,点阵字体跟图片很像,都是一个个像素点绘出来的。比如中国的“中”字,我们在12x12像素中,对它进行绘制时,像素是这样的:

0000010000000000
0000010000000000
0000010000000000
0111111111000000
0100010001000000
0100010001000000
0100010001000000
0111111111000000
0100010001000000
0000010000000000
0000010000000000
0000010000000000

通过这个像素表,隐约能看到“中”字的样子。
转换成十六进制是一个这样的数组:

0x04,0x00
0x04,0x00
0x04,0x00
0x7f,0xc0
0x44,0x40
0x44,0x40
0x44,0x40
0x7f,0xc0
0x44,0x40
0x04,0x00
0x04,0x00
0x04,0x00

由于这样不太直观,不能一眼看出来这个字符的字形是什么。所以 uCGUI 定义了 256 个宏,从 0x00~0xFF,用 “_” 代表为 0 的 bit, 用 “X” 代表为 1 的 bit;比如 “_xxx__xx” = 01110011 = 0x73.
该宏定义放在 GUI.h 大家可以查看一下:

#define    ________    0x0
#define    _______X    0x1
#define    ______X_    0x2
#define    ______XX    0x3
#define    _____X__    0x4
#define    _____X_X    0x5
#define    _____XX_    0x6
#define    _____XXX    0x7
#define    ____X___    0x8
#define    ____X__X    0x9
#define    ____X_X_    0xa
#define    ____X_XX    0xb
#define    ____XX__    0xc
#define    ____XX_X    0xd
#define    ____XXX_    0xe
#define    ____XXXX    0xf
#define    ___X____    0x10
#define    ___X___X    0x11
#define    ___X__X_    0x12
#define    ___X__XX    0x13
#define    ___X_X__    0x14
#define    ___X_X_X    0x15
#define    ___X_XX_    0x16
#define    ___X_XXX    0x17
#define    ___XX___    0x18
#define    ___XX__X    0x19
#define    ___XX_X_    0x1a
#define    ___XX_XX    0x1b
#define    ___XXX__    0x1c
#define    ___XXX_X    0x1d
#define    ___XXXX_    0x1e
#define    ___XXXXX    0x1f
#define    __X_____    0x20
#define    __X____X    0x21
#define    __X___X_    0x22
#define    __X___XX    0x23
#define    __X__X__    0x24
#define    __X__X_X    0x25
#define    __X__XX_    0x26
#define    __X__XXX    0x27
#define    __X_X___    0x28
#define    __X_X__X    0x29
#define    __X_X_X_    0x2a
#define    __X_X_XX    0x2b
#define    __X_XX__    0x2c
#define    __X_XX_X    0x2d
#define    __X_XXX_    0x2e
#define    __X_XXXX    0x2f
#define    __XX____    0x30
#define    __XX___X    0x31
#define    __XX__X_    0x32
#define    __XX__XX    0x33
#define    __XX_X__    0x34
#define    __XX_X_X    0x35
#define    __XX_XX_    0x36
#define    __XX_XXX    0x37
#define    __XXX___    0x38
#define    __XXX__X    0x39
#define    __XXX_X_    0x3a
#define    __XXX_XX    0x3b
#define    __XXXX__    0x3c
#define    __XXXX_X    0x3d
#define    __XXXXX_    0x3e
#define    __XXXXXX    0x3f
#define    _X______    0x40
#define    _X_____X    0x41
#define    _X____X_    0x42
#define    _X____XX    0x43
#define    _X___X__    0x44
#define    _X___X_X    0x45
#define    _X___XX_    0x46
#define    _X___XXX    0x47
#define    _X__X___    0x48
#define    _X__X__X    0x49
#define    _X__X_X_    0x4a
#define    _X__X_XX    0x4b
#define    _X__XX__    0x4c
#define    _X__XX_X    0x4d
#define    _X__XXX_    0x4e
#define    _X__XXXX    0x4f
#define    _X_X____    0x50
#define    _X_X___X    0x51
#define    _X_X__X_    0x52
#define    _X_X__XX    0x53
#define    _X_X_X__    0x54
#define    _X_X_X_X    0x55
#define    _X_X_XX_    0x56
#define    _X_X_XXX    0x57
#define    _X_XX___    0x58
#define    _X_XX__X    0x59
#define    _X_XX_X_    0x5a
#define    _X_XX_XX    0x5b
#define    _X_XXX__    0x5c
#define    _X_XXX_X    0x5d
#define    _X_XXXX_    0x5e
#define    _X_XXXXX    0x5f
#define    _XX_____    0x60
#define    _XX____X    0x61
#define    _XX___X_    0x62
#define    _XX___XX    0x63
#define    _XX__X__    0x64
#define    _XX__X_X    0x65
#define    _XX__XX_    0x66
#define    _XX__XXX    0x67
#define    _XX_X___    0x68
#define    _XX_X__X    0x69
#define    _XX_X_X_    0x6a
#define    _XX_X_XX    0x6b
#define    _XX_XX__    0x6c
#define    _XX_XX_X    0x6d
#define    _XX_XXX_    0x6e
#define    _XX_XXXX    0x6f
#define    _XXX____    0x70
#define    _XXX___X    0x71
#define    _XXX__X_    0x72
#define    _XXX__XX    0x73
#define    _XXX_X__    0x74
#define    _XXX_X_X    0x75
#define    _XXX_XX_    0x76
#define    _XXX_XXX    0x77
#define    _XXXX___    0x78
#define    _XXXX__X    0x79
#define    _XXXX_X_    0x7a
#define    _XXXX_XX    0x7b
#define    _XXXXX__    0x7c
#define    _XXXXX_X    0x7d
#define    _XXXXXX_    0x7e
#define    _XXXXXXX    0x7f
#define    X_______    0x80
#define    X______X    0x81
#define    X_____X_    0x82
#define    X_____XX    0x83
#define    X____X__    0x84
#define    X____X_X    0x85
#define    X____XX_    0x86
#define    X____XXX    0x87
#define    X___X___    0x88
#define    X___X__X    0x89
#define    X___X_X_    0x8a
#define    X___X_XX    0x8b
#define    X___XX__    0x8c
#define    X___XX_X    0x8d
#define    X___XXX_    0x8e
#define    X___XXXX    0x8f
#define    X__X____    0x90
#define    X__X___X    0x91
#define    X__X__X_    0x92
#define    X__X__XX    0x93
#define    X__X_X__    0x94
#define    X__X_X_X    0x95
#define    X__X_XX_    0x96
#define    X__X_XXX    0x97
#define    X__XX___    0x98
#define    X__XX__X    0x99
#define    X__XX_X_    0x9a
#define X__XX_XX    0x9b
#define X__XXX__    0x9c
#define X__XXX_X    0x9d
#define    X__XXXX_    0x9e
#define    X__XXXXX    0x9f
#define    X_X_____    0xa0
#define    X_X____X    0xa1
#define    X_X___X_    0xa2
#define    X_X___XX    0xa3
#define    X_X__X__    0xa4
#define    X_X__X_X    0xa5
#define    X_X__XX_    0xa6
#define    X_X__XXX    0xa7
#define    X_X_X___    0xa8
#define    X_X_X__X    0xa9
#define    X_X_X_X_    0xaa
#define    X_X_X_XX    0xab
#define    X_X_XX__    0xac
#define    X_X_XX_X    0xad
#define    X_X_XXX_    0xae
#define    X_X_XXXX    0xaf
#define    X_XX____    0xb0
#define X_XX___X    0xb1
#define    X_XX__X_    0xb2
#define    X_XX__XX    0xb3
#define    X_XX_X__    0xb4
#define    X_XX_X_X    0xb5
#define    X_XX_XX_    0xb6
#define    X_XX_XXX    0xb7
#define    X_XXX___    0xb8
#define    X_XXX__X    0xb9
#define    X_XXX_X_    0xba
#define    X_XXX_XX    0xbb
#define    X_XXXX__    0xbc
#define    X_XXXX_X    0xbd
#define    X_XXXXX_    0xbe
#define    X_XXXXXX    0xbf
#define    XX______    0xc0
#define    XX_____X    0xc1
#define    XX____X_    0xc2
#define    XX____XX    0xc3
#define    XX___X__    0xc4
#define    XX___X_X    0xc5
#define    XX___XX_    0xc6
#define    XX___XXX    0xc7
#define    XX__X___    0xc8
#define    XX__X__X    0xc9
#define    XX__X_X_    0xca
#define    XX__X_XX    0xcb
#define    XX__XX__    0xcc
#define    XX__XX_X    0xcd
#define    XX__XXX_    0xce
#define XX__XXXX    0xcf
#define    XX_X____    0xd0
#define    XX_X___X    0xd1
#define    XX_X__X_    0xd2
#define    XX_X__XX    0xd3
#define    XX_X_X__    0xd4
#define    XX_X_X_X    0xd5
#define    XX_X_XX_    0xd6
#define    XX_X_XXX    0xd7
#define    XX_XX___    0xd8
#define    XX_XX__X    0xd9
#define    XX_XX_X_    0xda
#define    XX_XX_XX    0xdb
#define    XX_XXX__    0xdc
#define    XX_XXX_X    0xdd
#define    XX_XXXX_    0xde
#define    XX_XXXXX    0xdf
#define    XXX_____    0xe0
#define    XXX____X    0xe1
#define    XXX___X_    0xe2
#define    XXX___XX    0xe3
#define    XXX__X__    0xe4
#define    XXX__X_X    0xe5
#define    XXX__XX_    0xe6
#define    XXX__XXX    0xe7
#define    XXX_X___    0xe8
#define    XXX_X__X    0xe9
#define    XXX_X_X_    0xea
#define    XXX_X_XX    0xeb
#define    XXX_XX__    0xec
#define    XXX_XX_X    0xed
#define    XXX_XXX_    0xee
#define    XXX_XXXX    0xef
#define    XXXX____    0xf0
#define    XXXX___X    0xf1
#define    XXXX__X_    0xf2
#define    XXXX__XX    0xf3
#define    XXXX_X__    0xf4
#define    XXXX_X_X    0xf5
#define    XXXX_XX_    0xf6
#define    XXXX_XXX    0xf7
#define    XXXXX___    0xf8
#define    XXXXX__X    0xf9
#define    XXXXX_X_    0xfa
#define    XXXXX_XX    0xfb
#define    XXXXXX__    0xfc
#define    XXXXXX_X    0xfd
#define    XXXXXXX_    0xfe
#define    XXXXXXXX    0xff

那么,“中”字就可以很直观的看出来:

/* char: 中   code:0xD6D0 */
static GUI_CONST_STORAGE unsigned char acD6D0[24] = { 
    _____X__,________,
    _____X__,________,
    _____X__,________,
    _XXXXXXX,XX______,
    _X___X__,_X______,
    _X___X__,_X______,
    _X___X__,_X______,
    _XXXXXXX,XX______,
    _X___X__,_X______,
    _____X__,________,
    _____X__,________,
    _____X__,________
};

二、GUI_FONT 的数据结构

uCGUI 有对外公布的字体接口 GUI_FONT ,还有3个内部的数据结构,所以总的数据结构是4个:
1、GUI_FONT 整个字体的结构描述
2、GUI_FONT_PROP 字符信息块的结构
3、GUI_CHARINFO 字符映射的总表
4、字符的图元数据

他们在内存中的组织形式如下:

三、GB2312 和 GBK

通过前面一、二小节内容,我们基本认识了 uCGUI FONT,那么,如何显示汉字呢?
其实,就是 GUI_FONT_PROP 的起始和结束字符的了,把他们设置为汉字的内码即可。
比如 “中”字,在GB2312里面是 D6D0 那么 GUI_FONT_PROP应该是这样:

static GUI_CONST_STORAGE GUI_FONT_PROP Prop1 = {
  0xD6D0, /*start :中*/
  0xD6D0, /*end   :中,  len=1*/
  &Cinfo[ 0 ],
  (void*)0
};

通常一个汉字占2个字节,一个英文占1个字节(ANSI 编码时,本文不讨论 Unicode 和 UTF8)。

注: 《ASCII、Unicode 和 UTF-8 之间的关系》,请详见:
https://www.asciim.cn/m/articles/ascii_unicode_utf8.html

那么如何区分一段文字中的汉字和英文呢?
比如:中a国b人c
他的编码是:D6 D0 61 B9 FA 62 C8 CB 63

分解之后为:
中 = D6 D0
a = 61
国 = B9 FA
b = 62
人 = C8 CB
c = 63

我们通过判断每一个字符,如果该字符>127(127=0x7F),那么就认为是2个Byte组成的中文,否则就是1个Byte的英文。
下面使用 Delphi 语言写出该判断算法,源码如下:

  j:=0;
  i:=1;
  while (i<=len) do
  begin
    outStr:='';
    c:=Byte(s[i]);

    if c<128 then //英文
    begin
      outStr:=Char(c);
      //Memo2.Lines.Add(outStr);

      try
        Form1.StrToImg(outStr,w,h,cInfo[j]);
      except
        break;
      end;
      Inc(j);
    end else
    begin   //中文
      outStr:=Char(c)+s[i+1];
      //Memo2.Lines.Add(outStr);
      try
      Form1.StrToImg(outStr,w,h,cInfo[j]);
      except
        break;
      end;
      //cInfo[j].okwidth := w;   //中文下强制为w
      Inc(j);
      Inc(i);
    end;
    Inc(i);
    Form1.progress:=100*i div len;
    Application.ProcessMessages;
  end;

那么,GB2312和GBK又有什么关系呢? 可以这么理解:GB2312 <= GBK 。GBK包含了GB2312的所有字符。其他的都一样。

四、GB2312 字符简介

GB2312中对汉字进行了“分区”处理,处理规则为:

  1. 每区含有94个汉字和符号,这种处理方式也称为区位码(区号 位号,一块组成一个区位码);
  2. 每个汉字及符号以2个字节来表示;
  3. 第一个字节称为“高位字节”,第二个字节称为“低位字节” (即:大端模式);
  4. 第一个字节是区号,第二个字节叫位号;
    区号 和 位号,均从1开始编号;
  5. 高位字节,使用的十六进制范围:0xA1~0xF7 ,使用的区号范围:01~87。 算法原理:高位字节 = 区号 + 0xA0 ,例如:0xF7=87+0xA0
    低位字节,使用的十六进制范围:0xA1~0xFE ,对应的位号范围:01~94。 算法原理:低位字节 = 位号 + 0xA0 ,例如:0xFE=94+0xA0
例如:

“啊” = 0xB0A1
解析为区位号的算法:
区号 = 0xB0 - 0xA0 = 16
位号 = 0xA1 - 0xA0 = 1
即: 16 区 1 位

五、如何生成 uCGUI 字体

使用 uCGUI 字体生成器,可以自动生成想要的字体:

六、uCGUI 抗锯齿字体

UCGUI是支持抗锯齿字体的,实际上就是把字体灰度化了,ucgui支持4级和16级灰度抗锯齿。
4级灰度叫 GUI_FONTTYPE_AA2_XXXX, 16级灰度叫 GUI_FONTTYPE_AA4_XXXX


点击 AA2(4级灰度)或者 AA4(16级灰度),再按开始转换,即可生成带灰度的字体。

七、把图片转换成字体

虽然 UCGUI 支持图片,如果把图片用来做一些小的 icon 的话,就不如当成字体使用灵活了,当成字体使用的话,可以随时修改 icon 的颜色,因为如果仅仅当成图片使用的话,一种颜色又得换一张图片,就有点太耗存储了。(当然也视情况而定,图片小而个数多时,把图片转字体更灵活;图片大而个数少时,还是当成图片使用;毕竟图片还有压缩模式可以节约ROM。)


作者:coscka
博客地址: https://blog.csdn.net/coscka