原创
C语言读取汉字字模
2008-10-31 23:02
4584
3
4
分类:
软件与OS
前不久,在网上看到一个生成点阵字的网站。觉得很有意思!
到底什么是点阵字,点阵字和字模之间有什么关系?
让我们先看一个点阵汉字和一个英文字母:
**
**************************
**
**
**
**
** **
**************************
**
**
**
**
**
** **
******************************
........
...#....
..###...
.##.##..
##...##.
##...##.
#######.
##...##.
##...##.
##...##.
##...##.
........
........
........
........
........
这就是点阵字,也就是根据字符的字模用符号画出来的,当然你可以把*号#号改成其它的任何符号都可以。
是不是觉得很有意思了?
汉字内码:
我们都知道,英文只有少数的几十个字符,在计算机中用一个字节可以很容易的表示出来(也就是ASCII码);而汉字由于结构本身的原因,数量很大,常用的也有几千个。显然计算机中按照英文字符的方式对处理汉字是不可取的。
由是前人们就将ASCII表的高128位很少用到的数值以两个为一组来表示汉字,这就是汉字的内码。而剩下的低128位则留给英文字符使用,即英文的内码。
看一个C程序示例:
程序代码
main()
{
unsigned char *s,*e="A",*c="王";
clrscr();
printf("English char =");
s=e;
while(*s!=0) /*C的字符串以0为结束符*/
{
printf("%3d,",*s);
s++;
}
printf("\nChinease char=");
s=c;
while(*s!=0)
{
printf("%3d,",*s);
s++;
}
getch();
}
编译运行以后,输出的结果为:
English char = 65,
Chinease char= 205,245,
查ASCII码字符表,很容易得到A的ASCII码为65。
我们可以查ASCII码表,得到出ASCII码对应的字符,那我们有什么办法来知道一个汉字内码对应的汉字了?
让我们先来认识一下区位码:
1981年5月,我国国家标准总局颁布了《信息交换用汉字编码字符集》(GB2312-80),简称国家标准汉字编码,也叫国标码。国标码共收进标准字符7445个。其中一级汉字3755个,二级汉字3008个,共计6763个汉字。 由于汉字的字符多,一个字节(即8位二进制代码)不足以表示所有的常用汉字。汉字国标码的每个汉字或符号在计算机中都使用2个字节(16位二进制)代码来表示。
在GB2312-80代码表中,纵向分为0~93,共94行。将行号称为区号,列号称为位号,分别有94个区和94个位。区号和位号用十进制表示,不足两位前面补0。这样每个汉字或符号都可用4位十进制表示。这就是我们常说的区位码。每一区共有94个汉字,而位记录该汉字在该区中的具体位置。(记得我们以前读书的时候,报考计算机考试,填写姓名都要我们用区位码填,我们都拿着自己的姓名一个个去查,我们查的那个东东就是区位码。现在想想,真是心寒呀。还好,我后来就没有查了,弄了个excel的宏。把班上人的姓名全部放到一个excel中,然后一点鼠标,哈哈…… 区位码全自动出来了)。
现在我们知道,可以从区位码得到汉字,也可以从汉字反查出区位码。那么我们如何从内码得到区位吗?
汉字内码与区位码之间有一个简单的数学关系:
内码高字节 = 区码+A0H = 区码+160
内码低字节 = 位码+A0H = 区码+160
这个转换关系,我也不清楚原因,有谁知道还望指点。网上的大师们说这样转换,咱们就这样转换吧。
我们刚刚输出“王”的内码为:205(高字节),245(低字节)。由上面的换算关系,可以得到“王”字的区位码为:
区码=205-160=45
位码=245-160=85
查一下区位码表,4585所表示的汉字正好是“王”。
也可以打开输入法,选择内码输入法,然后选择区位码,输入4585,就会输出“王”字。
汉字字模:
现在让我们来认识一下什么是字模,所谓字模就是是汉字(或者字符)的形态。字模中保存了汉字的点阵信息,记录组成一个字符的点在何处显示,在何处不显示。我们只要得到汉字的字模,我们就可以很容易的程序来控制,把这个字符画出来。
我们刚刚得到的仅仅是汉字的内码,并根据汉字内码得到区位码,由区位码查表得到汉字。那么我们如何来得到汉字的字模了?
用过UCDOS(或者CCDOS,估计现在只有少数人还知道UCDOS是什么东东)的人应该知道,通过UCDOS可以让DOS系统下正确的显示中文目录。不通过UCDOS之类的软件,在纯DOS下,我们看到的中文目录会是一堆的乱码,而英文目录能够正确显示,这是什么原因了。
这是因为,英文的字模信息是一般固化在ROM里。中文字模信息一般记录在一个专门的文件中,这个文件在UCDOS和CCDOS中都有,文件名是HZK16。也就是16x16点阵的汉字字模信息,所谓16x16,就是说这个汉字在横向有16个点,和纵向16个点的区域里显示。还有24x24,32x32等。
我们也可以在UCDOS下找到英文字模的信息文件,文件名是ASC16,这里记录了英文字符的字模信息。ASC16文件记录的英文字符是8x16点阵的。这些记录字符字模信息的文件通常也叫字库文件。
ASC16文件的大小刚好为 4K (4,096 个字节),每一英文字符横向有8个点,纵向有16个点。也就是说要描述一个英文字符的点阵信息,必须要16*8=128bit=16Byte。而英文字符是一个字节表示,所能表示的最字符数为2的8次方,也就是256个字符(ASCII中是从0到255)。256个字符*16(每个字符要16个字节) = 4096 字节。 刚好为ASC16文件的大小。因此,我们要读取英文字模的信息,我们就先得到这个字符的ASCII码。
以得到字符"A"的字模信息为例:
假如我们要得到的字母A的字模信息,我们得到"A"的ASCII值为65,我们就可以算出字符"A"的字模信息在ASC16文件中的偏移量=(65*16)+1=1041字节(注意这个数字,我们将在后面用程序进行验证),我们只需要从ASC16文件中1041字节开始读取16个字节就可以得到"A"的字模信息了。
同样,我们来看一下汉字字模。
汉字是16*16的所以描述一个汉字字模信息的大小为:16*16=256bit=32Byte,汉字是按照区位码的顺序来排列的。
我们以得到”王“字的字模信息为例:
我们先得到”王“字的内码为:205,245,根据内码与区位码的转换关系得到”王“字的区位码为:45,85。
由前面区位码介绍中,我们知道,每一区有94个汉字,位号表示在该区的位置。因此“王”字中区位码中的位置为:
94*(区号-1) + (位号-1) = 94*((45-1)+(85-1)) = 4220。
而每一个汉字占32个字节,因此我们得到“王”字在字库文件(HZK16)中的偏移量为:4220*32=135040字节(注意这个数字,我们在后面将用程序进行验证)。我们只需要从HZK16文件中135040字节开始读取32个字节就可以得到“王”字的字模信息了。
完整的原程序代码如下:
程序代码
/**********************************
* C 程序得到汉字字模信息
* by DreamTime [梦想年华]
* fanwsp@126.com
* www.FreeAge.cn
* 2007-11
***********************************/
#include "stdio.h"
/**********************************
* 得到英文字符的字模信息,存入数组
* 参数:
* *c:要得到字模信息的字符指针
* buffer[]:存储得到字模信息的数组
* 无返回值
***********************************/
void getAscCode(char *c,char buff[])
{
unsigned long offset;
FILE *ASC;
/*打开字库文件asc16*/
if((ASC=fopen("asc16","rb"))==NULL){
printf("Can't open asc,Please add it?");
getch();
exit(0);
}
offset = *(c)*16+1; /*通过ascii码算出偏移量*/
fseek(ASC,offset,SEEK_SET); /*将文件指针移动到偏移量的位置*/
fread(buff, 16, 1, ASC); /*从偏移量的位置读取32个字节*/
printf("ASCII:%d,offset:%d \n\r",*c,offset);
}
/**********************************
* 得到汉字字符的字模信息,存入数组
* 参数:
* *c:要得到字模信息的字符指针
* buffer[]:存储字模信息的数组
* 无返回值
***********************************/
void getHzKCode(char *c,char buff[])
{
unsigned char qh,wh;
unsigned long offset;
FILE *HZK;
/*打开字库文件hzk16*/
if((HZK=fopen("hzk16","rb"))==NULL){
printf("Can't open haz16,Please add it?");
getch();
exit(0);
}
/*区码=内码(高字节)-160 位码=内码(低字节)-160*/
qh = *(c) -0xa0; /*10进制的160等于16进制的A0*/
wh = *(c+1) -0xa0; /*获得区码与位码*/
offset = (94*(qh-1)+(wh-1))*32L;/*计算该汉字在字库中偏移量*/
fseek(HZK,offset,SEEK_SET); /*将文件指针移动到偏移量的位置*/
fread(buff,32,1,HZK); /*从偏移量的位置读取32个字节*/
printf("qh:%d,wh:%d,offset:%ld\n\r",qh,wh,offset);
}
/**********************************
* 根据字模信息输出英文字符
* 参数:
* *mat:字模指针
* *c1 :字模中为1的点显示的字符,也就是前景字符
* *c2 :字模中为0的点显示的字符,也就是背景字符
* 无返回值
***********************************/
void printAscChar(char *mat,char *c1,char *c2)
{
int i,j;
for(i=0;i<16;i++) /* 8x16的点阵,一共有16行*/
{
for(j=0;j<8;j++) /*横向一个字节8位,依次判断每位是否为0*/
if(mat[i]&(0x80>>j)) /*测试当前位是否为1*/
printf("%s",c1); /*为1的显示为字符c1*/
else printf("%s",c2); /*为0的显示为字符c2*/
printf("\n"); /*输完一行以后,进行换行*/
}
}
/**********************************
* 根据字模信息输汉字字符
* 参数:
* *mat:字模指针
* *c1 :字模中为1的点显示的字符,也就是前景字符
* *c2 :字模中为0的点显示的字符,也就是背景字符
* 无返回值
***********************************/
void printHzKChar(char *mat,char *c1,char *c2)
{
int i, j, k;
for(i=0;i<16;i++) /*16x16点阵汉字,一共有16行*/
{
for(j=0;j<2;j++) /*横向有2个字节,循环判断每个字节的*/
for(k=0;k<8;k++) /*每个字节有8位,循环判断每位是否为1*/
if(mat[i*2+j]&(0x80>>k)) /*测试当前位是否为1*/
printf("%s",c1); /*为1的显示为字符c1*/
else printf("%s",c2); /*为0的显示为字符c2*/
printf("\n"); /*输完一行以后,进行换行*/
}
}
/**********************************
* 主函数
***********************************/
void main()
{
unsigned char *AscC = "A";
unsigned char *AscC1 = "*";
unsigned char *AscC2 = " ";
unsigned char *HzkC = "王";
/*汉字占两个字节,前景字符和背景字符都要有两个英文字符或一个中文字符,否则字体将变形*/
unsigned char *HzkC1 = "**";
unsigned char *HzkC2 = " ";
char *asc;
char *hzk;
char buffer1[16]; /*存储英文字模信息*/
char buffer2[32]; /*存储中文字模信息*/
/*输出英文字符*/
getAscCode(AscC,buffer1);
asc = buffer1;
printAscChar(asc,AscC1,AscC2);
/*暂停一下*/
getch();
clrscr();
/*输出中文字符*/
getHzKCode(HzkC,buffer2);
hzk = buffer2;
printHzKChar(hzk,HzkC1,HzkC2);
getch(); /* 暂停一下 */
}
运行结果如下:
这是运行以后输出的界面。我们可以看到输出了“A”的ASCII为65,偏移量为:1041,与我们前面算出来的结果完全吻合。
按任意键,程序将输出中文字符信息:
我们可以看到,输出了“王”的区位码为:45,85,偏移量为:135040,与我们前面算出来的结果一样的。
到此,我们读取字模的信息就已经完成了,知道了原理,你也可以用画图的方式来显示汉字。
本文只是自己对于字模的一些肤浅的认识,有什么错误的地方,还望各位同仁给予批评指正。
点击下载源文件、ASC16及HZK16字库文件
re:
区位码->内码A0的原因:
GB2312-80将代码表分为94个区(Section),对应第一字节;每个区94个位(Position),对应第二字节。两个字节的值,分别为区号值和位号值各加 32(20H)。
GB码的编码范围为2121H~777EH,与ASCII码有重叠,同时使用会有冲突,通常将GB码两个字节的最高位均置1(MSB=1),以示区别。实际应用的GB码是GB码高位置1后的变形码,编码范围为0A1A1H~0F7FEH。
用户377235 2012-10-31 14:44