汉字库,字模的了解

汉字库,字模的了解

标签:  技术类 
2007-09-04 21:50阅读( 726) 评论(0)
    由于Turbo C应用于DOS操作系统下,在使用Turbo C进行程序设计时,一般情况下只好使用英文进行人机交互。要是想直接用中文界面,就需要另想他法了。
  如果使用中文DOS系统(如UCDOS),则可以解决在字符界面下的汉字显示问题。也就是说,可以用printf或其他字符串函数来输出汉字。
  但是,这样仍然有一些不方便。必须先启动中文DOS系统,再执行Turbo C或编译好的程序。并且在中文版DOS下运行Tubor C时,还可能出现一些问题。而对于图形界面来说,这种办法也行不通了。
  那么在图形界面下显示汉字的问题就迫切需要解决了。既然是图形界面,只要把汉字当成一幅画,画在显示屏上不就可以了。关键在于如何取得汉字的图形,也就是汉字的点阵字模呢。其实那些中文版的DOS显示汉字的方式也就是在图形界面下画出汉字的,它们已经提供了现成的点阵字库文件。例如常用的16×16点阵HZK16文件,12×12点阵HZK12文件等等,这些文件包括了GB 2312字符集中的所有汉字。现在只要弄清汉字点阵在字库文件中的格式,就可以按照自己的意愿去显示汉字了。
  下面以HZK16文件为例,分析取得汉字字模的方法。
  HZK16文件是按照GB 2312-80标准,也就是通常所说的国标码或区位码的标准排列的。国标码分为 94 个区(Section),每个区 94 个位(Position),所以也称为区位码。其中01~09 区为符号、数字区,16~87 区为汉字区。而 10~15 区、88~94 区是空白区域。
  如何取得汉字的区位码呢?在计算机处理汉字和ASCII字符时,使每个ASCII字符占用1个字节,而一个汉字占用两个字节,其值称为汉字的内码。其中第一个字节的值为区号加上32(20H),第二个字节的值为位号加上32(20H)。为了与ASCII字符区别开,表示汉字的两个字节的最高位都是1,也就是两个字节的值都又加上了128(80H)。这样,通过汉字的内码,就可以计算出汉字的区位码。
  具体算式如下:
  qh=c1-32-128=c1-160    wh=c2-32-128=c2-160
  或   
  qh=c1-0xa0    wh=c2-0xa0
  qh,wh为汉字的区号和位号,c1,c2为汉字的第一字节和第二字节。
  根据区号和位号可以得到汉字字模在文件中的位置:
  location=(94*(qh-1)+(wh-1))*一个点阵字模的字节数。
  那么一个点阵字模究竟占用多少字节数呢?我们来分析一下汉字字模的具体排列方式。
  例如下图中显示的“汉”字,使用16×16点阵。字模中每一点使用一个二进制位(Bit)表示,如果是1,则说明此处有点,若是0,则说明没有。这样,一个16×16点阵的汉字总共需要16*16/8=32个字节表示。字模的表示顺序为:先从左到右,再从上到下,也就是先画左上方的8个点,再是右上方的8个点,然后是第二行左边8个点,右边8个点,依此类推,画满16×16个点。  
 

  对于其它点阵字库文件,则也是使用类似的方法进行显示。例如HZK12,但是HZK12文件的格式有些特别,如果你将它的字模当作12*12位计算的话,根本无法正常显示汉字。因为字库设计者为了使用的方便,字模每行的位数均补齐为8的整数倍,于是实际该字库的位长度是16*12,每个字模大小为24字节,虽然每行都多出了4位,但这4位都是0(不显示),并不影响显示效果。 还有UCDOS下的HZK24S(宋体)、HZK24K(楷体)或HZK24H(黑体)这些打印字库文件,每个字模占用24*24/8=72字节,不过这类大字模汉字库为了打印的方便,将字模都放倒了,所以在显示时要注意把横纵方向颠倒过来就可以了。
  这样我们就完全清楚了如何得到汉字的点阵字模,这样就可以在程序中随意的显示汉字了。
  如果在程序中使用的汉字数目不多,也可以不必总是在程序里带上几百K的字库文件,也许你的程序才只有几十K。这样可以事先将所需要显示的汉字字模提取出来,放在另一个文件里,按照自己的顺序读取文件就可以了。
  下面的程序说明了具体显示汉字的方法,以16×16汉字为例,使用HZK16文件。

#i nclude
#i nclude

/* x,y为显示坐标,s为显示字符串,colour为颜色 */
void hanzi16(int x,int y,char *s,int colour)
{
 FILE *fp;
 char buffer[32];                   /* 32字节的字模缓冲区 */
 register i,j,k;
 unsigned char qh,wh;
 unsigned long location;
 if((fp=fopen("hzk16","rb"))==NULL)
  {
   printf("Cant open hzk16!");
   getch();
   exit(0);
  }
 while(*s)
  {
   qh=*s-0xa0;
   wh=*(s+1)-0xa0;
   location=(94*(qh-1)+(wh-1))*32L;    /* 计算汉字字模在文件中的位置 */
   fseek(fp,location,SEEK_SET);
   fread(buffer,32,1,fp);
   for(i=0;i<16;i++)
    for(j=0;j<2;j++)
     for(k=0;k<8;k++)
      if(((buffer[i*2+j]>>(7-k))&0x1)!=NULL)
       putpixel(x+8*j+k,y+i,colour);
   s+=2;
   x+=16;                              /* 汉字间距 */
  }
 fclose(fp);
}

main()
{
 int gd=DETECT,gm;
 initgraph(&gd,&gm,"");

 hanzi16(246,200,"疯狂甲虫乐园!",BROWN);

 getch();
 closegraph();
}

 

DOS下的点阵汉字显示

         这里以 HZK16 文件为例,分析取得汉字字模的方法。

         HZK16 文件是按照国标码的标准排列的。国标码分为 94 个区,每个区 94个位(Position),所以也称为区位码。

         汉字占用两个字节,其中第一个字节的值为区号加上 32(20H),第二个字节的值为位号加上32(20H)。为了与ASCII字符区别开,表示汉字的两个字节的最高位都是1,也就是两个字节的值都又加上 了 128(80H)。这样,通过汉字的内码,就可以计算出汉字的区位码。具体算式如下:

qh=c1-32-128=c1-160,wh=c2-32-128=c2-160

qh,wh为汉字的区号和位号,c1,c2为汉字的第一字节和第二字节。根据区号和位号可以得到汉字字模在文件中的位置:

location=(94*(qh-1)+(wh-1))*一个点阵字模的字节数。

         一个点阵字模究竟占用多少字节数是看其点阵数来确定的。如使用16×16点阵,字模中每一点使用一个二进制位(Bit)表示,如果是1,则说明此处有点,若是0,则说明没有。这样,一个16×16点阵的汉字总共需要16*16/8=32个字节表示。

         下面是一些例子(特别说明:如果发现例子不能运行,请与站长联系。)

#include <io.h>
#include <stdio.h>
#include <dos.h>
#include <fcntl.h>
#include <graphics.h>
#include <conio.h>

#define Byte unsigned char
#define Word unsigned int

int flag16,flag24;
void dishz();

main()
{
int Driver=DETECT,Mode;
registerbgidriver(EGAVGA_driver);
initgraph(&Driver,&Mode,"");
directvideo=0;
setbkcolor(1);
cleardevice();
dishz();
getch();
closegraph();
}

/*========================================================================*/
/*------------------------------16×16点阵汉字----------------------------*/
/*========================================================================*/

int out16hz(int x,int y,int z,int color,char *p)/* x,y起点坐标,z字间距 */
{
Word num,QM,WM,flag=0; /* QM,WM 区号和位号 */
int record,i,j,k;
long pointer;
char Bit[32]; /* 16点阵的汉字占32个字节 */
while((num=*p++)!=0)
{
   if(num>0xA1)
    if(flag==0) /* 注意一个汉字是两个字节的,这里是第一个字节 */
    {
     QM=(num-0xA1)&0x07F; /* 注意这里是减161了 */
     flag=1;
    }
    else   /* 阅第二个字节 */
    {
     WM=(num-0xA1)&0x07F; /* 与上 0x07F 再检测一次 */
     flag=0;
     record=QM*94+WM;
     pointer=record*32L; /* 定位出汉字字模在文件中的位置 */
     lseek(flag16,pointer,SEEK_SET);
     read(flag16,Bit,32);
     for(i=0;i<16;i++) /* 写点 */
      for(j=0;j<2;j++)
       for(k=0;k<8;k++)
        if(((Bit[i*2+j]>>(7-k))&0x1)!=NULL)
         putpixel(x+j*8+k,y+i,color);
     x=x+z+16;
    }
}
}

/*========================================================================*/
/*------------------------------24×24点阵汉字----------------------------*/
/*========================================================================*/

int out24hz(int x,int y,int z,int color,int m,int n,char *p)
{
Word num,QM,WM,flag=0;
int i,j,k,width,height,record;
long pointer;
char Bit[72];
while((num=*p++)!=0)
{
   if(num>0xA1)
    if(flag==0)
    {
     QM=(num-15-0xA1)&0x07F;
     flag=1;
    }
    else
    {
     WM=(num-0xA1)&0x07F;
     flag=0;
     record=QM*94+WM;
     pointer=record*72L;
     lseek(flag24,pointer,SEEK_SET);
     read(flag24,Bit,72);
     for(i=0;i<24*m;i=i+m)
      for(width=0;width<m;width++)
       for(j=0;j<=2;j++)
        for(k=0;k<8;k++)
         if(((Bit[i/m*3+j]>>(7-k))&0x1)!=NULL)
          for(height=0;height<n;height++)
           putpixel(x+i+width,y+j*8*n+k*n+height,color);
     x=x+24*m+z;
    }
}
}

/*========================================================================*/
/*---------------------------------输出演示-------------------------------*/
/*========================================================================*/

void dishz()
{
Byte *str1[]={"16点阵汉字输出演示"};
Byte *str2[]={"24点阵汉字输出演示"};

int x=200,y=50,size=10;
flag16=open("c:\\ucdos\\HZK16F",O_RDWR|O_BINARY);
flag24=open("c:\\ucdos\\HZK24F",O_RDWR|O_BINARY);
if(flag16==-1)
{
   printf("Cannot Open HZK16\n");
   exit(1);
}
if(flag24==-1)
{
   printf("Cannot Open HZK24\n");
   exit(1);
}
out16hz(x,y,size,15,*str1);
y+=30;
out24hz(x,y,size,10,1,1,*str2);
x=100;y+=50;
x=200;y+=50;
out24hz(x,y,size,12,1,2,*str2);
x=100;y+=80;
out24hz(x,y,size,9,2,2,*str2);
close(flag16);
close(flag24);
}

 

 

DOS下的点阵汉字

  你是否碰到过用启动盘启动系统后用DIR命令得到一串串莫名其妙的字符?有经验的朋友会告诉你:那是汉字。汉字?你不禁会问:怎么一个我一个也不认识。但那确确实实是汉字,如果你启动UCDOS或其他的汉字系统后,就会看到那是一个个熟悉的汉字。同样是汉字,为什么前后会看到不同的结果?呵呵,其实在电脑硬件中,根本没有汉字这个概念,也没有英文的概念,这铁玩意认识的概念只有——内码。

汉字的内码

  点头表示什么?是“对”、“YES”,偏偏有的地方表示的意义却恰恰相反。一个动作,有不同的诠释;一个问题,有不同的答案;而一个符号,却有不同的意义,关键在于:你是如何地理解。在电脑中亦如此,所有的数据都是以0和1保存的,按不同的数据操作,可以得到不同的结果。对于显示英文操作,由于英文字母种类很少,只需要8位(一字节)即可。而对于中文,常用却有5000以上,于是我们的DOS前辈想了一个办法,就是将ASCII表的高128个很少用到的数值以两个为一组来表示汉字,即汉字的内码。而剩下的低128位则留给英文字符使用,即英文的内码。不信,你可以用记事本写一C文件:

main()
{
    unsigned char *s,*e="ABcd",*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();
}

再用TC输入*.txt打开运行,看见了没有,那些数值即英文和汉字的各字节内码。

汉字字模

  得到了汉字的内码后,还仅是一组数字,那又如何在屏幕上去显示呢?这就涉及到文字的字模,字模虽然也是一组数字,但它的意义却与数字的意义有了根本的变化,它是用数字的各位信息来记载英文或汉字的形状,如英文的'A'在字模中是这样记载的:

而中文的“你”在字模中却是这样记载的:

  在硬件系统内,英文的字模信息一般固化在ROM里,即使在没有进入系统的CMOS里,也可以让你看到英文字符。而在DOS下,中文的字模信息一般记录在汉字库文件HZK16里。

汉字库文件

  了解字母和汉字是按字模位信息显示的原理后,那如何得到汉字的字模信息呢?难道要我们自己去做?NO。DOS前辈们经过艰辛的努力,将制作好的字模放到了一个个标准的库中以免去后辈的麻烦,这就是点阵字库文件。一般我们使用16*16的点阵宋体字库,所谓16*16,是每一个汉字在纵、横各16点的区域内显示的。不过后来又有了HZK12、HZK24,HZK32和HZK48字库及黑体、楷体和隶书字库。虽然汉字库种类繁多,但都是按照区位的顺序排列的。前一个字节为该汉字的区号,后一个字节为该字的位号。每一个区记录94个汉字,位号则为该字在该区中的位置。因此,汉字在汉字库中的具体位置计算公式为:94*(区号-1)+位号-1。减1是因为数组是以0为开始而区号位号是以1为开始的。这仅为以汉字为单位该汉字在汉字库中的位置,那么,如何得到以字节为单位得到该汉字在汉字库中的位置呢?只需乘上一个汉字字模占用的字节数即可,即:(94*(区号-1)+位号-1)*一个汉字字模占用字节数,而按每种汉字库的汉字大小不同又会得到不同的结果。以16*16点阵字库为例,计算公式则为:(94*(区号-1)+(位号-1))*32。汉字库文该从该位置起的32字节信息即记录了该字的字模信息。

汉字库文件

  了解点阵汉字及汉字库的构成原理后,显示汉字就变得简单。以16*16点阵字库为例,通常的方法是:将文件工作指针移到需要的汉字字模处、将汉字库文件读入一2*16数组再用for循环一位位地显示。以使用VGAHI模式显示“我”字为例,程序如下:
#include "graphics.h"
#include "stdio.h"
main()
{
    int i=VGA,j=VGAHI,k;
    unsigned char mat[16][2],chinease[3]="我";
    FILE *HZK;
    if((HZK=fopen("hzk16","rb"))==NULL)
        exit(0);
    initgraph(&i,&j,"");
    i=chinease[0]-0xa0;j=chinease[1]-0xa0; /*获得区码与位码*/
    fseek(HZK,(94*(i-1)+(j-1))*32,SEEK_SET);
    fread(mat,32,1,HZK);

    for(j=0;j<16;j++)
        for(i=0;i<2;i++)
            for(k=0;k<8;k++)
                if(mat[j][i]&(0x80>>k)) /*测试为1的位则显示*/
                    putpixel(i*8+k,j,WHITE);

    getch();
    closegraph();
    fclose(HZK);
}

怎么样?只要掌握了正确的方法,显示汉字并不复杂。

打印字库文件和HZK12

  如果你有UCDOS的HZK24S(宋体)、HZK24K(楷体)或HZK24H(黑体),你还可以使用不同字体的大字模汉字了。HZK24系列是24*24的点阵字库,每字模占用3*24字节。如果你按照HZK16的显示方法的话,你会看到......呵呵,字被放倒了。这是因为该类字库与一般的汉字库不同,这类大字模汉字库是专供打印的打印字库,为了打印的方便将字模都放倒了,你使用时,只要将字模的位信息纵横转置显示即可。例如你如果定义为mat[24][3]则应该这样输出:
    for(i=0;i<24;i++)
        for(j=0;j<24;j++)
            if((0x80>>i%8)&mat[j][i/8]) /*转置显示*/
                putpixel(j+x,y+i,color);
  还有一类字库HZK12,虽然属于标准字库类型,但如果你将它的字模当作12*12位计算的话,根本无法正常显示汉字。因为字库设计者为了使用的方便,字摸每行的位数均补齐为8的整数倍,于是实际该字库的位长度是16*12,虽然每行都多出了4位,但这4位都是0(不显示),并不影响显示效果。

对于24*24的点阵字库,存放格式如下: 
          纵向存放3个字节(24位),横向存放24个字节,每个字模占72个字节 
          字符排列顺序如下: 
          1   4   7   10   ...... 
          2   5   8   11   ...... 
          3   6   9   12   ......  
                
               对于16*16的点阵字库,存放格式如下: 
          横向存放2个字节(16位),其中第二个字节没有多余的数据 
          纵向存放16个字节,每个字模占32个字节 
          字符排列顺序如下: 
               1    2 
               3    4 
               5    6 
               ...... 
                
               对于14*14的点阵字库,存放格式如下: 
          横向存放2个字节(16位),其中第二个字节的后2位是多余的数据 
          纵向存放14个字节,每个字模占28个字节 
          字符排列顺序如下: 
               1    2 
               3    4 
               5    6 
               ...... 
           
               对于12*12的点阵字库,存放格式如下: 
          横向存放2个字节(16位),其中第二个字节的后4位是多余的数据 
          纵向存放12个字节,每个字模占24个字节 
          字符排列顺序如下: 
               1    2 
               3    4 
               5    6 
               ...... 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值