国标汉字字符集(GB2312-80)在汉字操作系统中以汉字库的形式提供,并对汉字库的结构做了统一规定。汉字库的结构如图:
HZK16的GB2312-80支持的汉字有6763个,符号682个。字库有94个区,其中一级汉字有3755个,按声序排列,二级汉字有3008个,按偏旁部首排列。每个区有94个位,每个位存一个汉字。这样每个汉字在汉字库中有确定的区号和位号。区号在前,位号在后,合成一个4位十进制数字,这就是区位码。区位码用两个字节存放(GB2312汉字是由两个字节编码的,范围为A1A1~FEFE。A1-A9为符号区,B0到F7为汉字区),第一个字节表示区号,第二个字节表示位号。只要知道了区位码,就可以知道该字在字库中的地址。
16×16点阵库中,每个点阵模用32个字节来描述,其中的每个点使用一个二进制位。当需要显示时,把某个汉字的16×16点阵信息直接送到显示器上,值为1的点在屏幕上显示一个亮点,值为0的点则不亮,这样就可以显示出相应的汉字。
在国标字库中,每个汉字还可以使用国标码。国标码与区位码之间的换算关系:
国标码的区号=区位码的区号+32(或20H)
国标码的位号=区位码的位号+32(或20H)
或 区码=区号-0xa0 (因为汉字编码是从0xa0区开始的,所以文件最前面就是从0xa0区开始,要算出相对区码)
位码=位号-0xa0
此外还有汉字内码。汉字内码是汉字信息处理系统内部表示汉字的编码,也称机内码。西文字符的机内码多采用一个字节来表示的ASCII码,有的系统则采用EBCDIC码。一般只使用7位来表示128个字符,而把高位用作奇偶校验(或者不用)。我国的国标GB2312-80规定,一个汉字用两个字节表示,目前规定每个字节也只用七位,其高位未作定义。为了保证系统的中西文兼容,意味着系统的机内码中必须保持ASCII(IBM-PC采用该码作为西文字符的机内码)的使用,同时又要允许汉字机内码的使用,并且使两者之间没有冲突。如果用GB2312-80中的国标码作为机内码,则在系统中同时存在ASCII码和国标码时,将会产生二义性。例如,机内有两个字节的内容分别为30H和21H,它们既可以表示汉字“啊”的国标码,又可以表示字符“0”和“!”的ASCII码。所以,原原本本地采用国标码作为汉字机内码是不行的,必须要加以适当的变换。
国标码规定,组成两字节代码的各字节的最高位均为0,但在机内码表示汉字时,应将每个字节的最高位置1,以表示该编码是汉字,这种编码称作为变形国标码。这样作既解决了西文机内码与汉字机内码的二义性,又保证汉字机内码与国标码之间有极简单的对应关系。
国标码两字节的最高位分别加1后,便得到机内码。“计”字机内码如图:
这样国标码与机内码存在一种简单的对应关系:
机内码区号=国标码区号+128(或80H)
机内码位号=国标码位号+128(或80H)
因为知道了某汉字的内码,即可确定出对应的区位码,知道了区位码,就可以找出该汉字字模在字库中的存放地址,从该地址中调出对应汉字的32个字节的字模信息,就可以显示出16×16点阵组成的该汉字了。
从上文的分析可知,汉字内码与区位码存在固定的转换关系,设某汉字内码的十六进制数表示为0xkkjj,则相应区位码的区号qh和位号wh分别为:
qh=0xkk-0xa0;
wh=0xjj-oxa0;
若用十进制数表示内码为c1c2,则
qh=c1-160;
wh-c2-160;
即区位码qw=100*(c1-160)+(c2-160);相反要是知道了区位码qw,则也可以求得区号和位号:
qh=qw/100;
wh=qw-100*qh;
因而该汉字在汉字库中离起点的偏移位置(以字节为单位),可有表达式offset=(94*(qh-1)+(wh-1))*32L(32L为长整形数)计算,其中假设int qh,wh,qw,long offset。
对于16×16点阵汉字,其字模大小为(16×16)/8=32个字节,其输出时的排列方式如图:
几种常用的汉字库中地址码offset的计算公式:
1、ucdos中的字库CCLIB.DAT存放16×16点阵字模:
offset=((qh-1)*94+(wh-1))*32L;
2、CCDOS 2.13中的字库HZK16存放16×16点阵字模:
offset=((qh-16)*94+wh-1+15*94)*32L;
3、SPDOS 5.0的简单字库CCLIB.DAT存放16×16点阵字模:
offset=((qh-7)*94+wh-1)*32L;
4、CCDOS 2.13中字库hzk24存放24×24点阵字模:
offset=((qh-16)*94+wh-1)*72;
下面以一个小示例显示汉字:
#include <stdio.h> int main(void) { char chs[32]; FILE *fp; int i,j; unsigned long offset; unsigned char hz[]="计"; unsigned char qh,wh; qh=hz[0]-0xa0;//获取区吗 wh=hz[1]-0xa0;//获取位码 /* //int a=hz[0]; //int b=hz[1]; //printf("%x%x",a,b); //hz[0] hz[1]分别为汉字的机内码 //offset=((hz[0]-0xa1)*94+(hz[1]-0xa1))*32;//注意0xa1十进制数为161 */ offset=((qh-1)*94+(wh-1))*32;//根据内码找出汉字在HZK16中的偏移位置 if ((fp=fopen("HZK16","r"))==NULL) return 1; fseek(fp,offset,SEEK_SET); fread(chs,32,1,fp); for (i=0;i<32;i++) { if (i%2==0) printf("\n"); //每行两字节,16X16点阵 for (j=7;j>=0;j--) { if (chs[i]&(0x01<<j)) /* chs[i] & 0x80 判断第7位 chs[i] & 0x40 判断第6位 chs[i] & 0x20 判断第5位 chs[i] & 0x10 判断第4位 chs[i] & 0x08 判断第3位 chs[i] & 0x04 判断第2位 chs[i] & 0x02 判断第1位 chs[i] & 0x01 判断第0位 */ printf("计"); //由高到低,为1则输出'字',反之输出' '; else printf(" "); } } fclose(fp); return 0; }