原创 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。

文章评论1条评论)

登录后参与讨论

用户377235 2012-10-31 14:44

高手
相关推荐阅读
用户6646 2010-06-09 16:44
Linux C中令人讨厌的段错误
作者:孙晓明,华清远见嵌入式学院讲师同学们在做练习的时候,编译完程序,执行的时候,有时会莫名的出现 “Segment fault”,即段错误,段错误是让许多C程序员都头疼的提示,因为对于这种模糊的提示...
用户6646 2010-06-09 13:06
VC6 显示行号 (无限制注册版)
make编译时出现错误, 会提示哪一行。可惜vc中不能显示行号,很是郁闷。这个插件可以显示行号,呵呵,很有用。VC显示行号插件说明-----------------------------------...
用户6646 2010-05-22 10:07
删除 nero search 的有效方法
不知道从什么时候开始,Nero多出来一个绑定的Nero Search,虽然可以不然它显示,可是他仍然会在后台占用资源。可以用以下方法解决:开始->运行,依次输入下列文字回车、确定即可(每次输一行...
用户6646 2010-04-03 08:46
2007年5月22日完成的全电控小电视(版本V2.0)
基础功能部分写了4000多行汇编,加上遥控红外部分达到了6000多行,大体介绍如下:1. 内置开关电源转换,11-37V供电;2. 全轻触操作,无机械可调器件,PWM调节亮度/色彩/背光;3.按钮有:...
用户6646 2010-03-17 09:41
三极管HFE与β的关系
hfe是三极管H参数,全称“共发射极低频小信号输出交流短路电流放大系数”,在等效四端网络中又叫“h21”。β是Ic与Ib函数关系的普遍表达式,尤其特指在晶体管基区中电流的分配关系。无论在教科书还是在应...
用户6646 2010-03-17 09:35
三极管饱和(2)
from:http://blog.ednchina.com/xcbao/10816/category.aspx本图片来自于<模拟集成电路的分析与设计>,用来表现三极管饱和时的carriers的分布。但...
我要评论
1
3
关闭 站长推荐上一条 /2 下一条