字体历史
在OpenGL的世界里,文字也是图像。在cocos中,一个Label其实就包含了一个或者多个Sprite。
在早期的计算机中,显示文字的方法是,将一种字体中的所有字符的纹理存储在一张大图中,然后根据需要显示的字符在精灵表中找到对应的位图显示到屏幕上,这就是点阵字。
显然这不能满足现代的需求,比如不同大小、文字间距、加粗、字体颜色、阴影、模糊,换行。而且对字符集较大的文字支持不友好,比如汉字有数万个字符,集成到一张精灵表中显然是不现实的,而且很多的文字可能很少用到,会浪费大量内存。
轮廓字体,是通过某种统一的协议,来对字体的每个字符进行描述,然后计算机可以借助该协议的库根据字符的描述信息在程序运行时动态生成字符的纹理进行显示。cocos引擎就采用了FreeType库来解析TrueType字体(TTF)。
FreeType字体引擎库
字体包含了一系列字符的字形描述的文件,而字体引擎则负责根据字符的字形描述信息、屏幕分辨率生成合适大小的纹理,其实就是光栅化的过程。
FreeType已被广泛用于Linux、IOS、Android、ChromeOS等平台,cocos也是。
字体文件
要生成字符的纹理,不仅需要字形描述信息,还有很多其他信息。一个字体文件包含的信息如下:
- 字形描述
- 每个字符都对应一个。
- 字符映射表:Charmap
- 字符到字形的映射,通过字符值找到字形描述。
- 字符有多种编码,一个字体文件可能包含多个字符映射表,一个TrueType字体一般都包含一个苹果特殊风格编码的字符映射表,一个Unicode编码的字符映射表。
- 字符度量
- 如何放置(组织)字符,字符可不是像砖块,一个个简单拼凑起来,不然会很难看,比如字母a、g的底部并不是在同一水平线上。
- 其他信息
字符度量
字符水平布局
字符垂直布局
- bounding box
- bbox,边界框,刚好包裹住字符,通过xMin,yMin,xMax,yMax来确定。
- bearingX:左跨距
- 原点orign到bbox左边的距离。水平布局是正数,垂直布局是负数。
- bearingY:上跨距
- 原点oring到bbox上边距的距离,水平布局是正数,垂直布局是负数
- advanceX:步进宽度
- 渲染完一个字形后,前进该字形的一个advanceX渲染下一个字形。
- advanceY:步进高度
- width:字符宽度
- 字体坐标未缩放时,等于xMax-xMin,可能被网格对齐修改。
height:字符高度
字体坐标未缩放时,等于yMax-yMin,可能被网格对齐修改。
字形解析
FreeType从解析字形到生成字符纹理的过程如下:
字形缩放
- 对字形中的所有点进行缩放,直至字形调整到指定大小,注意这可不是应用层的文字缩放,对于TTF字体来说,相同文字在不同尺寸上,字形会有变化,如果是生硬的缩放那文字会很难看。
- 网格对齐
- 对缩放后的字形轮廓进行网格对齐
- 字距微调
- 对网格对齐的轮廓进行字距微调。
cocos字体
UML类图
点击查看【processon】FontAtlas
字体的“部分字符”的纹理集。我们不可能说要用到汉字,加载一个汉字字体文件,就加载所有汉字的纹理,最好的方式是,需要绘制什么字符就创建什么字符的纹理,我们可以通过GlyphCollection来决定加载哪些字符的纹理。
根据FontAtlas,Label就能够绘制文字了,当然这些文字必须是包含在FontAtlas中,也就是已经创建了纹理的字符。 ```cpp struct FontLetterDefinition { // 描述了字符在精灵表中的纹理坐标、
float U; float V; float width; float height; float offsetX; float offsetY; int textureID; bool validDefinition; int xAdvance; };// 设计分辨率大小、以及相对应的纹理
- 对网格对齐的轮廓进行字距微调。
class CC_DLL FontAtlas : public Ref { …… protected:
//精灵表//key: pageindex,页索引,字体可能有多张精灵表,比如FontFreeType,字符较多尺寸较大,将分成多张纹理// FontCharmap、FontFNT一般都是一张精灵表就足够。//value: 字符的纹理集,注意一张纹理包含了多个字符,并不是一个字符的纹理。std::unordered_map<ssize_t, Texture2D*> _atlasTextures;//保存每个字符对应的FontLetterDefinitionstd::unordered_map<char32_t, FontLetterDefinition> _letterDefinitions;
};
<a name="ezlYG"></a>## Font(abstract)是个抽象类,表示加载到内存中的字体。必须实现能创建字符纹理的方法createFontAtlas。```cppclass CC_DLL Font : public Ref {public://创建 GlyphCollection 字符集合中字符的纹理。virtual FontAtlas* createFontAtlas() = 0;virtual int* getHorizontalKerningForTextUTF32(const std::u32string& text, int &outNumLetters) const = 0;};
FontFreeType
TrueType字体(TTF),由FreeType字体库进行解析。
该类包含了TTF字体配置信息,注意在创建它的时候是加载了TTF字体配置信息,而并没有生成文字的纹理,纹理是在需要绘制具体字符时,实时动态解析字体并生成纹理。
class CC_DLL FontFreeType : public Font{public:static FontFreeType* create(const std::string &fontName, float fontSize, GlyphCollection glyphs,const char *customGlyphs,bool distanceFieldEnabled = false, float outline = 0);private:static const char* _glyphASCII; //ASCII字符集// \"!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`// abcdefghijklmnopqrstuvwxyz{|}~¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿À// ÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ "static const char* _glyphNEHE; //键盘字符集// !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghi// jklmnopqrstuvwxyz{|}~ ";static FT_Library _FTlibrary; //FreeType字体引擎库实例,全局共享一个static bool _FTInitialized; //必须先初始化字体引擎库。FT_Face _fontRef; // 一个face对应一个字体,比如Arial、Arial Italic是两种不同的外观。FT_Stroker _stroker; // 用于描边FT_Encoding _encoding; // 当前系统环境采用哪些字符编码,这决定了使用哪张字符映射表。std::string _fontName; // 字体文件路径,可以是相对路径bool _distanceFieldEnabled; // 用于发光特效float _outlineSize; // 描边特效int _lineHeight; // 行高FontAtlas* _fontAtlas; // 字符纹理集GlyphCollection _usedGlyphs; // 字符集类型std::string _customGlyphs; // 自定义的字符集,字符集类型是CUSTOMtypedef struct _DataRef{Data data;unsigned int referenceCount;} DataRef;// 缓存字体文件数据static std::unordered_map<std::string, DataRef> s_cacheFontData;};// 加载一个TTF字体FontFreeType * FontFreeType::create(const std::string &fontName, float fontSize,GlyphCollection glyphs, const char *customGlyphs,bool distanceFieldEnabled /* = false */,float outline /* = 0 */) {//初始化字体引擎库及一些相关模块如用于描边的StrokerFontFreeType *tempFont = new (std::nothrow) FontFreeType(distanceFieldEnabled,outline);//设置字体的字符集,默认是ASCIItempFont->setGlyphCollection(glyphs, customGlyphs);//enum class GlyphCollection {// DYNAMIC, //动态设置字符集,一般用于被使用的字符不可预知,比如输入框输入字符。// NEHE, //键盘字符集// ASCII, //ASCII字符集,比键盘更多一点// CUSTOM //自定义字符集,一般用于被使用到的字符可预知,可以有效减少内存占用。//};//加载字体,创建FT_Face、选择编码(charmap字符映射表),设置字体大小。if (!tempFont->createFontObject(fontName, fontSize)) {delete tempFont;return nullptr;}}FontFreeType::FontFreeType(bool distanceFieldEnabled /* = false */, float outline /* = 0 */) {//如果要设置描边,就加载Stroker模块if (outline > 0.0f) {......FT_Stroker_New(FontFreeType::getFTLibrary(), &_stroker);......}}// 加载字体bool FontFreeType::createFontObject(const std::string &fontName, float fontSize) {FT_Face face; //必须选择字体中的一个外观,比如Arial还是Arail Italic// 字体文件数据加载到内存会被缓存起来// 首先查找缓存中的字体文件数据,否则加载字体数据到内存,并缓存起来。auto it = s_cacheFontData.find(fontName);if (it != s_cacheFontData.end()) {(*it).second.referenceCount += 1;}else {s_cacheFontData[fontName].referenceCount = 1;s_cacheFontData[fontName].data = FileUtils::getInstance()->getDataFromFile(fontName);}//创建FT_Faceif (FT_New_Memory_Face(getFTLibrary(), s_cacheFontData[fontName].data.getBytes(), s_cacheFontData[fontName].data.getSize(), 0, &face ))return false;// 选择charmap字符映射表,选择了UNICODE编码的表。if (FT_Select_Charmap(face, FT_ENCODING_UNICODE)){......}// 设置字体大小if (FT_Set_Char_Size(face, fontSizePoints, fontSizePoints, dpi, dpi)) return false;......}//创建当前字符集的字符纹理FontAtlas * FontFreeType::createFontAtlas(){if (_fontAtlas == nullptr) {_fontAtlas = new (std::nothrow) FontAtlas(*this);if (_fontAtlas && _usedGlyphs != GlyphCollection::DYNAMIC) {std::u32string utf32;//获取到字符集if (StringUtils::UTF8ToUTF32(getGlyphCollection(), utf32)) {//生成字符集的纹理_fontAtlas->prepareLetterDefinitions(utf32);}}// this->autorelease();}return _fontAtlas;}// A function used to load a single glyph into the glyph slot of a face object,// according to its character code.unsigned char* FontFreeType::getGlyphBitmap(uint64_t theChar, long &outWidth,long &outHeight, Rect &outRect,int &xAdvance){......}//设置字符集void FontFreeType::setGlyphCollection(GlyphCollection glyphs, const char* customGlyphs /* = nullptr */) {_usedGlyphs = glyphs;if (glyphs == GlyphCollection::CUSTOM) {_customGlyphs = customGlyphs;}}//获取字符集const char* FontFreeType::getGlyphCollection() const {const char* glyphCollection = nullptr;switch (_usedGlyphs) {case cocos2d::GlyphCollection::DYNAMIC:break;case cocos2d::GlyphCollection::NEHE:glyphCollection = _glyphNEHE;break;case cocos2d::GlyphCollection::ASCII:glyphCollection = _glyphASCII;break;case cocos2d::GlyphCollection::CUSTOM:glyphCollection = _customGlyphs.c_str();break;default:break;}return glyphCollection;}
FontFnt
FontFreeType是通过FreeType引擎库动态解析轮廓字体生成精灵表来绘制文字。这个在渲染上有一定性能影响。
而FontFnt则将这一步工作由程序外部提前完成(GlyphDesigner工具),在创建的时候即由外部提供字符的精灵表,以及包含的字符的度量信息,这些数据都在一个.fnt文件中。
和FontFreeType相比,少了根据字符动态解析轮廓字体生成精灵表的过程,性能上要快,和绘制普通精灵差不多。但是可想而知,在灵活性上也打折扣,无法改变字体,包含的字符也有限,比如像汉字,我们不可能把所有的汉字字符都生成精灵表,那将占用很大内存,而且很多字符可能用不到,浪费内存。
优点是可以借助GlyphDesigner工具很方便的调整字体颜色、字体特效(发光、加粗、边框)。
class CC_DLL FontFNT : public Font {public://创建的时候只需要提供.fnt文件,包含字符的精灵表以及字符的度量信息。static FontFNT * create(const std::string& fntFilePath, const Vec2& imageOffset = Vec2::ZERO);protected:BMFontConfiguration * _configuration;Vec2 _imageOffset;//User defined font sizefloat _fontSize;};// 全局缓存了.fnt的配置数据static Map<std::string, BMFontConfiguration*>* s_configurations = nullptr;FontFNT * FontFNT::create(const std::string& fntFilePath,const Vec2& imageOffset /* = Vec2::ZERO */) {//加载的.fnt配置数据BMFontConfiguration *newConf = FNTConfigLoadFile(fntFilePath);// 加载字符的精灵表Texture2D *tempTexture = Director::getInstance()->getTextureCache()->addImage(newConf->getAtlasName());......}FontAtlas * FontFNT::createFontAtlas() {FontAtlas *tempAtlas = new (std::nothrow) FontAtlas(*this);if (tempAtlas == nullptr)return nullptr;for (auto&& e : _configuration->_fontDefDictionary) {BMFontDef& fontDef = e.second;FontLetterDefinition tempDefinition;......//LetterDefinition:字符在精灵表中的位置等信息。tempAtlas->addLetterDefinition(fontDef.charID,tempDefinition);}// add the texture (only one texture for now)Texture2D *tempTexture = Director::getInstance()->getTextureCache()->addImage(_configuration->getAtlasName());......// add the texturetempAtlas->addTexture(tempTexture, 0);......}//.fnt文件的数据class CC_DLL BMFontConfiguration : public Ref {// FIXME: Creating a public interface so that the bitmapFontArray[] is accessiblepublic://@public// BMFont definitionsstd::unordered_map<int /* key */, BMFontDef /* fontDef */> _fontDefDictionary;//! FNTConfig: Common Height Should be signed (issue #1343)int _commonHeight;//! PaddingBMFontPadding _padding;//! atlas namestd::string _atlasName;//! values for kerningstd::unordered_map<uint64_t /* key */, int /* amount */> _kerningDictionary;// Character Set defines the letters that actually exist in the fontstd::set<unsigned int> *_characterSet;//! Font Sizeint _fontSize;}
编辑工具
Bitmap Font编辑工具
- http://glyphdesigner.71squared.com/ (Commercial, Mac OS X)
- http://www.n4te.com/hiero/hiero.jnlp (Free, Java)
- http://slick.cokeandcode.com/demos/hiero.jnlp (Free, Java)
http://www.angelcode.com/products/bmfont/ (Free, Windows only)
FontCharmap
FontFnt相对于FontFreeType,简化了生成字符精灵表和度量信息的过程。根据度量信息和精灵表来绘制文字。
而FontCharmap相对于FontFnt则更进一步简化,简化在度量信息上,每个字符的尺寸相等,字符都是整齐的并列排列,并没有上面复杂的字符度量信息,这样几乎就没有度量信息的解析工作,只需要一张字符的精灵表和字符的大小就可以了,设定精灵表中第一个字符的索引值,只需要按照索引偏移值即可获得指定字符的纹理。 ```cpp class FontCharMap : public Font {
public: static FontCharMap * create(const std::string& charMapFile, //精灵表文件int itemWidth, //字符尺寸,即精灵表中元素尺寸int itemHeight,int startCharMap); //第一个字符的索引值//即精灵表中的第一个元素索引值
static FontCharMap create(Texture2D texture,
int itemWidth,intitemHeight,int startCharMap);
//也可以通过plist配置文件来加载,它里面其实就包含了上面参数的信息。 static FontCharMap * create(const std::string& plistFile); };
FontAtlas FontCharMap::createFontAtlas() { FontAtlas tempAtlas = new (std::nothrow) FontAtlas(*this);
FontLetterDefinition tempDefinition;......int charId = _mapStartChar;for (int row = 0; row < itemsPerColumn; ++row) {for (int col = 0; col < itemsPerRow; ++col) {......tempAtlas->addLetterDefinition(charId, tempDefinition);}}tempAtlas->addTexture(_texture,0);return tempAtlas;
}
<a name="F5E0N"></a>## FontAtlasCache由FontAtlasCache统一管理FontAtlas,就像TextureCache管理Texture一样。如果FontAtlas已存在,就直接返回,否则创建FontAtlas实例。```cppclass CC_DLL FontAtlasCache{public://获取/创建TTF的FontAtlasstatic FontAtlas* getFontAtlasTTF(const _ttfConfig* config);//获取/创建.fnt的FontAtlasstatic FontAtlas* getFontAtlasFNT(const std::string& fontFileName, const Vec2& imageOffset = Vec2::ZERO);//获取/创建charmap的FontAtlasstatic FontAtlas* getFontAtlasCharMap(const std::string& charMapFile, int itemWidth, int itemHeight, int startCharMap);static FontAtlas* getFontAtlasCharMap(Texture2D* texture, int itemWidth, int itemHeight, int startCharMap);static FontAtlas* getFontAtlasCharMap(const std::string& plistFile);static bool releaseFontAtlas(FontAtlas *atlas);/** Removes cached data.It will purge the textures atlas and if multiple texture exist in one FontAtlas.*/static void purgeCachedData();/** Release current FNT texture and reload it.CAUTION : All component use this font texture should be reset font name, though the file name is same!otherwise, it will cause program crash!*/static void reloadFontAtlasFNT(const std::string& fontFileName, const Vec2& imageOffset = Vec2::ZERO);/** Unload all texture atlas texture create by special file name.CAUTION : All component use this font texture should be reset font name, though the file name is same!otherwise, it will cause program crash!*/static void unloadFontAtlasTTF(const std::string& fontFileName);private:// key: 字体文件的全路径+其他参数的合成// 它唯一确定了FontAtlas,即key不同,那将使用全新的FontAtlas// 比如TTF文字,绘制的文字尺寸不同也将导致key不同,而创建新的FontAtlas,进而导致创建新的纹理!// 所以使用TTF字体时,要尽量少使用不同尺寸的文字。// value: 对应创建的FontAtlas字符纹理集(精灵表集)static std::unordered_map<std::string, FontAtlas *> _atlasMap;};FontAtlas* FontAtlasCache::getFontAtlasTTF(const _ttfConfig* config) {//ttf字体文件路径auto realFontFilename = FileUtils::getInstance()->getNewFilename(config->fontFilePath); // resolves real file path, to prevent storing multiple atlases for the same file.//从下面我们可以看到,生成ttf的key的因子。//key = useDistanceField + fontSize + outlineSize + realFontFilename组成//上面的任意一个因素的不同,都将导致创建不同的FontAtlas。std::string key;char keyPrefix[ATLAS_MAP_KEY_PREFIX_BUFFER_SIZE];snprintf(keyPrefix, ATLAS_MAP_KEY_PREFIX_BUFFER_SIZE, useDistanceField ?"df %.2f %d " : "%.2f %d ", config->fontSize, config->outlineSize);std::string atlasName(keyPrefix);atlasName += realFontFilename;auto it = _atlasMap.find(atlasName);if ( it == _atlasMap.end() ) {auto font = FontFreeType::create(realFontFilename, config->fontSize, config->glyphs,config->customGlyphs, useDistanceField, config->outlineSize);......auto tempAtlas = font->createFontAtlas();......}}elsereturn it->second;return nullptr;}FontAtlas* FontAtlasCache::getFontAtlasFNT(const std::string& fontFileName,const Vec2& imageOffset /* = Vec2::ZERO */) {// 从下面我们可以看出,FNT文字的key的参考因子// key = imageOffset + realFontFilename// imageOffset用于计算字符在精灵表中的坐标(FontLetterDefinition)char keyPrefix[ATLAS_MAP_KEY_PREFIX_BUFFER_SIZE];snprintf(keyPrefix, ATLAS_MAP_KEY_PREFIX_BUFFER_SIZE, "%.2f %.2f ", imageOffset.x, imageOffset.y);std::string atlasName(keyPrefix);atlasName += realFontFilename;auto it = _atlasMap.find(atlasName);if ( it == _atlasMap.end() ) {auto font = FontFNT::create(realFontFilename, imageOffset);......auto tempAtlas = font->createFontAtlas();......}elsereturn it->second;return nullptr;}FontAtlas* FontAtlasCache::getFontAtlasCharMap(Texture2D* texture, int itemWidth, int itemHeight, int startCharMap) {//charmap比简单,不多做解释char key[ATLAS_MAP_KEY_PREFIX_BUFFER_SIZE];sprintf(key,"name:%u_%d_%d_%d",texture->getName(),itemWidth,itemHeight,startCharMap);std::string atlasName = key;auto it = _atlasMap.find(atlasName);if ( it == _atlasMap.end() ) {auto font = FontCharMap::create(texture,itemWidth,itemHeight,startCharMap);......auto tempAtlas = font->createFontAtlas();......}elsereturn it->second;return nullptr;}
Label
通过前面的学习我们已经知道,绘制文字的本质就是绘制Sprite精灵,很多的文字字符的纹理组成一张大的纹理(精灵表),这里我们就很自然的想到,如果绘制的文字字符的纹理都在一张大纹理上,岂不是可以利用SpriteBatchNode大大提高效率?答案是很明显的,cocos确实采用了这种方案。
class Label : public Node {......protected:......Vector<SpriteBatchNode*> _batchNodes;// 一个SpriteBatchNode下的所有Sprite共享同一个Texture// 为什么是数组不是就一个?因为可能字符尺寸太大或者数量太多,而分成了多张精灵表。// 这种情况针对的是TTF字体,即动态生成的轮廓字体。......}void Label::onDraw(const Mat4& transform, bool /*transformUpdated*/) {......for (auto&& batchNode : _batchNodes) {//每个SpriteBatchNode绘制同一张精灵表中部分文字。batchNode->getTextureAtlas()->drawQuads();}}
创建方法
class Label : public Node {// Allocates and initializes a Label, base on platform-dependent API// 这是底层调用了platform平台的API创建字符纹理。static Label* createWithSystemFont(const std::string& text, const std::string& font, float fontSize,const Size& dimensions = Size::ZERO, TextHAlignment hAlignment = TextHAlignment::LEFT,TextVAlignment vAlignment = TextVAlignment::TOP);// Allocates and initializes a Label, base on FreeType2// True Type Font绘制文字。static Label * createWithTTF(const std::string& text, const std::string& fontFilePath, float fontSize,const Size& dimensions = Size::ZERO, TextHAlignment hAlignment = TextHAlignment::LEFT,TextVAlignment vAlignment = TextVAlignment::TOP);// 同上static Label* createWithTTF(const TTFConfig& ttfConfig, const std::string& text,TextHAlignment hAlignment = TextHAlignment::LEFT, int maxLineWidth = 0);// FontFnt文字,static Label* createWithBMFont(const std::string& bmfontPath, const std::string& text,const TextHAlignment& hAlignment = TextHAlignment::LEFT, int maxLineWidth = 0,const Vec2& imageOffset = Vec2::ZERO);// FontCharmap文字static Label * createWithCharMap(const std::string& charMapFile, int itemWidth, int itemHeight, int startCharMap);static Label * createWithCharMap(Texture2D* texture, int itemWidth, int itemHeight, int startCharMap);static Label * createWithCharMap(const std::string& plistFile);}
createWithSystemFont
系统字体和TTF、Bitmap Font、Charmap不同,它是由通过系统API直接返回一张指定文字字符的纹理,这比charmap都简单,只有一张字符的纹理,直接进行绘制就行了,同时也就限制了很多功能,比如文字特效等。
void Label::updateContent() {if (_systemFontDirty) {//系统字体并不是走SpriteBatchNode路线。if (_fontAtlas) {_batchNodes.clear();CC_SAFE_RELEASE_NULL(_reusedLetter);FontAtlasCache::releaseFontAtlas(_fontAtlas);_fontAtlas = nullptr;}_systemFontDirty = false;}......else {auto fontDef = _getFontDefinition();// 直接就是创建了一个SpritecreateSpriteForSystemFont(fontDef);if (_shadowEnabled) {createShadowSpriteForSystemFont(fontDef);}}void Label::createSpriteForSystemFont(const FontDefinition& fontDef) {//STRING_TEXTURE:表示调用底层系统API,根据文字符创建一张它的纹理。_currentLabelType = LabelType::STRING_TEXTURE;auto texture = new (std::nothrow) Texture2D;// 比如显示文字 abc,通过这一句就创建了一张abc字样的纹理,直接绘制即可// 这里调用了底层系统API,所以这是平台相关的(IOS、Android、Windows)texture->initWithString(_utf8Text.c_str(), fontDef);_textSprite = Sprite::createWithTexture(texture);......}void Label::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags) {......if (_systemFontDirty || _contentDirty) {updateContent();}......if (!_children.empty()) {......this->drawSelf(visibleByCamera, renderer, flags);......}else {this->drawSelf(visibleByCamera, renderer, flags);}......}void Label::drawSelf(bool visibleByCamera, Renderer* renderer, uint32_t flags) {if (_textSprite) {//系统字体,其实就是绘制这个_textSprite精灵节点if (_shadowNode) {_shadowNode->visit(renderer, _modelViewTransform, flags);}_textSprite->visit(renderer, _modelViewTransform, flags);}......}
Label常用API
待完成,这只是API的调用,日后再补充完善。
