汉字库,字模的了解_显示汉字为什么是dedede-程序员宅基地

汉字库,字模的了解

标签:  技术类 
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 
               ...... 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ljclx1748/article/details/14645081

智能推荐

稀疏编码的数学基础与理论分析-程序员宅基地

文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...

EasyGBS国标流媒体服务器GB28181国标方案安装使用文档-程序员宅基地

文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档

【Web】记录巅峰极客2023 BabyURL题目复现——Jackson原生链_原生jackson 反序列化链子-程序员宅基地

文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子

一文搞懂SpringCloud,详解干货,做好笔记_spring cloud-程序员宅基地

文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud

Js实现图片点击切换与轮播-程序员宅基地

文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换

tensorflow-gpu版本安装教程(过程详细)_tensorflow gpu版本安装-程序员宅基地

文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装

随便推点

物联网时代 权限滥用漏洞的攻击及防御-程序员宅基地

文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者

Visual Odometry and Depth Calculation--Epipolar Geometry--Direct Method--PnP_normalized plane coordinates-程序员宅基地

文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be ​ and th_normalized plane coordinates

开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先抽取关系)_语义角色增强的关系抽取-程序员宅基地

文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景​ 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取

10个顶尖响应式HTML5网页_html欢迎页面-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面

计算机二级 考试科目,2018全国计算机等级考试调整,一、二级都增加了考试科目...-程序员宅基地

文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思

conan简单使用_apt install conan-程序员宅基地

文章浏览阅读240次。conan简单使用。_apt install conan