点阵字库是把每一个汉字都分成16×16或24×24个点,然后用每个点的虚实来表示汉字的轮廓,常用来作为显示字库使用,这类点阵字库汉字最大的缺点是不能放大,一旦放大后就会发现文字边缘的锯齿。



点阵字库与字符字模

在dos终端模式下是不可以显示中文汉字的,只能显示英文。

  汉字与英文的区别是:

  1. 汉字字库中,任何字符均用2个字节编码,即区码和位码,在英文字库中,所有字符均用单字节编码。

  2. 16点阵汉字字库(16*16)用32个字节存储一个字符的字模,16点阵英文字库(8*16)用16个字节存储单个字符的字模。

  在DOS终端模式下用的是16点阵英文字库,如果要让DOS终端中显示中文,可以改写终端模式下的16点阵英文字库,使其显示的不是原有的英文字符,而是汉字字符,当然也可以加入自造点阵图形图像。原理为:

  我们输入AB,正常显示的是AB,但如果改变AB的字模,用汉字的字模代替,这样输入AB字符,并不显示AB,而是显示一个汉字。将一个汉字从中间劈为两半,左面部分顶替A的字模,右面部分顶替B的字模。

  dos所用字库,文件头结构很简单,如默认的8*16英文字库,文件头长度为4,跳过这四个字节就是字模数据;也有没有文件头的,从第一个字节开始就是字模数据。



点阵字库结构

1、点阵字库存储

  在汉字的点阵字库中,每个字节的每个位都代表一个汉字的一个点,每个汉字都是由一个矩形的点阵组成,0代表没有,1代表有点,将0和1分别用不同颜色画出,就形成了一个汉字,常用的点阵矩阵有12*12, 14*14, 16*16三种字库。

  字库根据字节所表示点的不同有分为横向矩阵和纵向矩阵,目前多数的字库都是横向矩阵的存储方式(用得最多的应该是早期UCDOS字库),纵向矩阵一般是因为有某些液晶是采用纵向扫描显示法,为了提高显示速度,于是便把字库矩阵做成纵向,省得在显示时还要做矩阵转换。我们接下去所描述的都是指横向矩阵字库。

  2、16*16点阵字库

  对于16*16的矩阵来说,它所需要的位数共是16*16=256个位,每个字节为8位,因此,每个汉字都需要用256/8=32个字节来表示。

  即每两个字节代表一行的16个点,共需要16行,显示汉字时,只需一次性读取32个字节,并将每两个字节为一行打印出来,即可形成一个汉字。

  点阵结构如下图所示:

  第一字节 第二字节

  0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

  

  3、14*14与12*12点阵字库

  对于14*14和12*12的字库,理论上计算,它们所需要的点阵分别为(14*14/8)=25, (12*12/8)=18个字节,但是,如果按这种方式来存储,那么取点阵和显示时,由于它们每一行都不是8的整位数,因此,就会涉到点阵的计算处理问题,会增加程序的复杂度,降低程序的效率。

  为了解决这个问题,有些点阵字库会将14*14和12*12的字库按16*14和16*12来存储,即,每行还是按两个字节来存储,但是14*14的字库,每两个字节的最后两位是没有使用,12*12的字节,每两字节的最后4位是没有使用,这个根据不同的字库会有不同的处理方式,所以在使用字库时要注意这个问题,特别是14*14的字库。



汉字点阵获取

1、利用区位码获取汉字

  汉字点阵字库是根据区位码的顺序进行存储的,因此,我们可以根据区位来获取一个字库的点阵,它的计算公式如下:

  点阵起始位置 = ((区码- 1)*94 + (位码 – 1)) * 汉字点阵字节数

  获取点阵起始位置后,我们就可以从这个位置开始,读取出一个汉字的点阵。

  2、利用汉字机内码获取汉字

  前面我们己经讲过,汉字的区位码和机内码的关系如下:

  机内码高位字节 = 区码 + 20H + 80H(或区码 + A0H)

  机内码低位字节 = 位码 + 20H + 80H(或位码 + AOH)

  反过来说,我们也可以根据机内码来获得区位码:

  区码 = 机内码高位字节 - A0H

  位码 = 机内码低位字节 - AOH

  将这个公式与获取汉字点阵的公式进行合并计就可以得到汉字的点阵位置。


在DOS程序中使用点阵字库的方法

首先需要理解的是点阵字库是一个数据文件,在这个数据文件里面保存了所有文字的点阵数据.至于什么是点阵,我想我不讲大家都知道 的,使用过"文曲星"之类的电子辞典吧,那个的液晶显示器上面显示的汉子就能够明显的看出"点阵"的痕迹.在 PC 机上也是如此,文字也是由点阵来组成了,不同的是,PC机显示器的显示分辨率更高,高到了我们肉眼无法区分的地步,因此"点阵"的痕迹也就不那么明显了。

  点阵、矩阵、位图这三个概念在本质上是有联系的,从某种程度上来讲,这三个就是同义词.点阵从本质上讲就是单色位图,他使用一个比特来表示一个点,如果这 个比特为0,表示某个位置没有点,如果为1表示某个位置有点.矩阵和位图有着密不可分的联系,矩阵其实是位图的数学抽象,是一个二维的阵列.位图就是这种 二维的阵列,这个阵列中的 (x,y) 位置上的数据代表的就是对原始图形进行采样量化后的颜色值.但是,另一方面,我们要面对的问题是,计算机中数据的存放都是一维的,线性的.因此,我们需要 将二维的数据线性化到一维里面去.通常的做法就是将二维数据按行顺序的存放,这样就线性化到了一维。

  那么点阵字的数据存放细节到底是怎么样的呢.其实也十分的简单,举个例子最能说明问题.比如说 16*16 的点阵,也就是说每一行有16个点,由于一个点使用一个比特来表示,如果这个比特的值为1,则表示这个位置有点,如果这个比特的值为0,则表示这个位置没 有点,那么一行也就需要16个比特,而8个比特就是一个字节,也就是说,这个点阵中,一行的数据需要两个字节来存放.第一行的前八个点的数据存放在点阵数 据的第一个字节里面,第一行的后面八个点的数据存放在点阵数据的第二个字节里面,第二行的前八个点的数据存放在点阵数据的第三个字节里面,…,然后后 面的就以此类推了.这样我们可以计算出存放一个点阵总共需要32个字节.看看下面这个图形化的例子:

  | |1| | | | | | | | | | |1| | | |

  | | |1|1| |1|1|1|1|1|1|1|1|1| | |

  | | | |1| | | | | | | | |1| | | |

  |1| | | | | |1| | | | | |1| | | |

  | |1|1| | | |1| | | | | |1| | | |

  | | |1| | | |1| | | | |1| | | | |

  | | | | |1| | |1| | | |1| | | | |

  | | | |1| | | |1| | |1| | | | | |

  | | |1| | | | | |1| |1| | | | | |

  |1|1|1| | | | | | |1| | | | | | |

  | | |1| | | | | |1| |1| | | | | |

  | | |1| | | | |1| | | |1| | | | |

  | | |1| | | |1| | | | | |1| | | |

  | | |1| | |1| | | | | | |1|1|1| |

  | | | | |1| | | | | | | | |1| | |

  | | | | | | | | | | | | | | | | |

  可以看出这是一个"汉"字的点阵,当然文本的方式效果不是很好.根据上面的原则,我们可以写出这个点阵的点阵数 据:0x40,0x08,0x37,0xfc,0x10,0x08,…, 当然写这个确实很麻烦所以我不再继续下去.我这样做,也只是为了向你说明,在点阵字库中,每一个点阵的数据就是按照这种方式存放的.

  当然也存在着不规则的点阵,这里说的不规则,指的是点阵的宽度不是8的倍数,比如 12*12 的点阵,那么这样的点阵数据又是如何存放的呢?其实也很简单,每一行的前面8个点存放在一个字节里面,每一行的剩下的4点就使用一个字节来存放,也就是说 剩下的4个点将占用一个字节的高4位,而这个字节的低4位没有使用,全部都默认的为零.这样做当然显得有点浪费,不过却能够便于我们进行存放和寻址.对于 其他不规则的点阵,也是按照这个原则进行处理的.这样我们可以得出一个 m*n 的点阵所占用的字节数为 (m+7)/8*n.

  在明白了以上所讲的以后,我们可以写出一个显示一个任意大小的点阵字模的函数,这个函数的功能是输出一个宽度为w,高度为h的字模到屏幕的 (x,y) 坐标出,文字的颜色为 color,文字的点阵数据为 pdata 所指:

/*输出字模的函数*/
  void _draw_model(char *pdata, int w, int h, int x, int y, int color)
  {
  int     i;    /* 控制行 */
  int     j;    /* 控制一行中的8个点 */
  int     k;    /* 一行中的第几个"8个点"了 */
  int     nc;   /* 到点阵数据的第几个字节了 */
  int     cols; /* 控制列 */
  BYTE    static mask[8]={128, 64, 32, 16, 8, 4, 2, 1}; /* 位屏蔽字 */
  w = (w + 7) / 8 * 8; /* 重新计算w */
  nc = 0;
  for (i=0; i<h; i++)
  {
  cols = 0;
  for (k=0; k<w/8; k++)
  {
  for (j=0; j<8; j++)
  {
  if (pdata[nc]&mask[j])
  putpixel(x+cols, y+i, color);
  cols++;
  }
  nc++;
  }
  }
  }

  代码很简单,不用怎么讲解就能看懂,代码可能不是最优化的,但是应该是最易读懂的.其中的 putpixel 函数,使用的是TC提供的 Graphics 中的画点函数.使用这个函数就可以完成点阵任意大小的点阵字模的输出.

  接下来的问题就是如何在汉子库中寻址某个汉子的点阵数据了.要解决这个问题,首先需要了解汉字在计算机中是如何表示的.在计算机中英文可以使用 ASCII 码来表示,而汉字使用的是扩展 ASCII 码,并且使用两个扩展 ASCII 码来表示一个汉字.一个 ASCII 码使用一个字节表示,所谓扩展 ASCII 码,也就是 ASCII 码的最高位是1的 ASCII 码,简单的说就是码值大于等于 128 的 ASCII 码.一个汉字由两个扩展 ASCII 码组成,第一个扩展 ASCII 码用来存放区码,第二个扩展 ASCII 码用来存放位码.在 GB2312-80 标准中,将所有的汉字分为94个区,每个区有94个位可以存放94个汉字,形成了人们常说的区位码,这样总共就有 94*94=8836 个汉字.在点阵字库中,汉字点阵数据就是按照这个区位的顺序来存放的,也就是最先存放的是第一个区的汉字点阵数据,在每一个区中有是按照位的顺序来存放 的.在汉字的内码中,汉字区位码的存放实在扩展 ASCII 基础上存放的,并且将区码和位码都加上了32,然后存放在两个扩展 ASCII 码中.具体的说就是:

  第一个扩展ASCII码 = 128+32 + 汉字区码

  第二个扩展ASCII吗 = 128+32 + 汉字位码

  如果用char hz[2]来表示一个汉字,那么我可以计算出这个汉字的区位码为:

  区码 = hz[0] - 128 - 32 = hz[0] - 160

  位码 = hz[1] - 128 - 32 = hz[1] - 160.

  这样,我们可以根据区位码在文件中进行殉职了,寻址公式如下:

  汉字点阵数据在字库文件中的偏移 = ((区码-1) * 94 + 位码) * 一个点阵字模占用的字节数

  在寻址以后,即可读取汉字的点阵数据到缓冲区进行显示了.以下是实现代码:

 

/* 输出一个汉字的函数 */
  void _draw_hz(char hz[2], FILE *fp, int x, int y, int w, int h, int color)
  {
  char fONtbuf[128];   /* 足够大的缓冲区,也可以动态分配 */
  int ch0 = (BYTE)hz[0]-0xA0; /* 区码 */
  int ch1 = (BYTE)hz[1]-0xA0; /* 位码 */
  /* 计算偏移 */
  long offset = (long)pf->_hz_buf_size * ((ch0 - 1) * 94 + ch1 - 1);
  fseek(fp, offset, SEEK_SET);              /* 进行寻址 */
  fread(fontbuf, 1, (w + 7) / 8 * h, fp);   /* 读入点阵数据 */
  _draw_model(fontbuf, w, h, x, y, color); /* 绘制字模 */
  }

  以上介绍完了中文点阵字库的原理,当然还有英文点阵字库了.英文点阵字库中单个点阵字模数据的存放方式与中文是一模一样的,也就是对我们所写的 _draw_model 函数同样可以使用到英文字库中.唯一不同的是对点阵字库的寻址上.英文使用的就是 ASCII 码,其码值是0到127,寻址公式为:

  英文点阵数据在英文点阵字库中的偏移 = 英文的ASCII码 * 一个英文字模占用的字节数可以看到,区分中英文的关键就是,一个字符是 ASCII 码还是扩展 ASCII 码,如果是 ASCII 码,其范围是0到127,这样是使用的英文字库,如果是扩展 ASCII 码,则与其后的另一个扩展 ASCII 码组成汉字内码,使用中文字库进行显示.只要正确区分 ASCII 码的类型并进行分别的处理,也就能实现中英文字符串的混合输出了。