关于字符编码的知识,请参考前辈的博客:字符串和编码格式
这里使用的是cocos2dx的2.2.3版本,底层的RictText换行机制不能满足中文换行,需要改动。由于在3.x的版本已经优化了,而且以后的项目也会转到3.x的版本,所以这里只是做一个记录,对底层换行机制的思想做一个分析,仅供学习用。
RichText的换行原理:
以文本换行为例(图片同样的道理),先用一个不带换行的label,算出该label的总长度L,然后和程序设定的宽w比较,w<L则一行足以,否则按w在L中比例来截取原字符串,作为第一行,剩余部分递归处理。看代码:
- <span style="font-size:14px;">void RichText::handleTextRenderer(const char *text, const char *fontName, float fontSize, const ccColor3B &color, GLubyte opacity)
- {
- /* 不换行时的label */
- CCLabelTTF* textRenderer = CCLabelTTF::create(text, fontName, fontSize);
- /* 不换行时的label宽度 */
- float textRendererWidth = textRenderer->getContentSize().width;
- /* 用设定的大小 - label宽度 */
- _leftSpaceWidth -= textRendererWidth;
- if (_leftSpaceWidth < 0.0f)
- {
- /* 需要换行,则需要按比例截取原字符串:先计算超出的宽度所占的比例 */
- float overstepPercent = (-_leftSpaceWidth) / textRendererWidth;
- std::string curText = text;
- /* 计算字符串的长度 */
- int stringLength = curText.length();
- /* 1-超出比例 = 当前(设定宽度所占比) 来截取字符串 */
- int leftLength = stringLength * (1.0f - overstepPercent);
- /* 使用stl标准库的string函数截取字串 */
- std::string leftWords = curText.substr(0, leftLength);
- std::string cutWords = curText.substr(leftLength, curText.length()-1);
- /* 字符串不为空,则绘制第一行label */
- if (leftLength > 0)
- {
- CCLabelTTF* leftRenderer = CCLabelTTF::create(leftWords.substr(0, leftLength).c_str(), fontName, fontSize);
- leftRenderer->setColor(color);
- leftRenderer->setOpacity(opacity);
- pushToContainer(leftRenderer);
- }
- /* 开启新的一行,并作递归处理 */
- addNewLine();
- handleTextRenderer(cutWords.c_str(), fontName, fontSize, color, opacity);
- }
- else
- {
- textRenderer->setColor(color);
- textRenderer->setOpacity(opacity);
- pushToContainer(textRenderer);
- }</span>
这里的重点是截取字串的方式,自带的substr不能很好的处理多字节字符的截取,中文会出现乱码,所以用自己写的函数实现,这个函数在网上能找到,但仍然有问题,我做了一些修改,将原来的substr改为自定义的“utf8_substr”函数:
- <span style="font-size:14px;">/*参数如下:
- *str:原字符串
- *start:截串的起始位置(起始位置未必准确,通过判断调整)
- *leng:截取的长度(同样长度未必准确)
- */
- static std::string utf8_substr(const std::string& str, unsigned long start, unsigned long leng)
- {
- if (leng==0)
- {
- return "";
- }
- unsigned long c, i, strLen, minIdx=std::string::npos, actualLength=std::string::npos;
- //有效(不乱码)的起始位置和有效原的截取长度与参数有偏移差
- long startOffset,lenOffset;
- for (i=0, strLen=str.length(); i <= strLen; i++)
- {
- //i 按照字符所占的字节数做跳转的,utf-8中文字符时 i 的值0 3 6 9.....
- if (i <= start)
- {
- minIdx = i; //保证最小索引在一个字符的初始字节上(中文字符有3个字节,这里指第一个字节)
- startOffset = start - minIdx;
- }
- if (i <= start+leng)
- {
- lenOffset = start+leng - i;
- actualLength = leng + abs(startOffset) - abs(lenOffset); //多余的字节放到下一段处理
- }
- /* 于所有字符的编码表中的范围做判断,该字符占几个字节 */
- c = (unsigned char) str[i];
- if (c<=127) i+=0;
- else if ((c & 0xE0) == 0xC0) i+=1;
- else if ((c & 0xF0) == 0xE0) i+=2;
- else if ((c & 0xF8) == 0xF0) i+=3;
- else return "";//invalid utf8
- }
- if (minIdx==std::string::npos || actualLength==std::string::npos)
- {
- return "";
- }
- //CCLOG(" result = %s",str.substr(minIdx,actualLength).c_str());
- return str.substr(minIdx,actualLength);
- }
- </span>
字符编码表:
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
将上面的utf8_substr函数添加到RichText.cpp中,替换类中用到substr的地方即可。