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

  因为系统字体已经没办法满足项目的需求,需要用一个新的字体,但由于担心字体版权等问题,因为改用通用做法,做一套全字体的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 }

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

posted @ 2016-01-19 11:32  JohnKing_  阅读(938)  评论(0编辑  收藏  举报