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

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

经过一番调研,证明这个思路是对的,是具有可行性的,同时也具有很强的实践意义的。(此为胡扯,不可相信)
这次写的代码全部使用C语言标准库中与文件有关的函数,所涉及的函数均以“f”开头,做到了平台无关性,为跨平台打下基础,具有很强的移植性。不过由于时间、精力、金钱、能力、水平关系,没有在vc6.0、vs2008、MiniGW、Dev c++下一一测试。
无特别说明,文中说的“ASCII字库”是指ASC16文件,完整给出了0~255的字符。“汉字字库”是指HZK16,包含了GB2312编码中的字符。
啥也别说,直接上代码,完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#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: %d\n", 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,\n\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]);
                
        }
        #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字节为单位,逐一读取该文件的中数据。读取完整的字库文件条件为:

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

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

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

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

1
fprintf(fp_c, "/* %d 0x%x ' %c ' *\n", i/16, i/16, i/16);

主要是打印这个ASCII的十进制、十六进制以及它本身显示的字符,从后面三个数可以看出,这几个东西其实是一个东西,本质是一样,只不过表现形式不一样而已。 至于汉字字库,一样的道理,只是以32个字节为一单位。 以ASCII为例,下面是生成的ascii16.h文件的部分内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/******************************************************/
/*          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来显示的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#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 %x\n", 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 %x\n", *p, *(p+1));
                offset = ( 94*(qh-1) + (wh-1) ) * 32;
                debug("qh: %x wh: %x offset: %x\n", 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 %x\n", *p, *(p+1));
                        offset = ( 94*(qh-1) + (wh-1) ) * 32;
                        debug("qh: %x wh: %x offset: %x\n", 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(1, 0, incode);

        //getch(); /*暂停*/
        endwin();  /* close it */
        return 0;
}

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

1
 display_font(1, 0, incode);

就表明了在第1行,第0列显示incode数组的字符。
经过修改后的代码比较整洁,效果与前面的一致,虽然没有了文件的操作,但是占用内存空间比较大,这个代码编译得到的可执行文件大小有271KB,算比较大的了。在操作文件及占用空间之间如何选择,就仁者见仁,智者见智了。
(实践证明,本研究符合当初的设计,经过一段时间的使用,达到预期目标,在生产实践中具有很强的指导意义及教育意义。为将来进一步研究打下牢固的基础。)

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