昨天写完几篇文章后觉得意犹未尽,我想想了,既然字库文件是二进制文件,完全可以转化为十六进制,存储在数组中,这样在寻找字符时就不用操作文件了,直接在内存中获取。
经过一番调研,证明这个思路是对的,是具有可行性的,同时也具有很强的实践意义的。(此为胡扯,不可相信)
这次写的代码全部使用C语言标准库中与文件有关的函数,所涉及的函数均以“f”开头,做到了平台无关性,为跨平台打下基础,具有很强的移植性。不过由于时间、精力、金钱、能力、水平关系,没有在vc6.0、vs2008、MiniGW、Dev c++下一一测试。
无特别说明,文中说的“ASCII字库”是指ASC16文件,完整给出了0~255的字符。“汉字字库”是指HZK16,包含了GB2312编码中的字符。
啥也别说,直接上代码,完整的代码如下:
//#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字节为单位,逐一读取该文件的中数据。读取完整的字库文件条件为:
但是ASCII中只有区区96个可打印的字符,因此为节省空间起见,就将那么字符存储起来,条件为:
至于为什么,前面强调了“实践性”,当然是实践得到的。由于ASC16包含了0~255个,一半即为0~127,可打印字符从0x20处开始,即0x20是第一个可打印的字符——虽然它是空格。有了循环条件,就可以合理地读取,保存到文件中了。使用fseek定位某个字符的偏移,使用fread读取该偏移处的16个字符。之后再使用fprintf写入另一文件中。当然可以每次读取一个字节,写入一个字节,也可以读取32个字节,写入32个字节。至于
主要是打印这个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 <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行,第0列显示incode数组的字符。
经过修改后的代码比较整洁,效果与前面的一致,虽然没有了文件的操作,但是占用内存空间比较大,这个代码编译得到的可执行文件大小有271KB,算比较大的了。在操作文件及占用空间之间如何选择,就仁者见仁,智者见智了。
(实践证明,本研究符合当初的设计,经过一段时间的使用,达到预期目标,在生产实践中具有很强的指导意义及教育意义。为将来进一步研究打下牢固的基础。)
至此,近来的研究暂时告一段落,以后搞些什么,再说吧。