文章目录
本节源码在github仓库show_font、freetype中。
1、字符的编码方式
1.1、串口发送数据给PC机的流程
比如要发送copy
这个字符串,那么2440使用串口挨个发送43、6F、70
,表示"C"、"O"、"P"
。PC机接收到之后就会显示出"C"、"O"、"P"
这些字符。
既我们要搞清楚三点:数字、数字代表什么、显示为什么。
1.2、数字代表什么
PC中字符编码的发展
中国人使用的编码
其中用什么值表示汉字就称为字符编码(charset),比如"中"
字使用"D6 D0"表示,那么这一套字符编码就称为国标(GB2312
)。港澳台使用的字符编码为BIG5
,比如"D6 D0"
表示"笢"
。
那么从数字
到数字代表什么
就引入了字符编码,如:ASCII码
、GBK
、BIG5
。这就会有使用不同编码方式编写的文字之间不能互通,比如使用GB2312
编码方式解析BIG5
编写的文字就会导致错误出现。
世界统一使用unicode编码表统一字符编码
unicode只是建立了数值
和字符
之间的关系,既用某一个数值
表示某一个符号
。
这么编码表怎么来表示它呢?比如表示abc
这个字符串使用3字节表示一个字符的话,在ASCII码中为 0x61、0x62、0x63
,所有编码方式都兼容ASCII码。
那么使用使用3字节来表示unicode码
的话,abc
表示为:
0x00、0x00、0x61
0x00、0x00、0x62
0x00、0x00、0x63
既使用了9字节来表示abc
,这就会造成浪费,那么就有UTF-8、UTF-16LE、UTF-16BE
等等方法来表示unicode码
。
使用4中不同编码方式表示"abc中"
:
-
ANSI,会使用计算机默认的编码格式打开。
-
UTF-8,使用变长字节表示,前面的
EF BB BF
表示它是UTF-8
的编码格式。
UTF-8实现方法如下:
中字的表示方法如下:
-
UTF-16BE(大端,使用
FE FF
表示,2字节表示),大端里高字节数据61、62、63在后面,对于纯英文字符浪费一个字节
-
UTF-16LE(小端,使用
FF FE
表示,2字节表示),小端里高字节数据61、62、63在前面,对于纯英文字符浪费一个字节。
1.3、显示为"什么"
字符显示原理
从一个数字到代表什么,使用字符编码来解决;那么从代表什么到显示"为"什么使用字体来解决。
如之前的图中,2440将数字43发送给串口,串口使用这个43到字体文件中找到对应字体的字模点阵将其显示出来;怎么从字体文件中找到那个字呢?字体文件中含有编码表和字体数据,根据这两项显示出正确的字符。
编辑一段代码,编译运行之后查看其信息:
#include <stdio.h>
int main(int argc, char **argv)
{
int i = 0;
unsigned char *str = "abc中";
while (str[i]) {
printf("%02x ", str[i]);
i++;
}
printf("\n");
return 0;
}
上述代码分别以ANSI
和UTF-8
格式保存,再进行编译运行:
源文件用不同的编码方式编写,会导致执行结果不一样。怎么解决?编译程序时,要指定charset
(字符集)。
man gcc
/charset
-finput-charset=charset 表示源文件的编码方式, 默认以UTF-8来解析
-fexec-charset=charset 表示可执行程序里的字符以什么编码方式来表示,默认是UTF-8
gcc -o a a.c /* 默认是UTF-8 */
现在指定源文件的编码方式为GBK
,输出可执行程序的编码方式为UTF-8
,进行编译;
gcc -finput-charset=GBK -fexec-charset=UTF-8 -o utf-8_2 ansi.c
测试如下,这就显示正确了。
字符显示流程
- 在一个源文件中使用某个数值来表示某个字符;
- 怎样知道某个数值表示某个字符呢?引入
charset
字符集(字符编码),有ANSI
、GBK
、BIG5
等等; - 这些字符集之间不通用,引入
unicode
编码表,编码表指定某个数值对应某个字符; - 怎么表示这个
unicode
编码表呢?有UTF-8
、UTF-16BE
、UTF16-LE
等等方法; - 最后文字怎样显示呢?软件收到一个数值就去查找编码表来确定是哪一个字符,这个文字以什么方式显示出来?要去字体文件中找到这个字符对应的字体数据,字体文件中肯定有编码表,就根据这个编码表来进行显示。
2、点阵显示
2.1、介绍
LCD控制器
取出SDRAM
中显存里若干字节的数据(代表一个像素),发给LCD
就能显示某个颜色了,取到最后一个格子的数据就会从头开始取数据。
我们想在LCD
上显示一个字的话,使用16x8
的点阵数据,有些点亮有些不点亮。在某个位置显示某个字符,在显存中找到这个位置,根据点阵设置显存中的数据。
如果要显示字母A,点阵数据如下:
2.2、写测试程序
2.3、调试代码
编译程序
arm-linux-gcc -o show_font show_font.c
cp show_font /work/nfs_root/fs_mini_mdev_new
cp HZK16 /work/nfs_root/fs_mini_mdev_new
配置、修改内核支持把lcd.c
编译进去
sudo cp /work/drivers_and_test_new/10th_lcd/lcd.c drivers/video/
修改Makefile
sudo vim drivers/video/Makefile
若改变代码将其放在tq2440
或者mini2440
上:
再修改Makefile为对应的平台:
mini2440:
三星2440:
配置内核
make menuconfig
Device Drivers --->
Graphics support --->
<*> Support for frame buffer devices --->
<*> S3C2410 LCD framebuffer support
make uImage
cp arch/arm/boot/uImage /work/nfs_rootuImage_lcd_2.2
使用新内核启动
nfs 32000000 192.168.2.22:/work/nfs_root/uImage_lcd_2.2; bootm 32000000
//将程序放在下面TQ2440和mini2440上同样可以通过测试
nfs 32000000 192.168.1.123:/work/nfs_root/uImage_tq2440; bootm 32000000
nfs 32000000 192.168.1.123:/work/nfs_root/uImage_mini2440; bootm 32000000
把tty1
也就是lcd
当作控制台显示,去掉tty1:...
就是显示在串口console....
中:
测试
三星2440中:
TQ2440中:
mini2440中:
3、freetype
下面使用到的代码和文档在GitHub仓库中。
3.1、freetype理论介绍
显示一个字符
在上一小节里面,从点阵字库里面把英文字母或汉字的字模取出来,然后在 LCD 上面显示。这种方法有一个缺点,一旦选定了点阵字库文件之后,这个文字的大小就定死了,不能够缩放。
但是在我们的日常生活中,字体是可以缩放的。这些字体称之为矢量字体。
矢量字体在字体文件里面存放的方式如下:
- 1、一个字符就是存储了若干条闭合曲线的关键点
- 2、使用数学曲线(贝塞尔曲线)链接关键点
- 3、填充这些关键点的内部空间构成一个字符
构造字体文件
制作字库的时候,把字描出来,让字精细,格子必须细。在格子里面以点阵的方式描绘出想要的字。美工人员做字的样子,程序员提取关键点,及相对位置制作为字库文件。
一个字符的轮廓如上图,美工先作图画出,程序员再将提取关键点(glyph)
及其相对位置。构成一个charmaps(s表示可能支持多种编码方式,ANSI、unicode等等)
将所有字符存储器中,一个一个的字符成为glyph
(字形)。
文字的显示过程
- 1、给定一个汉字、字符,可以从字体文件确定它的编码值(是GBK、unicode还是等等);
- 2、根据编码值从字体文件中找到它的字形
"glyph"
; - 3、设置字体的大小
- 4、使用某些函数把
glyph
里面的点缩放为第3步
的字体大小; - 5、转换为位图点阵;
- 6、在LCD上显示出来
编程实现文字的显示过程
文档参考
其中使用一个函数 FT_Load_Char( face, text[n], FT_LOAD_RENDER );
代替了左边的三个函数找到、取出、转换位图
。
3.2、在PC上测试freetype
freeType中各个API介绍
官方文档:freetype-doc-2.4.10\freetype-2.4.10\docs\tutorial
下面提供了四个例子:
后面三个例子是 C++
的,第一个例子是 C语言
的。
对接口不了解的话,源代码里有接口参考:freetype-2.4.10\docs\reference
。
给定字符大小,计算一个字符用多少像素表示:
下图中`FT_Set_Char_Size`函数参数解析:
char_width : 字符宽度 单位 :1/64 point point = 1/72 inch
若 char_with 写 100,则字体物理大小为 100*(1/64)*(1/72) 英寸
hor_resolution : 水平方向分辨率 单位:dpi (dpi: dots-per-inch 每英寸里面有多少个像素)
ver_resolution : 垂直方向分辨率 单位:dpi (dpi: dots-per-inch 每英寸里面有多少个像素)
“像素”pix 实际上是投影光学上的名词,一个像素的大小尺寸不太好衡量,其实它就是屏幕上的一个光点。在计算机显示器和电
视机的屏幕上都使用到像素作为它们的基本度量单位,分辨率越高,像素就越小。
假设 char_width = 100,hor_resolution = ver_resolution = 200,表明一个英寸里面有 200 像素,则一个字符的像素为:
100*(1/64)*(1/72)*200 = 4.3(像素)
字符旋转:
笛卡尔坐标:左图所示 LCD 坐标: 右图所示
字体文件,字体函数用的都是笛卡尔坐标
由于在笛卡尔左边中的原点为左下的位置,而LCD左边的原点为左上的位置,所以如果要在LCD中的(x,y)坐标出显示某体字体,则转化到
笛卡尔左边中要进行左边变换,变换为x’=x,y’=height-y
测试freeType中的例子
显示字符
tar xjf freetype-2.4.10.tar.bz2
cd freetype-2.4.10
./configure
make
sudo make install
gcc -o example1 example1.c -I /usr/local/include/freetype2 -lfreetype -lm
gcc -finput-charset=GBK -fexec-charset=UTF-8 -o example1 example1.c -I /usr/local/include/freetype2 -lfreetype -lm
-I (大i):表示指定头文件路径
-l (小l):表示指定库
-lm :表示数学库
./example1 ./simsun.ttc abc /* example1放在有simsun文件的目录下才能运行 */
编译03.freetype\01th_pc\01th_english
中的example1.c
:
修改宽度和高度:
想在新的坐标(0,40)的地方开始显示:
之前:
修改为:
再次编译运行:
显示汉字
代码为03.freetype\01th_pc\02th_chinese
中的example1.c
。
编译运行时agf
参数已经没有用了,但是还是要保留着,因为应用程序会判断参数个数对不对:
宽字符
代码为03.freetype\01th_pc\03th_wchar
中的example1.c
。
想要显示一个汉字的时候还要去找它的 unicode
码,这样太麻烦了。能不能在程序里像如下这种方式写我想要的汉字:
这是不可以的。原因是对于汉字这种字符我们用两个字节表示,对于英文字母我们用一个字节显示。这样在处理字符的时候就要区别是汉字还是英文字母,处理起来麻烦。
引入了宽字符:宽字符中汉字个用 4 个字节表示,英文字母也用 4 个字节表示: 以后我们处理过程就会非常的方便。
宽字符的头文件:
定义一个宽字符:
将03.freetype\01th_pc\03th_wchar
中的example1.c
进行编译运行:
错误为:无法转换字符集。
这里需要指定字符集:源代码 example1.c
是以ASCII
码保存的。
编译时指定输入输出字符集:
gcc -finput-charset=GBK -fexec-charset=UTF-8 -o example1 example1.c -I/usr/local/include/freetype2 -lfreetype –lm
运行:打印出了 韦东山 g
的 unicode
码:
拖到 ubuntu 编译:
运行出现段错误:Segmentation fault (core dumped)
将 text,num_chars
那一行注释掉:
再次编译运行:
深入理解
我们在源代码 example1.c
里指定了从(0,40)
这个坐标开始打印文字。
将03.freetype\01th_pc\04th_print_info
中的example1.c
拖到 ubuntu
编译,运行:
但是上述的结果却不是在(0,40)
位置显示字符的。
在描画一个字符的时候要指定一个原点 origin
,原点这条线称为基线
,字符
有可能会超过基线
。
汉字不会超过基线,但是英文字母会超过基线:
在代码中:
打印xMin
, xMax
, yMin
, yMax
:
看 freetype-doc-2.4.10\freetype-2.4.10\docs\tutorial
里的 step2.html
:
搜索 cbox :
将03.freetype\01th_pc\04th_print_info
中的example1.c
放在ubuntu
下编译运行:
编译:gcc -finput-charset=GBK -fexec-charset=UTF-8 -o example1 example1.c -I /usr/local/include/freetype2 -lfreetype -lm
运行:./example1 ./simsun.ttc abc
显示:
往上翻找到打印信息:
根据上面的打印参数作下面的画:
如果实验中不显示"韦gif"
的话,要把 串口工具的字体调小一点:
3.3、在LCD上显示字符
上一节,将字符打印到标准输出上的流程如下:
- 1、从
main
函数开始,先初始化FT_Init_FreeType
; - 2、打开一个字体文件,得到一个
face
(平面); - 3、设置字体的大小;
- 4、设置转换矩阵、参数;
- 5、
FT_Load_Char
函数会根据字符的uniocde码
在字体文件中得到glyph
,其中参数FT_LOAD_RENDER
指定将glyph
转换为位图
,之后就可以打印出字符了; - 6、使用
draw_bitmap
函数打印,将点阵存在全局数组中,使用show_image
打印到标准输出
现在要想把字符打印到LCD
,在draw_bitmap
函数中将点阵信息存到LCD
的显存framebuf
显存中即可。
根据说明文档交叉编译freetype
库:
需要三步:
A: ./configure
B: make
C: make install
编译:
tar xjf freetype-2.4.10.tar.bz2
cd freetype-2.4.10
./configure --host=arm-linux
su root // 不切换的话会出错 https://www.veryarm.com/41852.html
make DESTDIR=$PWD/tmp install //表示编译出来的程序是在 arm 开发板 linux 下面使用
编译出来的头文件应该放入:
/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include
编译出来的库文件应该放入:
/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib
把tmp/usr/local/lib/*
库文件复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib
//-d表示原来是链接文件,就保持链接文件 -rf表示全部都copy
sudo cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib -d -rf
cp *so* /work/nfs_root/fs_mini_mdev_new/lib -d //动态库拷贝到网络根文件系统
把tmp/usr/local/include/*
头文件复制到 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include
cp * /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include -rf
去掉 freetype2
这一层
cd /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include
mv freetype2/freetype .
编译example1.c
:
arm-linux-gcc -finput-charset=GBK -o example1 example1.c -lfreetype -lm
拷贝到开发板:
cp example1 simsun.ttc /work/nfs_root/fs_mini_mdev_new/
运行example1
打印字体:
同样一个应用程序,在 PC 机上可以执行,交叉编译之后,在开发板上也可以执行。
3.4、在 LCD 上面显示矢量字体
不在这个控制台
(开发板的串口窗口)上输出,在 LCD
上面输出时,由于之前第二节有在LCD
上显示一个字符,只不过这个字符不能变化大小旋转等等、所以引入了freetype
,现在只要把第二节中的show_font.c
和我们example1.c
结合就可以实现在LCD上显示矢量字体了。
修改03.freetype\02th_arm\02th_lcd
的show_font.c
删除代码中无法识别的字符:
改为:
转换字符集:
iconv -f GBK -t UTF-8 show_font.c
编译:
arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_font show_font.c -lfreetype -lm
注意:这里输入是国标码,输出是国标码是指下图中str
里存的时候输入文件里是国标码,当编译出应用程序时 str
里面存的也是国标码
下图中输入是国标码,但是在宽字符串 chinese_str
里面存储的是宽字符的类型,宽字符的类型里面存的就是 unicode 码
。
将应用程序拷贝到网络根文件系统,在开发板上运行:
cp show_font /work/nfs_root/fs_mini_mdev_new
./show_font ./simsun.ttc
3.5、在 LCD 上面显示文字并显示一个角度
代码为03.freetype\02th_arm\03th_lcd_angle
中的show_font.c
编译:
arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_font show_font.c -lfreetype -lm
将应用程序拷贝到网络根文件系统:
cp show_font /work/nfs_root/fs_mini_mdev_new
在开发板上运行:
./show_font ./simsun.ttc 0 //旋转角度45、90、180、270
3.6、在 LCD 上显示多行文字
从左边开始显示一行文字
代码为03.freetype\02th_arm\04th_show_one_line
里的show_lines.c
文件中这些宽字符文字输入时是以国标码输入的,文件里面是以国标码存储的,但是在编译出来的程序是以宽字符类型 wchar_t
存储的,宽字符类型 wchar_t
里面存储的是 unicode 码
。
编译、拷贝、运行:
编译:arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_font show_font.c -lfreetype -lm
拷贝:cp show_font /work/nfs_root/fs_mini_mdev_new
运行:./show_font ./simsun.ttc 0 //旋转角度45、90、180、270
百字前面有空子的问题可能是 LCD
的驱动程序没有调整好水平方向的时序。
从左边开始显示两行文字
代码为03.freetype\02th_arm\05th_show_two_lines
中的show_lines.c
字符的位置:
计算方框:
显示在LCD上:
编译、拷贝、运行:
编译:arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_lines show_lines.c -lfreetype -lm
拷贝:cp show_font /work/nfs_root/fs_mini_mdev_new
运行:./show_lines ./simsun.ttc
居中显示两行文字
代码为03.freetype\02th_arm\06th_show_lines_center
中的show_lines.c
,显示坐标:
拖到 ubuntu 编译:
定位到第 158 行:
改为:
再拖到 ubuntu 编译:
定位到第 218 行:glyph
有conflict冲突
改为:
编译、拷贝、运行:
编译:arm-linux-gcc -finput-charset=GBK -fexec-charset=GBK -o show_lines show_lines.c -lfreetype -lm
拷贝:cp show_font /work/nfs_root/fs_mini_mdev_new
运行:./show_lines ./simsun.ttc
freetype
的使用简单地介绍到这里,如果想深究 freetype
里函数的用法,可以查看 freetype-2.4.10\docs\reference
目录里的函数说明文档。
如果不想看 freetype-2.4.10\docs\reference
目录里的那么多文档的话,可以看看 freetype
自带的文档 freetype-doc-2.4.10\freetype-2.4.10\docs\tutorial
目录里的 step1.html、step2.html
,里面讲解的非常清楚。
如果英文不好的话,也可以查看网上别人翻译好的中文版:FreeType 字体引擎分析与指南
4、参考资料
点阵字库HZK12 HZK16 HZK24 ASC12 ASC16 简介 及 使用方法
汉字拼音、五笔、GB2312、GBK、Unicode、BIG5编码速查