关于cocos2dx 2.x CCLabelBMFont的解析优化

时间:2023-02-08 17:59:05

  因为系统字体已经没办法满足项目的需求,需要用一个新的字体,但由于担心字体版权等问题,因为改用通用做法,做一套全字体的BMFont ,全字体9千多个汉字和其他符号,还好2048X2048堆下来了,只用了19号字体,而且万幸经过压缩后,图片也没有多大了。

  但是问题来了,在安卓上,首次引用到这个字体时,字体解析居然花了10多秒。

  打开.fnt文件

info face="STHeitiSC-Light" size=19 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1
common lineHeight
=26 base=19 scaleW=2048 scaleH=2048 pages=1 packed=0
page id
=0 file="youyuan.png"
chars count
=9237
char id=12298 x=1 y=1 width=11 height=21 xoffset=10 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
char id=12299 x=13 y=1 width=11 height=21 xoffset=0 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
char id=12300 x=25 y=1 width=6 height=21 xoffset=12 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
char id=12301 x=32 y=1 width=6 height=21 xoffset=2 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
char id=65288 x=39 y=1 width=6 height=21 xoffset=12 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
char id=65289 x=46 y=1 width=6 height=21 xoffset=2 yoffset=1 xadvance=20 page=0 chnl=0 letter=""
char id=124 x=53 y=1 width=2 height=21 xoffset=4 yoffset=3 xadvance=9 page=0 chnl=0 letter="|"
char id=30306 x=77 y=1 width=20 height=20 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0 letter=""
char id=30221 x=98 y=1 width=20 height=20 xoffset=0 yoffset=1 xadvance=19 page=0 chnl=0 letter=""
char id=23518 x=119 y=1 width=19 height=20 xoffset=1 yoffset=1 xadvance=19 page=0 chnl=0 letter=""
char id=22815 x=139 y=1 width=19 height=20 xoffset=1 yoffset=1 xadvance=19 page=0 chnl=0 letter=""

查看CCLabelBMFont.cpp,查看其解析函数

 1 std::set<unsigned int>* CCBMFontConfiguration::parseConfigFile(const char *controlFile)
2 {
3 std::string fullpath = CCFileUtils::sharedFileUtils()->fullPathForFilename(controlFile);
4 CCString *contents = CCString::createWithContentsOfFile(fullpath.c_str());
5
6 CCAssert(contents, "CCBMFontConfiguration::parseConfigFile | Open file error.");
7
8 set<unsigned int> *validCharsString = new set<unsigned int>();
9
10 if (!contents)
11 {
12 CCLOG("cocos2d: Error parsing FNTfile %s", controlFile);
13 return NULL;
14 }
15
16 // parse spacing / padding
17 std::string line;
18 std::string strLeft = contents->getCString();
19 while (strLeft.length() > 0)
20 {
21 int pos = strLeft.find('\n');
22
23 if (pos != (int)std::string::npos)
24 {
25 // the data is more than a line.get one line
26 line = strLeft.substr(0, pos);
27 strLeft = strLeft.substr(pos + 1);
28 }
29 else
30 {
31 // get the left data
32 line = strLeft;
33 strLeft.erase();
34 }
35
36 if(line.substr(0,strlen("info face")) == "info face")
37 {
38 // XXX: info parsing is incomplete
39 // Not needed for the Hiero editors, but needed for the AngelCode editor
40 // [self parseInfoArguments:line];
41 this->parseInfoArguments(line);
42 }
43 // Check to see if the start of the line is something we are interested in
44 else if(line.substr(0,strlen("common lineHeight")) == "common lineHeight")
45 {
46 this->parseCommonArguments(line);
47 }
48 else if(line.substr(0,strlen("page id")) == "page id")
49 {
50 this->parseImageFileName(line, controlFile);
51 }
52 else if(line.substr(0,strlen("chars c")) == "chars c")
53 {
54 // Ignore this line
55 }
56 else if(line.substr(0,strlen("char")) == "char")
57 {
58 // Parse the current line and create a new CharDef
59 tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
60 this->parseCharacterDefinition(line, &element->fontDef);
61
62 element->key = element->fontDef.charID;
63 HASH_ADD_INT(m_pFontDefDictionary, key, element);
64
65 validCharsString->insert(element->fontDef.charID);
66 }
67 // else if(line.substr(0,strlen("kernings count")) == "kernings count")
68 // {
69 // this->parseKerningCapacity(line);
70 // }
71 else if(line.substr(0,strlen("kerning first")) == "kerning first")
72 {
73 this->parseKerningEntry(line);
74 }
75 }
76
77 return validCharsString;
78 }

执行最多次数的应该是这里:

 1  else if(line.substr(0,strlen("char")) == "char")
2 {
3 // Parse the current line and create a new CharDef
4 tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
5 this->parseCharacterDefinition(line, &element->fontDef);
6
7 element->key = element->fontDef.charID;
8 HASH_ADD_INT(m_pFontDefDictionary, key, element);
9
10 validCharsString->insert(element->fontDef.charID);
11 }

继续跟踪:parseCharacterDefinition() 这个函数

 1 void CCBMFontConfiguration::parseCharacterDefinition(std::string line, ccBMFontDef *characterDefinition)
2 {
3 //////////////////////////////////////////////////////////////////////////
4 // line to parse:
5 // char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=44 xadvance=14 page=0 chnl=0
6 //////////////////////////////////////////////////////////////////////////
7
8 // Character ID
9 int index = line.find("id=");
10 int index2 = line.find(' ', index);
11 std::string value = line.substr(index, index2-index);
12 sscanf(value.c_str(), "id=%u", &characterDefinition->charID);
13
14 // Character x
15 index = line.find("x=");
16 index2 = line.find(' ', index);
17 value = line.substr(index, index2-index);
18 sscanf(value.c_str(), "x=%f", &characterDefinition->rect.origin.x);
19 // Character y
20 index = line.find("y=");
21 index2 = line.find(' ', index);
22 value = line.substr(index, index2-index);
23 sscanf(value.c_str(), "y=%f", &characterDefinition->rect.origin.y);
24 // Character width
25 index = line.find("width=");
26 index2 = line.find(' ', index);
27 value = line.substr(index, index2-index);
28 sscanf(value.c_str(), "width=%f", &characterDefinition->rect.size.width);
29 // Character height
30 index = line.find("height=");
31 index2 = line.find(' ', index);
32 value = line.substr(index, index2-index);
33 sscanf(value.c_str(), "height=%f", &characterDefinition->rect.size.height);
34 // Character xoffset
35 index = line.find("xoffset=");
36 index2 = line.find(' ', index);
37 value = line.substr(index, index2-index);
38 sscanf(value.c_str(), "xoffset=%hd", &characterDefinition->xOffset);
39 // Character yoffset
40 index = line.find("yoffset=");
41 index2 = line.find(' ', index);
42 value = line.substr(index, index2-index);
43 sscanf(value.c_str(), "yoffset=%hd", &characterDefinition->yOffset);
44 // Character xadvance
45 index = line.find("xadvance=");
46 index2 = line.find(' ', index);
47 value = line.substr(index, index2-index);
48 sscanf(value.c_str(), "xadvance=%hd", &characterDefinition->xAdvance);
49 }

居然全部为字符串一行一行的解析,因为肯定是会很慢的。

查看.fnt文件 ,其char 解析部分,全部都是固定格式的。因此思路很简单,修改.fnt文件 ,改为二进制解析,这样肯定可以提升很高的效率。

当然首先要做的是一个转换工具,将.fnt 文件转化为二进制文件。这里,我做的并不是所有的数据都转为二进制,因为时间关系,也没过多的去研究其他的字符。因此,我做的是保留其他所有的符为原本的内容,只修改char id = 这些项的格式

为了确保原本的.fnt 文件还继续能读,新生成的.fnt 文件中加入了标记“type=sxbmfont”

 

修改fnt文件代码,这里只贴出关键代码,其他部分我用的是MFC简单写的,就不贴出来了

结构体格式定义:

1 struct BMFontCharNode
2 {
3 int id;
4 int x,y;
5 int width,height;
6 int xoffset,yoffset;
7 int xadvance;
8 };
9 typedef struct BMFontCharNode BMFontNode;

这是我的MFC点击编码时:

 1 void CtxtToBinDlg::OnBnClickedEncode()
2 {
3 // TODO: 在此添加控件通知处理程序代码
4 if (mOutContent == "")
5 {
6 MessageBox("请先选择输出目录");
7 return;
8 }
9 CString path = mOutContent + "/" + m_fileName;
10 fstream fin(m_filePath);
11 const int LINE_LENGTH = 150;
12 char str[LINE_LENGTH];
13 memset(str,0,LINE_LENGTH);
14 fstream fout(path,ios::out);
15 fout.write("type=sxbmfont\r\n",strlen("type=sxbmfont\r\n"));
16 for ( int i=0; i < 4 ; i ++)
17 {
18 if(fin.getline(str,LINE_LENGTH) )
19 {
20 for (int j=0;j<LINE_LENGTH;j++)
21 if (str[j] < 0)
22 str[j] = 0;
23 CString str1 = CString(str) + "\r\n";
24 if (str1.Left(strlen("char id=")) != "char id=")
25 {
26 fout.write(str1.GetBuffer(),str1.GetLength());
27 }
28 else
29 {
30 break;
31 }
32 memset(str,0,LINE_LENGTH);
33 }
34 }
35 fout.close();
36
37
38 fstream binary_file(path,ios::out|ios::binary|ios::app);
39 while( fin.getline(str,LINE_LENGTH) )
40 {
41 for (int j=0;j<LINE_LENGTH;j++)
42 if (str[j] < 0)
43 str[j] = 0;
44 CString str1 = str;
45 if (str1.Left(strlen("char id=")) == "char id=")
46 {
47 BMFontNode t;
48 sscanf_s(str1.GetBuffer(), "char id=%d x=%d y=%d width=%d height=%d xoffset=%d yoffset=%d xadvance=%d",
49 &t.id,&t.x,&t.y,&t.width,&t.height,&t.xoffset,&t.yoffset,&t.xadvance);
50 // int pase = 1;
51 binary_file.write(reinterpret_cast<char *>(&t),sizeof(t));
52 }
53 memset(str,0,LINE_LENGTH);
54 }
55 binary_file.close();
56 if(!isContent)
57 MessageBox("生成成功!");
58 }

 

生成新的.fnt文件事,需要修改CCLabelBMFont 来读取

修改的部分如下:

在头文件加入新的结构体定义:(与保存时的结构一致)

CCLabelBMFont.h中加入结构定义

1 struct BMFontCharNode
2 {
3 int id;
4 int x,y;
5 int width,height;
6 int xoffset,yoffset;
7 int xadvance;
8 };
9 typedef struct BMFontCharNode BMFontNode;

修改CCLabelBMFont.cpp

 

添加重载函数(重载char id = 行的解析)

 1 void CCBMFontConfiguration::parseCharacterDefinition(BMFontNode *node, ccBMFontDef *characterDefinition)
2 {
3 characterDefinition->charID = node->id;
4 characterDefinition->rect.origin.x = node->x;
5 characterDefinition->rect.origin.y = node->y;
6 characterDefinition->rect.size.width = node->width;
7 characterDefinition->rect.size.height = node->height;
8 characterDefinition->xOffset = node->xoffset;
9 characterDefinition->yOffset = node->yoffset;
10 characterDefinition->xAdvance = node->xadvance;
11 }

修改解析函数:

  1 std::set<unsigned int>* CCBMFontConfiguration::parseConfigFile(const char *controlFile)
2 {
3 /*
4 * 修改数据解析方式
5 *
6 */
7 std::string fullpath = CCFileUtils::sharedFileUtils()->fullPathForFilename(controlFile);
8
9 set<unsigned int> *validCharsString = new set<unsigned int>();
10
11
12 CCString *contents = CCString::createWithContentsOfFile(fullpath.c_str());
13 char contentstr[20];
14 memcpy(contentstr,contents->getCString(),strlen("type=sxbmfont"));
15 contentstr[strlen("type=sxbmfont")] = 0;
16 if (strcmp(contentstr,"type=sxbmfont"))
17 {
18 CCAssert(contents, "CCBMFontConfiguration::parseConfigFile | Open file error.");
19 // parse spacing / padding
20 std::string line;
21 std::string strLeft = contents->getCString();
22 while (strLeft.length() > 0)
23 {
24 int pos = strLeft.find('\n');
25
26 if (pos != (int)std::string::npos)
27 {
28 // the data is more than a line.get one line
29 line = strLeft.substr(0, pos);
30 strLeft = strLeft.substr(pos + 1);
31 }
32 else
33 {
34 // get the left data
35 line = strLeft;
36 strLeft.erase();
37 }
38 if(line.substr(0,strlen("info face")) == "info face")
39 {
40 // XXX: info parsing is incomplete
41 // Not needed for the Hiero editors, but needed for the AngelCode editor
42 // [self parseInfoArguments:line];
43 this->parseInfoArguments(line);
44 }
45 // Check to see if the start of the line is something we are interested in
46 else if(line.substr(0,strlen("common lineHeight")) == "common lineHeight")
47 {
48 this->parseCommonArguments(line);
49 }
50 else if(line.substr(0,strlen("page id")) == "page id")
51 {
52 this->parseImageFileName(line, controlFile);
53 }
54 else if(line.substr(0,strlen("chars c")) == "chars c")
55 {
56 // Ignore this line
57 }
58 else if(line.substr(0,strlen("char")) == "char")
59 {
60 // Parse the current line and create a new CharDef
61 tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
62 this->parseCharacterDefinition(line, &element->fontDef);
63
64 element->key = element->fontDef.charID;
65 HASH_ADD_INT(m_pFontDefDictionary, key, element);
66
67 validCharsString->insert(element->fontDef.charID);
68 }
69
70 else if(line.substr(0,strlen("kerning first")) == "kerning first")
71 {
72 this->parseKerningEntry(line);
73 }
74 }
75
76 }
77 else
78 {
79 unsigned long len = 0;
80 unsigned char* data = CCFileUtils::sharedFileUtils()->getFileData(fullpath.c_str(), "rb", &len);
81 const int LINE_LENGTH = 150;
82 char str[LINE_LENGTH];
83 int nodeSize = sizeof(BMFontNode);
84 int index = 0;
85 int count = 0;
86 for(int i=0 ; i < len ; i++)
87 {
88 if(data[i] == '\n')
89 {
90 memcpy(str,data+index,i-index+1);
91 str[i-index+1] = 0;
92 std::string line = str;
93 if(line.substr(0,strlen("info face")) == "info face")
94 {
95 this->parseInfoArguments(line);
96 }
97 // Check to see if the start of the line is something we are interested in
98 else if(line.substr(0,strlen("common lineHeight")) == "common lineHeight")
99 {
100 this->parseCommonArguments(line);
101 }
102 else if(line.substr(0,strlen("page id")) == "page id")
103 {
104 this->parseImageFileName(line, controlFile);
105 }
106 else if(line.substr(0,strlen("chars c")) == "chars c")
107 {
108 // Ignore this line
109 }
110 else if(line.substr(0,strlen("char")) == "char")
111 {
112 // Parse the current line and create a new CharDef
113 tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
114 this->parseCharacterDefinition(line, &element->fontDef);
115
116 element->key = element->fontDef.charID;
117 HASH_ADD_INT(m_pFontDefDictionary, key, element);
118
119 validCharsString->insert(element->fontDef.charID);
120 }
121 else if(line.substr(0,strlen("kerning first")) == "kerning first")
122 {
123 this->parseKerningEntry(line);
124 }
125 index = i+1;
126 count ++;
127 if(count == 5)
128 break;
129 }
130
131 }
132 while(index < len)
133 {
134 BMFontNode t;
135 memcpy(&t,data+index,nodeSize);
136 index = index + nodeSize;
137 tCCFontDefHashElement* element = (tCCFontDefHashElement*)malloc( sizeof(*element) );
138 this->parseCharacterDefinition(&t, &element->fontDef);
139
140 element->key = element->fontDef.charID;
141 HASH_ADD_INT(m_pFontDefDictionary, key, element);
142
143 validCharsString->insert(element->fontDef.charID);
144 }
145 }
146
147
148
149 return validCharsString;
150 }

 至此优化完成。修改后,这样解析过程应该基本上不怎么消耗时间了,因为没有过多的字符解析