我们知道, 在DOS下经常利用点阵来显示汉字. 带汉字显示的程序,很多都会自己带上汉字库, 这个字库里放的就是每个汉字的点阵.
一. 汉字的显示原理之一:点阵汉字
简单的理解, 所谓一个字的点阵. 其实就是指这个汉字用多少个象素点来描述. 每个象素点显示为什么颜色, 通常情况下, HZK16采用的是16*16点阵, 即256个象素点描述一个汉字.这些点的颜色分为两种, 一种是前景色, 一种是显示为背景色.那么,关于那些点显示为前景色, 那些点显示为背景色, 是如何得知的呢??可以这样来考虑, 你在纸上比较正正方方的写一个规则的楷字, 然后在这个字的从上到下,左到右, 分别画十七条直线, 那么这个字就被放置于一个16*16的方格之内, 这样我们就可以很明显的看出, 16*16的方格内的具体哪些点有笔划经过, 有笔划经过与没笔化经过的即就是应该被分别填充上前景色与背景色的点.现在,找到了一个汉字的点阵, 那么还须要用数据来记录点阵的信息, 通常情况下, 我们会用32个字节来表示16*16点阵的汉字, 即每一行用二个字节来记录十六个象素点的色色彩情况, 0表示背景色, 1表示前景色. 16行其须要32个字节.
点阵汉字的原理同时也决定了它的缺点, 他不具务放大特性, 因为它的显示是基于被定死的点阵, 放大后, 会产生明显的锯齿,非常的难看, 当然, 可以进行一些光滑处理, 但基本上没有多在的改观.但点阵汉字简易, 对于复杂汉字, 它比矢量显示汉字法更快带.矢量显示是基于记录汉字的笔化的. 对于简单的汉字它比较占优势, 容易放大处理. 但对于复杂的汉字, 表示起来, 则笔化太多..复杂.
二. 关于字库的建立及其原理
现在讲完了汉字点阵. 也说了一个汉字点阵的存放方式, 但具体的点阵如何存放, 读者也应该了解.
通常情况下, 一般的DOS下的程序都会提供一个汉字库, 这样在脱离汉字平台(如UCDO)的支持下也可以进行汉字显示, 但是这样会存一个问题, 就是如果每个DOS下的程序员都这么做的话, 就会造成一定的磁盘空间浪费. 所以有的DOS下的程序,针对自己所需要的汉字, 就会定制自己的小型字库, 那么字库的制作到底应该如何进行呢? 下面我们将就这个问题进行一些基本的讨论.
众所周知,一个ASCII字符占一个字节,它的数值从0到255, 那么汉字字符将如何与ASCII字符区别开来呢?
实际上,仔细观察ASCII字符表,从第161(即0xa1)个字符开始,后面的字符并不经常为E文所使用。充分利用这一特性,将161-255之间的数值空间作为汉字的标识码。既然255-161 = 94不能满足汉字容量的要求,就将每两个字符并在一块(即一个汉字占两个字节),显然,94* 94 =8836基本上已经满足了常用汉字个数的要求。
从以上的讨论可以知道, 用二个字节来表示一个汉字, 其原因就是上面说的, 这个就是我们常说的汉字机内码, 一个汉字的机内码是由值都大于0xa1的值组成的.
说完机内码, 有的朋友可能就会问题, 机内码与建立汉字字库有什么关系呢??
我们常见的标准的汉字字库HZX16(点阵16*16),HZK24(24*24)两种.由上面的讨论我们得知, 一个汉字点阵须要256个象素点阵来表示, 我们采用一个字节的8位来表示八个象素, 其须32个字节; 字库中要存放的是所有常用的汉字的二进制点阵数据, 它的存放是有序的, 下面我们说一下这个顺序:
首先.对于"我"字来说, 它的机内码是0xce,0xd2; 机内码每个字节均从0xa1开始, 那么我们已经采用的建立点阵字在库中的索引方法是:
将整个字库里面的汉字是94*94的二维数组, 要找任意一个汉字的点阵, 就须要知道这个汉字在这个二维数组当中的X维与Y维.
x维 = (机内码字节1-0xa1) & 0x7f;
y维 = (机内码字节2-0xa1) & 0x7f;
求汉字在X,Y维后, 那么按照每个汉字占用32个字节, 则可以得出汉字相对于字库头的偏移是 offset = (x*94 + y)*32;
其实,X与Y就是汉字的区位码, 汉字的区位码是从0-94的. 但实际上只用了16-87..
其中一级汉字在16-55..二级汉字在56-87.是按照一定的规则来确定区位码的.对于一级汉字.是按拼音首字母级笔划.二级汉字是按部首来的.我特意生了一个汉字的区痊码,机内码.在字库中偏移的文件..大家可以下载来看一下. 可以知道:
啊-------------区位码(x = 15, y = 0); offset=b040; 机内码:(0xb0,0xa1);
所以汉字的区内码,机内码,偏移的信息,请下载这个文件查看.
http://www.macro-tax.com/home/ucgui/HZK_info.rar
其中,区位码(x=0-14)与(88-94)都是没有对应汉字的.字库中实际的对应汉字点阵字数为94*72=6768个汉字.
实际上, 一个字库中有前16*32个字节没有表示具体的汉字的, 在字库里被用来表示什么东西没有什么具体的要求, 如果说你自己要做一个字库.那么这一段你可以自己发挥, 填充为一个中文的符号,笑脸,特别文字什么的.这些没有具体的要求.
同理.对于(88---94)*32, 你也可以自己发挥. 然后告知别人如何使用,因为这个没有标准, 所以一定要有特别的说明,别人才可可以使用.
在一般的HZK16当中, 最前16*32个节有表示两个大小的"A"及两个感叹号, 一个在圆内的"帅"字..大家可以仔细看一下,其它几个没作特别使用.
三.应用程序中进行汉这显示的处理
那么, 在以上我们谈了汉字的显示原理, 汉字字库的存放原理, 其实都是为了更方便的让我们自由使用..在实际小, 一个应用程序未必须要显示所有的汉字, 可能他仅须要显示1000个常用的汉字, 那么就可制作一个1000个常用的小型汉字字库, 即所需要的汉字库从250K降到32K左右了, 大大的减少了资源占用,使用上非常的灵活.
四. 在UCGUI中如何加入汉字显示的支持
UCGUI中没有汉字功能的支持, 但其实只要稍加改造, 我们就可以解决点阵汉字显示的问题.UCGUI中, 对于E文的显示, 也同样采用的是点阵的方式, 而且有8*8,6*8, 16*8, 16*16等各种点阵, 这里, 我们可以实现在设置显示16*16的E文字体时, 加上我们的汉字显示, 因为是同样的点阵, 我们不用任何改造, 只要有HZK16文件, 就可以在此E文字体下显示汉字了.
全部的改造基本上集中在这个函数内部.
- oid GL_DispLine(const char GUI_FAR *s, int Len, const GUI_RECT *pRect);
这个函数在GUICoreGUIChar.c 文件内部,要支持汉字显示, 那么必须改成如下形式.
- void GL_DispLine(const char GUI_FAR *s, int Len, const GUI_RECT *pRect) {
- /*
- Check if we have anything to do at all ...
- If the window manager has already set the clipping rect, it does not
- make sense to due this. So it makes sense only if
- a) The window manager is not used (-> Configuration)
- or
- b) The window manager is inactive (-> Memory device active)
- */
- if (GUI_Context.pClipRect_HL) {
- if (GUI_RectsIntersect(GUI_Context.pClipRect_HL, pRect) == 0)
- return;
- }
- if (GUI_Context.pAFont->pafEncode) {
- GUI_Context.pAFont->pafEncode->pfDispLine(s, Len);
- return;
- }
- #if (GUI_SUPPORT_UNICODE)
- {
- U8 c0;
- char UCActive=0;
- while (--Len >=0) {
- c0=*(U8*)s++;
- if (UCActive) {
- if (c0 == GUI_UC_ENDCHAR)
- UCActive = 0;
- else {
- U8 c1 = *(U8*)s++;
- Len--;
- GL_DispChar (GUI_DB2UC(c0, c1));
- }
- } else { /* Unicode not active */
- if (c0 == GUI_UC_STARTCHAR)
- UCActive = 1;
- else
- {
- //增加汉字支持所加的...
- if (c0&0x80 && (*(U8*)s)&0x80){
- char hz[3];
- hz[0]=c0;
- hz[1]=*(U8*)s;
- hz[2]=0;
- WriteHZ(0,0,hz,0);
- s++;
- }
- else
- GL_DispChar(c0);
- }
- }
- }
- }
- #else
- {
- U8 c0;
- while (--Len >=0) {
- c0=*(U8*)s++;
- //增加汉字支持所加的...2005-6-13 0:14:09
- if (c0&0x80 && (*(U8*)s)&0x80){
- char hz[3];
- hz[0]=c0;
- hz[1]=*(U8*)s;
- hz[2]=0;
- WriteHZ(0,0,hz,0);
- s++;
- }
- else{
- GL_DispChar(c0);
- }
- }
- }
- #endif
- }
处理汉字显示:
- int WriteHZ (int x, int y,const char *p,int color)
- {
- U16 c1,c2,rec;
- long l;
- char pixeldata[32];
- int BytesPerLine;
- GUI_DRAWMODE DrawMode = GUI_Context.TextMode;
- GUI_DRAWMODE OldDrawMode;
- if (handle<0 ) return 0;
- if (p==NULL) return 0;
- c1=(p[0]-0xa1)&0x07f;
- c2=(p[1]-0xa1)&0x07f;
- rec=c1*94+c2; //汉字库94*94的二维结构...
- l=rec*32L; //求字库偏移...
- lseek(handle,l,SEEK_SET);
- read(handle,pixeldata,32);
- BytesPerLine = 2;
- OldDrawMode = LCD_SetDrawMode(DrawMode);
- //半汉字点阵以二色位图方式绘出, 前景色/背景色
- LCD_DrawBitmap (GUI_Context.DispPosX, GUI_Context.DispPosY,
- HZSIZEX,HZSIZEY,
- 1, 1,
- 1, /*Bits per Pixel */
- BytesPerLine,
- (U8*)pixeldata,
- // NULL /* no palette means default palette */
- &LCD_BKCOLORINDEX //在csword的bc3.0版本中, 是用NULL, 但在此处要要修改, 表明此位图所用调色析 //为二色, 前景色与背景色...
- );
- LCD_SetDrawMode(OldDrawMode); /* Restore draw mode */
- GUI_Context.DispPosX += HZSIZEX;
- return 1;
- }
另外, 除了以上所讲的, 我在网上发现如下的一篇文章非常适合大家加强对汉字处理的理解. 汉字处理在DOS时代是一个比较热门的技术, 但在现在的WIN时代.没有什么人关注了, 但是在嵌入式开发了, 它还有一定的用武之地, 理解它还是有一定的帮助的.
一篇介绍汉字处理的文章, 非常不错, 很基础,很明白, 其分四节来讲汉字的基本原理.
http://www.vcer.net/showTip.jsp?tipid=2291