在路上
分类: 嵌入式
2014-06-08 01:21:11
1、在UCGUI中有两种类型字体, 一种是等宽字体(Monospaced Font),即字体当中所有字都是同一宽度,它在UCGUI中的相应结构体是GUI_FONT_MONO, 一种是均衡字体(Proportional font), 这种字体中的字都有自己独立的宽度, 字体内的每个字都可以有不同宽度, 它在UCGUI中的相应结构体是GUI_FONT_PROP, 对于等宽字体, 一般都是将所有字的点阵存放在一个数组中, 因为每个字都宽度相同. 对于均衡字体, 则要单独用数组来定义每个字符的点阵, 然后将每一个字符的宽高及点阵存为一个数组即字符信息(ucgui中对应结体为GUI_CHARINFO), 所有字符信息再存到一个数组当即称为字符集, 它包含每个字的字符信息(点阵高宽及一行占几个字节), 所谓一行占几个字节, 是指这个字体的点阵每一行有多少个字节, 它与宽度高度单位不同, 宽度高度的单位是象素数.
2.另外特别指出的是, 在等宽字体中不仅所有字符宽度相同,高宽也是相等的; 对于均衡字体, 不仅可以宽度不同, 高度也可以不同, 每一行有多少个字节自然也不同, 在均衡字体中每一个字符都单独定义之后才组成字体的字符集.
3.字符集的问题, 在UCGUI中每种字体含的字符集不同, 这个可以参看UCGUI手册中的"Standard Font"一章,这一章中对于字符集有如下描述:
ASCII: Only ASCII characters 0x20-0x7E (0x7F).[仅包含0x20-0x7E这个范围内的ansii字符]
1: ASCII characters and European extensions 0xA0 - 0xFF.[除0x20-0x7E这个范围内的ansii字符, 还有0xA0 - 0xFF这个范围内的欧洲字符集, 这里要指出美国英语只用到0x20-0x7E, 它只考虑了自己的须求, 没有考虑其它国家的须求, 在欧洲是有拉丁字符的, 所以欧洲国家扩展了剩余的0xA0 - 0xFF这个范围内的来表示欧洲的字符集, 其实我们国家的汉字也是在这个范围内扩展的, 不过我们用的是二个字节来表示一个汉字, 是因为汉字太多, 这区区94个值无法满足汉字的须求, 94*94就差不多了. 汉字用到的第一个值为0xb0a1(啊), 最后一个为0xf7fe(齄), 在机内码1当中只用到a1+15~a0+86这个范围的, 关于机内码是这个意思: 对于"啊"字模,机内码:(0xb0,0xa1), 0xb0为"啊"字的机内码1,0xa1为机内码2. 对于机内码2合使用是0xa1~0xff这个范围内的所有值, 关于机内码及汉字显示的原理及汉字库的构成, 本论坛中有专门的一篇文章介绍--"ucgui中处理汉字显示的说明", 请查看此贴, 这里不多说了, 所以范围是这样确定的.]
HK: Hiragana and Katakana[日文平假名与片假名].
1HK: ASCII, European extensions, Hiragana and Katakana[ansii,欧洲字符集,日文平假与片假].
D: Digit fonts[数字及运算符号集].
以上的ASCII/1/HK/1Hk/D都是字符集的简单代号.
3. 回过头来再看你的GUI_Font__21_Prop2,GUI_Font__21_Prop1. 那么很容易理解,GUI_Font__21_Prop2是欧洲字符集, 范围当然是0xa0-0xff. GUI_Font__21_Prop1中存的是ASCII字符集. 至于GUI_Font__21_CharInfo中则存的是全部的字符集的点阵信息, 包含所有字符信息. 最后, 将字体中包含的所有字符集用链表连接起来. 再将这个链表头指针存到字体结构(GUI_FONT)中的存放均衡字体的指针(const GUI_FONT_PROP* pProp)当中, 这样在处理字符的显示, 可以在这链表中查找所要显示的字符是在哪一个字符集中, 从而找到它的字符信息(即点阵数据及宽高).
typedef struct {
GUI_DISPCHAR* pfDispChar;
GUI_GETCHARDISTX* pfGetCharDistX;
GUI_GETFONTINFO* pfGetFontInfo;
GUI_ISINFONT* pfIsInFont;
tGUI_ENC_APIList* pafEncode;
U8 YSize;
U8 YDist;
U8 XMag;
U8 YMag;
union {//此联合处存放均衡或是待宽字符集信息...
void *pFontData;
const GUI_FONT_MONO* pMono;
const GUI_FONT_PROP* pProp;
} p;
U8 Baseline;
} GUI_FONT;
在GUI_Font__21_Prop1中的(void GUI_FLASH *)&GUI_Font__21_Prop2/* pointer to next GUI_FONT_PROP */
在GUI_Font__21_Prop2中的(void GUI_FLASH *)&GUI_Font__21_Prop3/* pointer to next GUI_FONT_PROP */
在GUI_FONT_MONO当中的成员next就是指向一下字符集的...
这个链表是人工写成的.....链表最后一个成员的next指向空....
这个链表的构造, 其实还是为了使用, 所以要理解它, 就要理解是如何用的.
均衡字体的显示, 是在GUIPROPAA_DispChar这个函数中处理的, 要理解链表的构造就要理解这个函数,下面做简要的分析....
void GUIPROPAA_DispChar(U16P c) {
int BytesPerLine;
GUI_DRAWMODE DrawMode = GUI_Context.TextMode;
const GUI_FONT_PROP* pProp = GUIPROP_FindChar(GUI_Context.pAFont->p.pProp, c);
if (pProp) {
GUI_DRAWMODE OldDrawMode;
const GUI_CHARINFO* pCharInfo = pProp->paCharInfo+(c-pProp->First);
BytesPerLine = pCharInfo->BytesPerLine;
OldDrawMode = LCD_SetDrawMode(DrawMode);
Draw ( GUI_Context.DispPosX, GUI_Context.DispPosY,
(pCharInfo->XSize+1)/2,
GUI_Context.pAFont->YSize,
BytesPerLine,
(U8 const*) pCharInfo->pData
);
LCD_SetDrawMode(OldDrawMode); /* Restore draw mode */
GUI_Context.DispPosX += (pCharInfo->XDist+1)/2;
}
}
而理解GUIPROPAA_DispChar的重点, 就是要理解它当中调用的用来寻找要显示的字符的字符信息的函数GUIPROP_FindChar, GUIPROP_FindChar主要是寻找字符所在的字符集(其实这个字符集在汉字应用当中,有些不同.
在hzk12.c中, 作者是将汉字接区来分集的, 下面我们以hzk12.c中的构造来讲解字符集链表:
hzk12.c 中共分成(0xa1a1~0xa1fe),(0xa2a1~0xa2fe)...(0xf7a1~0xf7fe)共分成86个字集, 另外加上(0x0020, 0x007f)这个ANSCII字符集, hzk12.c中的链表中就其有87个字符集, 这里的字符集的意义就不再是一个标准的字符集了, 而只能称之为字符的集合而已, 没有严格意义上的字符集的意思).
hzk12.c中, 字符集链表构成为: 字符集链表第一个元素为ascii字符集,第二个为机内码处于(0xa1a1~0xa1fe)间的汉字集, 最后一个为(0xf7a1,0xf7fe)....
了解了汉字库的这个字符集链表的构成, 那么现在来看一下如何寻找一个要显示的字符处于哪个字符集当中, 找了那个字符集才能找到这个字符的字符信息....
static const GUI_FONT_PROP* GUIPROP_FindChar(const GUI_FONT_PROP* pProp, U16P c) {
for (pProp = GUI_Context.pAFont->p.pProp; pProp; pProp=(const GUI_FONT_PROP*) pProp->pNext) {
if ((c>=pProp->First) && (c<=pProp->Last))
break;
}
return pProp;
}
点击(此处)折叠或打开
GUIPROP_FindChar 其实就是查找字符的机内码是位于哪个字符集之间, 比如寻找"啊"字, 机内码为0xb0a1,那么由上查找, 就可以知道它是位于链表中第16(0xb0-0xa1=16)个字符集(机内码处于0xb0a1~0xb0fe)当中, 那么就返回这个字符集的指针. 找到了要显示的字符所处的字符集, 再根据:
const GUI_CHARINFO* pCharInfo = pProp->paCharInfo+(c-pProp->First);
c-pProp->First即为该字符在此字符集中的偏移, pProp->paCharInfo为该字符集中第一个字符地址...
这样就找到了要显示的字符的字符信息了(宽高及点阵数等), 理解了这个过程, 那么反过来理解这个字符集为何要如此构造, 就比较容易了...
比如说: 为什么要将汉字分成86个字符集合? 这是由于汉字的机内码并没有用到所有的0xffff--xa1a1=0x5e5e中连续的值, 而是间断的, 0xa1a1~0xa1fe用到了, 0xa200~0xa2a1这段当中的值不能用(因为机内码2小于0xa1了), 只能用0xa2a1~0xa2fe, 所以这个特性决定了汉字的机内码分布是显区段的, 不能用一个单一的GUI_FONT_PROP结点来表示出所有的字符集, 因为汉字是区间分布的, 不是连续的.
比如说, 如下所示:
GUI_FLASH const GUI_FONT_PROP GUI_FontHZ12_Propa1= {
0xa1a1,
0xfefe,
&GUI_FontHZ12_CharInfo[ 96],
(void *)&GUI_FontHZ12_Propa2
};
用以下一个结点来表示所有汉字, 如同ACSII, 那么我们分析一下它为什么不可以:
首先对于区间(0xa1a1~0xa1fe)这第一个区间, 在以上的结构下, 这个区间内的字符还是能够正确找到所要显示的字符信息. 但对于(0xa2a1~0xa2fe)这个区间的, pProp->paCharInfo+(c-pProp->First)显然无法找到字符信息. 这是GUI_FontHZ12_CharInfo这个所有字符信息集数组的结构决定的, 因为汉字未用的区间的在它上面没有体现, 它上面存放的是将分隔开的汉字区间连在一起的, 这样你就无法根据pProp->paCharInfo+(c-pProp->First)来找到字符的位置了....
[ucgui原创]在UCGUI中增加汉字显示的说明.
在UCGUI中增加汉字显示的说明.
作 者: ucgui
email:
home:
版 本:
v1.0.0.1
UCGUI中本身只支持E,没有提供中文的字库的.C源码文件, 但是我们可以通过下面的方式来实现汉字的显示...
我们知道, 在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);
所以汉字的区内码,机内码,偏移的信息,请下载这个文件查看.
其中,区位码(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);
这个函数在GUI/Core/GUIChar.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
{
//增加汉字支持所加的...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);
}
}
}
}
#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时代.没有什么人关注了, 但是在嵌入式开发了, 它还有一定的用武之地,
理解它还是有一定的帮助的.
一篇介绍汉字处理的文章, 非常不错, 很基础,很明白, 其分四节来讲汉字的基本原理.