解析BMFont所生成的二进制文件

解析BMFont所生成的二进制文件

Author: OyaKti(SRK)
Date: 2016/8/9(七夕)

TCC编译的. 钦定ANSIC


讲真, 以下文章都是胡扯, 写这篇文章的时候脑子故障了. 我他妈直接读内存, 真他妈智障!

DirectX学习笔记[5]介绍了字体的渲染, 所以我们需要一个方便的位图字体生成方法. Cocos2D-X使用的BMFont就是一个不错的选择. 通过BMF生成的位图字体会有两个文件, 一个是字体的贴图, 一个是保存字体信息的fnt文件, fnt文件有xml格式的, 有纯文本格式的, 有二进制格式的, 为了减少储存空间, 以及好玩, 我就决定使用二进制格式来储存字体信息, 所以, 为了使用字体, 我们必须解析二进制文件中储存的字体信息!
先来个效果预览:
2761296135
怎么样啊, 是不是很有逼格?
解析二进制文件通常需要官方的文档以便获取二进制文件的布局(Layout), BMF的文档十分简洁, 一眼就能读懂.文档地址
获得了布局信息之后自然可以直接编写程序, 但是为了方便展示, 我们通常把布局反映到winHex上. 我通过不同的着色方式和幻灯来对文件进行了着色, 效果如下.
1936733687

前半部分五颜六色七彩斑斓的是前三个区块以及字符区块的第一个字符, 后面灰白相间的部分则是剩余的字符.
具体的布局情况请浏览官方的文档.
万事俱备, 只欠东风.
我们现在就开始码代码.
首先我们得定义字体, 页和字符.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct Char{
int id;
int Xpos, Ypos;
int Width, Height;
}Char;

typedef struct Page{
Char *chars;
char name[64];
int charNum;
}Page;

typedef struct Font{
Page *pages;
char name[64];
int pageNum;
}Font;

因为是简单的解析, 所以通道等信息就不包含在内了.
有了这些定义, 我们就可以解析了.
一口气把代码全都放上来吧!(偷懒max)

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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Char{
int id;
int Xpos, Ypos;
int Width, Height;
}Char;

typedef struct Page{
Char *chars;
char name[64];
int charNum;
}Page;

typedef struct Font{
Page *pages;
char name[64];
int pageNum;
}Font;

int ReverseByteToInt(unsigned char data[], unsigned int size, int offset){
int val = 0;
int i;
for(i = size + offset - 1 ; i >= offset ; i--)
val |= data[i] << (i - offset) * 8;
return val;
}

long filesize(FILE*stream){
long curpos, length;
curpos = ftell(stream);
fseek(stream, 0L, SEEK_END);
length = ftell(stream);
fseek(stream, curpos, SEEK_SET);
return length;
}

int OutPutChar(Char chr){
printf("========================\n");
printf("Char ID : %d , Char : %c\n", chr.id, chr.id);
printf("Xpos : %d , Ypos : %d\n", chr.Xpos, chr.Ypos);
printf("Width : %d , Height : %d\n", chr.Width, chr.Height);
printf("========================\n");
}

int ParseFont(Font *fnt, const char *Filename){
FILE *fntfile = fopen(Filename, "rb");
if(!fntfile){
printf("Cannot Read fnt File!\n");
fclose(fntfile);
return -1;
}
int byteCount = filesize(fntfile);
printf("FontFile Size : %dByte\n", byteCount);
unsigned char *data = (unsigned char *)malloc(sizeof(unsigned char) * byteCount);
fread(data, sizeof(unsigned char), byteCount, fntfile);

unsigned char FileHead[4] = { 0 };
memcpy(FileHead, data, 3);
if(strcmp(FileHead, "BMF") != 0){
printf("Data Format Wrong!\n");
return -1;
}
if(data[3] != 0x03){
printf("Not Currently Version!\n");
return -1;
}

fseek(fntfile, 23, SEEK_SET);
fscanf(fntfile, "%s", fnt->name);
printf("Font name : %s\n", fnt->name);
int pageBlockSize = ReverseByteToInt(data, 4,
ReverseByteToInt(data, 4, 5) + 30);
printf("PageBlockSize : %d\n", pageBlockSize);
//蒜香味的豆子吃进嘴里 就好像咬了一大口铁锈皮
fseek(fntfile, ReverseByteToInt(data, 4, 5) + 34, SEEK_SET);
char PageName[64] = { 0 };
fscanf(fntfile, "%s", PageName);
int pageNum = pageBlockSize / (strlen(PageName) + 1);
fnt->pageNum = pageNum;
printf("Page(s) Count : %d\n", pageNum);
int i;
if(pageNum != 1){
fnt->pages = (Page*)malloc(sizeof(Page) * pageNum);
fseek(fntfile, ReverseByteToInt(data, 4, 5) + 34, SEEK_SET);
for(i = 0; i < pageNum; i++){
fscanf(fntfile, "%s", PageName);
strcpy(fnt->pages[i].name, PageName);
PageName[0] = '\0';
}
}else{
fnt->pages = (Page*)malloc(sizeof(Page));
strcpy(fnt->pages[0].name, PageName);
}
unsigned char *data_ = (unsigned char *)malloc(
sizeof(unsigned char) * (byteCount \
- ReverseByteToInt(data, 4, 5)\
- pageBlockSize - 34)
);
memcpy(data_,
&data[ReverseByteToInt(data, 4, 5) + pageBlockSize + 34],
byteCount - ReverseByteToInt(data, 4, 5) - pageBlockSize - 34
);
for(i = 0; i < pageNum; i++){
printf("Page Name[%d] : %s\n", i, fnt->pages[i].name);
if(ParsePage(&(fnt->pages[i]), i, data_) != 0){
printf("Cannot Load Page[%d]!!\n", i);
continue;
}
}
free(data);
free(data_);
fclose(fntfile);
return 0;
}

int ParsePage(Page *pag, int pagNum, unsigned char data[]){
if(data[0] != 0x04){
printf("Unexpected Block Symbol!\n");
return -1;
}
int numChars_Total = (ReverseByteToInt(data, 4, 1) / 20);
int numChars = 0;
for(;numChars < numChars_Total;)
if(data[numChars * 2 + 5 + 18 * ++numChars] != pagNum)
break;
if(!(pag->chars = (Char*)malloc(sizeof(Char) * numChars))){
printf("Wrong in Memory allocation!\n");
return -1;
}
printf("Num of Chars = %d\n", numChars);
pag->charNum = numChars;
unsigned char charData[20] = { 0 };
int i;
for(i = 0; i < numChars; i++){
if(!memcpy(charData, &data[5 + i * 20], 20))
return -1;
if(ParseChar(&pag->chars[i], charData) != pagNum)
i = 0;
//OutPutChar(pag->chars[i]);
}
return 0;
}

int ParseChar(Char *chr, unsigned char data[20]){
chr->id = ReverseByteToInt(data, 4, 0);
chr->Xpos = ReverseByteToInt(data, 2, 4);
chr->Ypos = ReverseByteToInt(data, 2, 6);
chr->Width = ReverseByteToInt(data, 2, 8);
chr->Height = ReverseByteToInt(data, 2, 10);
return data[18];
}

int OutPutCharByID(int ID, Font fnt){
int i, j;
for(i = 0; i < fnt.pageNum; i++)
for(j = 0; j < fnt.pages[i].charNum; j++)
if(fnt.pages[i].chars[j].id == ID){
OutPutChar(fnt.pages[i].chars[j]);
return 0;
}
printf("Cannot find id : %d!\n", ID);
return -1;
}

int main(int argc, char **argv){
Font fnt;
if(argc < 3){
printf("Wrong Arg!\nBMFParse Fontname CharID");
return -1;
}
if(ParseFont(&fnt, argv[1]) != 0){
printf("Cannot Parse Font file!\n");
return -1;
}
OutPutCharByID(atoi(argv[2]), fnt);
return 0;
}

我的代码没有一点儿注释~ 部分魔数(Magic Number)也是数出来的, 不过不影响使用. 你看出这篇文章的用意了吗? 没错! 没错! 没错! 就是在证明我没有弃坑! 没有弃坑! 没有弃坑!(然后顺带增加一篇博文), 好了, 再见~