当前位置: 首页 > GNU/Linux程序 > 正文

点阵字体显示系列补记:将字库文件转换成数组形式

昨天写完几篇文章后觉得意犹未尽,我想想了,既然字库文件是二进制文件,完全可以转化为十六进制,存储在数组中,这样在寻找字符时就不用操作文件了,直接在内存中获取。

经过一番调研,证明这个思路是对的,是具有可行性的,同时也具有很强的实践意义的。(此为胡扯,不可相信)

这次写的代码全部使用C语言标准库中与文件有关的函数,所涉及的函数均以“f”开头,做到了平台无关性,为跨平台打下基础,具有很强的移植性。不过由于时间、精力、金钱、能力、水平关系,没有在vc6.0、vs2008、MiniGW、Dev c++下一一测试。

无特别说明,文中说的“ASCII字库”是指ASC16文件,完整给出了0~255的字符。“汉字字库”是指HZK16,包含了GB2312编码中的字符。

啥也别说,直接上代码,完整的代码如下:

#include <stdio.h>//#define ASCII

#ifdef ASCII  /* ascii */

#define ZK “ASC16″

#define OUT_FILE “ascii16.h”

#define ARRAY “ascii16″

#else  /* hz */

#define ZK “HZK16″

#define OUT_FILE “hzk16.h”

#define ARRAY “hzk16″

#endif

int main(void)

{

int i;

FILE *fp_c;

FILE *fp;

int len;

unsigned char mat[32] = {0};

fp   = fopen(ZK, “rb”);

fp_c = fopen(OUT_FILE, “w+”);

/* get file size */

fseek(fp, 0, SEEK_END);

len = ftell(fp);

//printf(“len: %dn”, len);

fprintf(fp_c, “/******************************************************/n”);

fprintf(fp_c, “/* Font file powered by Late Lee */n”);

fprintf(fp_c, “/* http://www.latelee.org */n”);

fprintf(fp_c, “/* %s %s */n”, __DATE__, __TIME__);

fprintf(fp_c, “/******************************************************/n”);

fprintf(fp_c, “unsigned char %s[] = n{n”, ARRAY);

/* for ascii */

#ifdef ASCII

//for (i = 0; i < len; i += 16) /* full */

for (i = 0x20*16; i < len/2; i+=16 /* 96 printable ascii code */

{

fseek(fp,i,SEEK_SET);

fread(mat,16,1,fp);

fprintf(fp_c, “/* %d 0x%x ‘ %c ‘ */n”, i/16, i/16, i/16);

fprintf(fp_c,

“0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,nn”,

mat[0],mat[1],mat[2],mat[3],mat[4],mat[5],mat[6],mat[7],mat[8],mat[9],mat[10],mat[11],mat[12],mat[13],mat[14],mat[15]);

}

#else

for (i = 0; i < len; i += 32)

{

fseek(fp,i,SEEK_SET);

fread(mat,32,1,fp);

fprintf(fp_c,

“0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,n”,

mat[0],mat[1],mat[2],mat[3],mat[4],mat[5],mat[6],mat[7],mat[8],mat[9],mat[10],mat[11],mat[12],mat[13],mat[14],mat[15],

mat[16],mat[17],mat[18],mat[19],mat[20],mat[21],mat[22],mat[23],mat[24],mat[25],mat[26],mat[27],mat[28],mat[29],mat[30],mat[31]);

}

#endif

fprintf(fp_c, “};n”);

fprintf(stdout, “Job done!n”);

return 0;

}

 

代码毫无算法可言,如果一定要说点什么,步骤大约是这样的:

1、打开字库文件,创建字库数组头文件,使用的函数为fopen。

2、获取字库文件长度大小,使用fseek和ftell函数。

3、由于ASCII字库中ASCII码占16个字节的空间,因此以16字节为单位,逐一读取该文件的中数据。读取完整的字库文件条件为:

for (i = 0; i < len; i += 16)

 

但是ASCII中只有区区96个可打印的字符,因此为节省空间起见,就将那么字符存储起来,条件为:

for (i = 0x20*16; i < len/2; i+=16)

至于为什么,前面强调了“实践性”,当然是实践得到的。由于ASC16包含了0~255个,一半即为0~127,可打印字符从0x20处开始,即0x20是第一个可打印的字符——虽然它是空格。有了循环条件,就可以合理地读取,保存到文件中了。使用fseek定位某个字符的偏移,使用fread读取该偏移处的16个字符。之后再使用fprintf写入另一文件中。当然可以每次读取一个字节,写入一个字节,也可以读取32个字节,写入32个字节。至于

fprintf(fp_c, “/* %d 0x%x ‘ %c ‘ */n”, i/16, i/16, i/16);

主要是打印这个ASCII的十进制、十六进制以及它本身显示的字符,从后面三个数可以看出,这几个东西其实是一个东西,本质是一样,只不过表现形式不一样而已。

至于汉字字库,一样的道理,只是以32个字节为一单位。

 

以ASCII为例,下面是生成的ascii16.h文件的部分内容:

/******************************************************/

/*          Font file powered by Late Lee             */

/*             http://www.latelee.org                 */

/*              May 26 2011 07:08:09                  */

/******************************************************/

unsigned char ascii16[] =

{

/*   32     0x20     ‘   ‘ */

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*   33     0x21     ‘ ! ‘ */

0x00,0x00,0x18,0x3c,0x3c,0x3c,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00,

/*   34     0x22     ‘ ” ‘ */

0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

/*   35     0x23     ‘ # ‘ */

0x00,0x00,0x00,0x6c,0x6c,0xfe,0x6c,0x6c,0x6c,0xfe,0x6c,0x6c,0x00,0x00,0x00,0x00,

/*   36     0x24     ‘ $ ‘ */

0x18,0x18,0x7c,0xc6,0xc2,0xc0,0x7c,0x06,0x06,0x86,0xc6,0x7c,0x18,0x18,0x00,0x00,

/*   37     0x25     ‘ % ‘ */

0x00,0x00,0x00,0x00,0xc2,0xc6,0x0c,0x18,0x30,0x60,0xc6,0x86,0x00,0x00,0x00,0x00,

/* 省略很多 */

}

上面格式不太整齐,原因在前面的文章已经说了,其实在编辑器中是非常整齐大方的。

研究成果已经出来了,那么要看看它能不能在实践中经受得起考验。

下面是是昨天经过修改后,并使用上面程序生成的英文字库数组及中文字库数组用ncurses来显示的完整代码:

#include <stdio.h>

#include <unistd.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <fcntl.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <ncurses.h>  /* ncurses库头文件 */#include “ascii16.h”

#include “hzk16.h”

#define ascii_code ascii16

#define hzk_code hzk16

/* for debug */

//#define DEBUG

#ifdef DEBUG

#define debug(fmt, …) printw(fmt, ##__VA_ARGS__)

#else

#define debug(fmt, …)

#endif

static void __display_font(int y, int x, unsigned char *mat, char *code)

{

int i, j, k;

for(i=0;i<16;i++) {

for(j=0;j<2;j++)

{

for(k=0;k<8;k++)

{

/* 从高位开始,逐位相与,为1者,输出“*” */

if(mat[i*2+j] & (0x80>>k))

mvprintw(y+i, x+j*8+k, code);

else

mvprintw(y+i, x+j*8+k, ” “);

}

}

}

refresh();

}

static void __display_ascii(int y, int x, unsigned char *ascii, char *code)

{

int i, j;

int bits;

for(i=0;i<16;i++) {

bits = ascii[i];

for(j=0;j<8;j++, bits<<=1) {

if (bits & 0x80)

mvprintw(y+i, x+j, code);

else

mvprintw(y+i, x+j, ” “);

}

}

refresh();

}

/*

* 打印ASCII,使用96个可打印字符版本的ASCII码数组

* y:屏幕行

* x:屏幕列

* font:ASCII字符串

* note:注意函数中的unsigned char*类型

*/

void display_ascii(int y, int x, unsigned char *font)

{

unsigned char *p_ascii;

int offset;

unsigned char *p = font;

while (*p != 0) {

offset = (*p – 0x20 ) * 16;

//offset = *p * 16;

p_ascii = ascii_code + offset;

//debug(“offset: %x %xn”, p_ascii, offset);

__display_ascii(y, x, p_ascii, “*”);

x += 10;

p++;

}

}

/*

* 打印汉字,使用HZK16文件

* fp:汉字库文件指针

* y:屏幕行

* x:屏幕列

* font:汉字字符串

* note:注意函数中的unsigned char*类型

*/

void display_hz(FILE *fp, int y, int x, unsigned char *font)

{

unsigned char mat[32]={0};

int qh,wh;

unsigned long offset;

unsigned char *p = font;

while (*p != 0) {

qh = *p   – 0xa0;

wh = *(p+1) – 0xa0;

debug(“code : %x %xn”, *p, *(p+1));

offset = ( 94*(qh-1) + (wh-1) ) * 32;

debug(“qh: %x wh: %x offset: %xn”, qh, wh, offset);

fseek(fp,offset,SEEK_SET);

fread(mat,32,1,fp);

__display_font(y, x, mat, “*”);

x += 18;

p+=2;         /* 中文字符,移动2个字节 */

}

}

/*

* 打印字符,中英文混合版本,不使用字库文件

* y:屏幕行

* x:屏幕列

* font:字符串

* note:注意函数中的unsigned char*类型

*/

void display_font(int y, int x, unsigned char *font)

{

int qh,wh;

unsigned long offset;

unsigned char *p = font;

unsigned char *p_ascii;

unsigned char *p_hzk;

while (*p != 0) {

qh = *p   – 0xa0;

wh = *(p+1) – 0xa0;

if (qh > 0 && wh > 0){

debug(“code : %x %xn”, *p, *(p+1));

offset = ( 94*(qh-1) + (wh-1) ) * 32;

debug(“qh: %x wh: %x offset: %xn”, qh, wh, offset);

p_hzk = hzk_code + offset;

__display_font(y, x, p_hzk, “*”);

x += 18 /* 16或以上 */

p+=2;         /* 中文字符,移动2个字节 */

else {

int offset1;

offset1 = (*p – 0x20 ) * 16;

p_ascii = ascii_code + offset1;

__display_ascii(y, x, p_ascii, “*”);

x += 10 /* 8或以上 */

p+=1;         /* 英文字符,移动1个字节 */

}

}

}

int main()

{

//unsigned char incode[] = “我Az你个pf”; /* 全部是中文字符,英文为全角状态下输入 */

//unsigned char incode[] = “波神留我看斜阳”; /* 全部中文 */

//unsigned char incode[] = “人生如梦”; /* 全部中文 */

//unsigned char incode[] = “I’m Late Lee”; /* 全部英文 */

unsigned char incode[] = “我AZ你个pf”;        /* 中文、英文 */

initscr();         /* init screen */

display_font(10, incode);

//getch(); /*暂停*/

endwin();  /* close it */

return 0;

}

至于代码中为什么将y放到前面,是因为这样更能直观表示我们对面的屏幕的坐标,比如

 display_font(10, incode);

就表明了在第1行,第0列显示incode数组的字符。

经过修改后的代码比较整洁,效果与前面的一致,虽然没有了文件的操作,但是占用内存空间比较大,这个代码编译得到的可执行文件大小有271KB,算比较大的了。在操作文件及占用空间之间如何选择,就仁者见仁,智者见智了。

(实践证明,本研究符合当初的设计,经过一段时间的使用,达到预期目标,在生产实践中具有很强的指导意义及教育意义。为将来进一步研究打下牢固的基础。)

至此,近来的研究暂时告一段落,以后搞些什么,再说吧。

本文固定链接: http://www.latelee.org/programming-under-linux/dot-font-code-to-array.html

目前暂无评论

发表评论

*

快捷键:Ctrl+Enter