1 /****************************************************************************
  2  Copyright (c) 2008-2010 Ricardo Quesada
  3  Copyright (c) 2011-2012 cocos2d-x.org
  4  Copyright (c) 2013-2014 Chukong Technologies Inc.
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25 
 26  Use any of these editors to generate BMFonts:
 27  http://glyphdesigner.71squared.com/ (Commercial, Mac OS X)
 28  http://www.n4te.com/hiero/hiero.jnlp (Free, Java)
 29  http://slick.cokeandcode.com/demos/hiero.jnlp (Free, Java)
 30  http://www.angelcode.com/products/bmfont/ (Free, Windows only)
 31  ****************************************************************************/
 32 /**
 33  * @constant
 34  * @type Number
 35  */
 36 cc.LABEL_AUTOMATIC_WIDTH = -1;
 37 
 38 /**
 39  * <p>cc.LabelBMFont is a subclass of cc.SpriteBatchNode.</p>
 40  *
 41  * <p>Features:<br/>
 42  * <ul><li>- Treats each character like a cc.Sprite. This means that each individual character can be:</li>
 43  * <li>- rotated</li>
 44  * <li>- scaled</li>
 45  * <li>- translated</li>
 46  * <li>- tinted</li>
 47  * <li>- change the opacity</li>
 48  * <li>- It can be used as part of a menu item.</li>
 49  * <li>- anchorPoint can be used to align the "label"</li>
 50  * <li>- Supports AngelCode text format</li></ul></p>
 51  *
 52  * <p>Limitations:<br/>
 53  * - All inner characters are using an anchorPoint of (0.5, 0.5) and it is not recommend to change it
 54  * because it might affect the rendering</p>
 55  *
 56  * <p>cc.LabelBMFont implements the protocol cc.LabelProtocol, like cc.Label and cc.LabelAtlas.<br/>
 57  * cc.LabelBMFont has the flexibility of cc.Label, the speed of cc.LabelAtlas and all the features of cc.Sprite.<br/>
 58  * If in doubt, use cc.LabelBMFont instead of cc.LabelAtlas / cc.Label.</p>
 59  *
 60  * <p>Supported editors:<br/>
 61  * http://glyphdesigner.71squared.com/ (Commercial, Mac OS X)<br/>
 62  * http://www.n4te.com/hiero/hiero.jnlp (Free, Java)<br/>
 63  * http://slick.cokeandcode.com/demos/hiero.jnlp (Free, Java)<br/>
 64  * http://www.angelcode.com/products/bmfont/ (Free, Windows only)</p>
 65  * @class
 66  * @extends cc.SpriteBatchNode
 67  *
 68  * @property {String}   string          - Content string of label
 69  * @property {Number}   textAlign       - Horizontal Alignment of label, cc.TEXT_ALIGNMENT_LEFT|cc.TEXT_ALIGNMENT_CENTER|cc.TEXT_ALIGNMENT_RIGHT
 70  * @property {Number}   boundingWidth   - Width of the bounding box of label, the real content width is limited by boundingWidth
 71  *
 72  * @param {String} str
 73  * @param {String} fntFile
 74  * @param {Number} [width=-1]
 75  * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT]
 76  * @param {cc.Point} [imageOffset=cc.p(0,0)]
 77  *
 78  * @example
 79  * // Example 01
 80  * var label1 = new cc.LabelBMFont("Test case", "test.fnt");
 81  *
 82  * // Example 02
 83  * var label2 = new cc.LabelBMFont("test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT);
 84  *
 85  * // Example 03
 86  * var label3 = new cc.LabelBMFont("This is a \n test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT, cc.p(0,0));
 87  */
 88 cc.LabelBMFont = cc.SpriteBatchNode.extend(/** @lends cc.LabelBMFont# */{
 89     //property string is Getter and Setter.
 90     //property textAlign is Getter and Setter.
 91     //property boundingWidth is Getter and Setter.
 92     _opacityModifyRGB: false,
 93 
 94     _string: "",
 95     _config: null,
 96 
 97     // name of fntFile
 98     _fntFile: "",
 99 
100     // initial string without line breaks
101     _initialString: "",
102 
103     // alignment of all lines
104     _alignment: cc.TEXT_ALIGNMENT_CENTER,
105 
106     // max width until a line break is added
107     _width: -1,
108     _lineBreakWithoutSpaces: false,
109     _imageOffset: null,
110 
111     _reusedChar: null,
112 
113     _textureLoaded: false,
114     _className: "LabelBMFont",
115 
116     _createRenderCmd: function(){
117         if(cc._renderType === cc._RENDER_TYPE_WEBGL)
118             return new cc.LabelBMFont.WebGLRenderCmd(this);
119         else
120             return new cc.LabelBMFont.CanvasRenderCmd(this);
121     },
122 
123     _setString: function (newString, needUpdateLabel) {
124         if (!needUpdateLabel) {
125             this._string = newString;
126         } else {
127             this._initialString = newString;
128         }
129         var locChildren = this._children;
130         if (locChildren) {
131             for (var i = 0; i < locChildren.length; i++) {
132                 var selNode = locChildren[i];
133                 if (selNode)
134                     selNode.setVisible(false);
135             }
136         }
137         if (this._textureLoaded) {
138             this.createFontChars();
139             if (needUpdateLabel)
140                 this.updateLabel();
141         }
142     },
143 
144     /**
145      * Constructor function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. <br />
146      * creates a bitmap font atlas with an initial string and the FNT file.
147      * @param {String} str
148      * @param {String} fntFile
149      * @param {Number} [width=-1]
150      * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT]
151      * @param {cc.Point} [imageOffset=cc.p(0,0)]
152      */
153     ctor: function (str, fntFile, width, alignment, imageOffset) {
154         cc.SpriteBatchNode.prototype.ctor.call(this);
155         this._imageOffset = cc.p(0, 0);
156         this._reusedChar = [];
157         this._cascadeColorEnabled = true;
158         this._cascadeOpacityEnabled = true;
159         this.initWithString(str, fntFile, width, alignment, imageOffset);
160     },
161 
162     /**
163      * return  texture is loaded
164      * @returns {boolean}
165      */
166     textureLoaded: function () {
167         return this._textureLoaded;
168     },
169 
170     /**
171      * add texture loaded event listener. <br />
172      * Will execute the callback in the loaded.
173      * @param {Function} callback
174      * @param {Object} target
175      * @deprecated since 3.1, please use addEventListener instead
176      */
177     addLoadedEventListener: function (callback, target) {
178         this.addEventListener("load", callback, target);
179     },
180 
181     /**
182      * Conforms to cc.RGBAProtocol protocol.
183      * @return {Boolean}
184      */
185     isOpacityModifyRGB: function () {
186         return this._opacityModifyRGB;
187     },
188 
189     /**
190      * Set whether to support cc.RGBAProtocol protocol
191      * @param {Boolean} opacityModifyRGB
192      */
193     setOpacityModifyRGB: function (opacityModifyRGB) {
194         this._opacityModifyRGB = opacityModifyRGB;
195         var locChildren = this._children;
196         if (locChildren) {
197             for (var i = 0; i < locChildren.length; i++) {
198                 var node = locChildren[i];
199                 if (node)
200                     node.opacityModifyRGB = this._opacityModifyRGB;
201             }
202         }
203     },
204 
205     _changeTextureColor: function () {
206         this._renderCmd._changeTextureColor();
207     },
208 
209     /**
210      * Initialization of the node, please do not call this function by yourself, you should pass the parameters to constructor to initialize it
.
211      */
212     init: function () {
213         return this.initWithString(null, null, null, null, null);
214     },
215 
216     /**
217      * init a bitmap font atlas with an initial string and the FNT file
218      * @param {String} str
219      * @param {String} fntFile
220      * @param {Number} [width=-1]
221      * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT]
222      * @param {cc.Point} [imageOffset=cc.p(0,0)]
223      * @return {Boolean}
224      */
225     initWithString: function (str, fntFile, width, alignment, imageOffset) {
226         var self = this, theString = str || "";
227         var cmd = this._renderCmd;
228 
229         if (self._config)
230             cc.log("cc.LabelBMFont.initWithString(): re-init is no longer supported");
231 
232         var texture;
233         if (fntFile) {
234             var newConf = cc.loader.getRes(fntFile);
235             if (!newConf) {
236                 cc.log("cc.LabelBMFont.initWithString(): Impossible to create font. Please check file");
237                 return false;
238             }
239 
240             self._config = newConf;
241             self._fntFile = fntFile;
242             texture = cc.textureCache.addImage(newConf.atlasName);
243             var locIsLoaded = texture.isLoaded();
244             self._textureLoaded = locIsLoaded;
245             if (!locIsLoaded) {
246                 texture.addEventListener("load", function (sender) {
247                     var self1 = this;
248                     self1._textureLoaded = true;
249                     //reset the LabelBMFont
250                     self1.initWithTexture(sender, self1._initialString.length);
251                     self1.setString(self1._initialString, true);
252                     self1.dispatchEvent("load");
253                 }, self);
254             }
255         } else {
256             texture = new cc.Texture2D();
257             var image = new Image();
258             texture.initWithElement(image);
259             self._textureLoaded = false;
260         }
261 
262         if (self.initWithTexture(texture, theString.length)) {
263             self._alignment = alignment || cc.TEXT_ALIGNMENT_LEFT;
264             self._imageOffset = imageOffset || cc.p(0, 0);
265             self._width = (width == null) ? -1 : width;
266 
267             self._realOpacity = 255;
268             self._realColor = cc.color(255, 255, 255, 255);
269 
270             self._contentSize.width = 0;
271             self._contentSize.height = 0;
272 
273             self.setAnchorPoint(0.5, 0.5);
274 
275             this._renderCmd._initBatchTexture();
276 
277             self.setString(theString, true);
278             return true;
279         }
280         return false;
281     },
282 
283     /**
284      * updates the font chars based on the string to render
285      */
286     createFontChars: function () {
287         var self = this;
288         var cmd = this._renderCmd;
289         var locTexture = cmd._texture || self.textureAtlas.texture;
290 
291         var nextFontPositionX = 0;
292 
293         var tmpSize = cc.size(0, 0);
294 
295         var longestLine = 0;
296 
297         var quantityOfLines = 1;
298 
299         var locStr = self._string;
300         var stringLen = locStr ? locStr.length : 0;
301 
302         if (stringLen === 0)
303             return;
304 
305         var i, locCfg = self._config, locKerningDict = locCfg.kerningDict,
306             locCommonH = locCfg.commonHeight, locFontDict = locCfg.fontDefDictionary;
307         for (i = 0; i < stringLen - 1; i++) {
308             if (locStr.charCodeAt(i) === 10) quantityOfLines++;
309         }
310 
311         var totalHeight = locCommonH * quantityOfLines;
312         var nextFontPositionY = -(locCommonH - locCommonH * quantityOfLines);
313 
314         var prev = -1;
315         for (i = 0; i < stringLen; i++) {
316             var key = locStr.charCodeAt(i);
317             if (key === 0) continue;
318 
319             if (key === 10) {
320                 //new line
321                 nextFontPositionX = 0;
322                 nextFontPositionY -= locCfg.commonHeight;
323                 continue;
324             }
325 
326             var kerningAmount = locKerningDict[(prev << 16) | (key & 0xffff)] || 0;
327             var fontDef = locFontDict[key];
328             if (!fontDef) {
329                 cc.log("cocos2d: LabelBMFont: character not found " + locStr[i]);
330 
331                 fontDef = {
332                     rect: {
333                         x: 0,
334                         y: 0,
335                         width: 0,
336                         height: 0
337                     },
338                     xOffset: 0,
339                     yOffset: 0,
340                     xAdvance: 0
341                 };
342             }
343 
344             var rect = cc.rect(fontDef.rect.x, fontDef.rect.y, fontDef.rect.width, fontDef.rect.height);
345             rect = cc.rectPixelsToPoints(rect);
346             rect.x += self._imageOffset.x;
347             rect.y += self._imageOffset.y;
348 
349             var fontChar = self.getChildByTag(i);
350 
351             if(!fontChar){
352                 fontChar = new cc.Sprite();
353                 fontChar.initWithTexture(locTexture, rect, false);
354                 fontChar._newTextureWhenChangeColor = true;
355                 this.addChild(fontChar, 0, i);
356             }else{
357                 this._renderCmd._updateCharTexture(fontChar, rect, key);
358             }
359 
360             // Apply label properties
361             fontChar.opacityModifyRGB = this._opacityModifyRGB;
362             this._renderCmd._updateCharColorAndOpacity(fontChar);
363 
364             var yOffset = locCfg.commonHeight - fontDef.yOffset;
365             var fontPos = cc.p(nextFontPositionX + fontDef.xOffset + fontDef.rect.width * 0.5 + kerningAmount,
366                 nextFontPositionY + yOffset - rect.height * 0.5 * cc.contentScaleFactor());
367             fontChar.setPosition(cc.pointPixelsToPoints(fontPos));
368 
369             // update kerning
370             nextFontPositionX += fontDef.xAdvance + kerningAmount;
371             prev = key;
372 
373             if (longestLine < nextFontPositionX)
374                 longestLine = nextFontPositionX;
375         }
376 
377         //If the last character processed has an xAdvance which is less that the width of the characters image, then we need
378         // to adjust the width of the string to take this into account, or the character will overlap the end of the bounding box
379         if(fontDef && fontDef.xAdvance < fontDef.rect.width)
380             tmpSize.width = longestLine - fontDef.xAdvance + fontDef.rect.width;
381         else
382             tmpSize.width = longestLine;
383         tmpSize.height = totalHeight;
384         self.setContentSize(cc.sizePixelsToPoints(tmpSize));
385     },
386 
387     /**
388      * Update String. <br />
389      * Only update this label display string.
390      * @param {Boolean} fromUpdate
391      */
392     updateString: function (fromUpdate) {
393         var self = this;
394         var locChildren = self._children;
395         if (locChildren) {
396             for (var i = 0, li = locChildren.length; i < li; i++) {
397                 var node = locChildren[i];
398                 if (node) node.visible = false;
399             }
400         }
401         if (self._config)
402             self.createFontChars();
403 
404         if (!fromUpdate)
405             self.updateLabel();
406     },
407 
408     /**
409      * Gets the text of this label
410      * @return {String}
411      */
412     getString: function () {
413         return this._initialString;
414     },
415 
416     /**
417      * Set the text
418      * @param {String} newString
419      * @param {Boolean|null} needUpdateLabel
420      */
421     setString: function (newString, needUpdateLabel) {
422         newString = String(newString);
423         if (needUpdateLabel == null)
424             needUpdateLabel = true;
425         if (newString == null || !cc.isString(newString))
426             newString = newString + "";
427 
428         this._initialString = newString;
429         this._setString(newString, needUpdateLabel);
430     },
431 
432     _setStringForSetter: function (newString) {
433         this.setString(newString, false);
434     },
435 
436     /**
437      * Set the text. <br />
438      * Change this Label display string.
439      * @deprecated since v3.0 please use .setString
440      * @param label
441      */
442     setCString: function (label) {
443         this.setString(label, true);
444     },
445 
446     // calc the text all with in a line
447     _getCharsWidth:function (startIndex, endIndex) {
448         if (endIndex <= 0)
449         {
450             return 0;
451         }
452         var curTextFirstSprite = this.getChildByTag(startIndex);
453         var curTextLastSprite = this.getChildByTag(startIndex + endIndex);
454         return this._getLetterPosXLeft(curTextLastSprite) - this._getLetterPosXLeft(curTextFirstSprite);
455     },
456 
457     _checkWarp:function (strArr, i, maxWidth, initStringWrapNum) {
458         var self = this;
459         var text = strArr[i];
460         var curLength = 0;
461         for (var strArrIndex = 0; strArrIndex < i; strArrIndex++)
462         {
463             curLength += strArr[strArrIndex].length;
464         }
465 
466         curLength = curLength + i - initStringWrapNum; // add the wrap line num
467 
468         var allWidth = self._getCharsWidth(curLength, strArr[i].length - 1);
469 
470         if (allWidth > maxWidth && text.length > 1) {
471             var fuzzyLen = text.length * ( maxWidth / allWidth ) | 0;
472             var tmpText = text.substr(fuzzyLen);
473             var width = allWidth - this._getCharsWidth(curLength + fuzzyLen, tmpText.length - 1);
474             var sLine;
475             var pushNum = 0;
476 
477             //Increased while cycle maximum ceiling. default 100 time
478             var checkWhile = 0;
479 
480             //Exceeded the size
481             while (width > maxWidth && checkWhile++ < 100) {
482                 fuzzyLen *= maxWidth / width;
483                 fuzzyLen = fuzzyLen | 0;
484                 tmpText = text.substr(fuzzyLen);
485                 width = allWidth - this._getCharsWidth(curLength + fuzzyLen, tmpText.length - 1);
486             }
487 
488             checkWhile = 0;
489 
490             //Find the truncation point
491             while (width < maxWidth && checkWhile++ < 100) {
492                 if (tmpText) {
493                     var exec = cc.LabelTTF._wordRex.exec(tmpText);
494                     pushNum = exec ? exec[0].length : 1;
495                     sLine = tmpText;
496                 }
497                 if (self._lineBreakWithoutSpaces) {
498                     pushNum = 0;
499                 }
500                 fuzzyLen = fuzzyLen + pushNum;
501                 tmpText = text.substr(fuzzyLen);
502                 width = allWidth - this._getCharsWidth(curLength + fuzzyLen, tmpText.length - 1);
503             }
504 
505             fuzzyLen -= pushNum;
506             if (fuzzyLen === 0) {
507                 fuzzyLen = 1;
508                 sLine = sLine.substr(1);
509             }
510 
511             var sText = text.substr(0, fuzzyLen), result;
512 
513             //symbol in the first
514             if (cc.LabelTTF.wrapInspection) {
515                 if (cc.LabelTTF._symbolRex.test(sLine || tmpText)) {
516                     result = cc.LabelTTF._lastWordRex.exec(sText);
517                     pushNum = result ? result[0].length : 0;
518                     if (self._lineBreakWithoutSpaces) {
519                         pushNum = 0;
520                     }
521                     fuzzyLen -= pushNum;
522 
523                     sLine = text.substr(fuzzyLen);
524                     sText = text.substr(0, fuzzyLen);
525                 }
526             }
527 
528             //To judge whether a English words are truncated
529             if (cc.LabelTTF._firsrEnglish.test(sLine)) {
530                 result = cc.LabelTTF._lastEnglish.exec(sText);
531                 if (result && sText !== result[0]) {
532                     pushNum = result[0].length;
533                     if (self._lineBreakWithoutSpaces) {
534                         pushNum = 0;
535                     }
536                     fuzzyLen -= pushNum;
537                     sLine = text.substr(fuzzyLen);
538                     sText = text.substr(0, fuzzyLen);
539                 }
540             }
541             strArr[i] = sLine || tmpText;
542             strArr.splice(i, 0, sText);
543         }
544     },
545 
546     /**
547      * Update Label. <br />
548      * Update this Label display string and more...
549      */
550     updateLabel: function () {
551         var self = this;
552         self.string = self._initialString;
553         var i, j, characterSprite;
554         // process string
555         // Step 1: Make multiline
556         if (self._width > 0) {
557             var stringArr = self.string.split('\n');
558             var wrapString = "";
559             var newWrapNum = 0;
560             var oldArrLength = 0;
561             for (i = 0; i < stringArr.length; i++) {
562                 oldArrLength = stringArr.length;
563                 this._checkWarp(stringArr, i, self._width * this._scaleX, newWrapNum);
564                 if (oldArrLength < stringArr.length) {
565                     newWrapNum++;
566                 }
567                 if (i > 0)
568                 {
569                     wrapString += "\n";
570                 }
571                 wrapString += stringArr[i];
572             }
573             wrapString = wrapString + String.fromCharCode(0);
574             self._setString(wrapString, false);
575         }
576 
577         // Step 2: Make alignment
578         if (self._alignment !== cc.TEXT_ALIGNMENT_LEFT) {
579             i = 0;
580 
581             var lineNumber = 0;
582             var strlen = self._string.length;
583             var last_line = [];
584 
585             for (var ctr = 0; ctr < strlen; ctr++) {
586                 if (self._string[ctr].charCodeAt(0) === 10 || self._string[ctr].charCodeAt(0) === 0) {
587                     var lineWidth = 0;
588                     var line_length = last_line.length;
589                     // if last line is empty we must just increase lineNumber and work with next line
590                     if (line_length === 0) {
591                         lineNumber++;
592                         continue;
593                     }
594                     var index = i + line_length - 1 + lineNumber;
595                     if (index < 0) continue;
596 
597                     var lastChar = self.getChildByTag(index);
598                     if (lastChar == null)
599                         continue;
600                     lineWidth = lastChar.getPositionX() + lastChar._getWidth() / 2;
601 
602                     var shift = 0;
603                     switch (self._alignment) {
604                         case cc.TEXT_ALIGNMENT_CENTER:
605                             shift = self.width / 2 - lineWidth / 2;
606                             break;
607                         case cc.TEXT_ALIGNMENT_RIGHT:
608                             shift = self.width - lineWidth;
609                             break;
610                         default:
611                             break;
612                     }
613 
614                     if (shift !== 0) {
615                         for (j = 0; j < line_length; j++) {
616                             index = i + j + lineNumber;
617                             if (index < 0) continue;
618                             characterSprite = self.getChildByTag(index);
619                             if (characterSprite)
620                                 characterSprite.x += shift;
621                         }
622                     }
623 
624                     i += line_length;
625                     lineNumber++;
626 
627                     last_line.length = 0;
628                     continue;
629                 }
630                 last_line.push(self._string[i]);
631             }
632         }
633     },
634 
635     /**
636      * Set text alignment.
637      * @param {Number} alignment
638      */
639     setAlignment: function (alignment) {
640         this._alignment = alignment;
641         this.updateLabel();
642     },
643 
644     _getAlignment: function () {
645         return this._alignment;
646     },
647 
648     /**
649      * Set the bounding width. <br />
650      * max with display width. The exceeding string will be wrapping.
651      * @param {Number} width
652      */
653     setBoundingWidth: function (width) {
654         this._width = width;
655         this.updateLabel();
656     },
657 
658     _getBoundingWidth: function () {
659         return this._width;
660     },
661 
662     /**
663      * Set the param to change English word warp according to whether the space. <br />
664      * default is false.
665      * @param {Boolean}  breakWithoutSpace
666      */
667     setLineBreakWithoutSpace: function (breakWithoutSpace) {
668         this._lineBreakWithoutSpaces = breakWithoutSpace;
669         this.updateLabel();
670     },
671 
672     /**
673      * Set scale. <br />
674      * Input a number, will be decrease or increase the font size. <br />
675      * @param {Number} scale
676      * @param {Number} [scaleY=null] default is scale
677      */
678     setScale: function (scale, scaleY) {
679         cc.Node.prototype.setScale.call(this, scale, scaleY);
680         this.updateLabel();
681     },
682 
683     /**
684      * Set scale of x. <br />
685      * Input a number, will be decrease or increase the font size. <br />
686      * Horizontal scale.
687      * @param {Number} scaleX
688      */
689     setScaleX: function (scaleX) {
690         cc.Node.prototype.setScaleX.call(this, scaleX);
691         this.updateLabel();
692     },
693 
694     /**
695      * Set scale of x. <br />
696      * Input a number, will be decrease or increase the font size. <br />
697      * Longitudinal scale.
698      * @param {Number} scaleY
699      */
700     setScaleY: function (scaleY) {
701         cc.Node.prototype.setScaleY.call(this, scaleY);
702         this.updateLabel();
703     },
704 
705     /**
706      * set fnt file path. <br />
707      * Change the fnt file path.
708      * @param {String} fntFile
709      */
710     setFntFile: function (fntFile) {
711         var self = this;
712         if (fntFile != null && fntFile !== self._fntFile) {
713             var newConf = cc.loader.getRes(fntFile);
714 
715             if (!newConf) {
716                 cc.log("cc.LabelBMFont.setFntFile() : Impossible to create font. Please check file");
717                 return;
718             }
719 
720             self._fntFile = fntFile;
721             self._config = newConf;
722 
723             var texture = cc.textureCache.addImage(newConf.atlasName);
724             var locIsLoaded = texture.isLoaded();
725             self._textureLoaded = locIsLoaded;
726             self.texture = texture;
727             this._renderCmd._updateFntFileTexture();
728             if (!locIsLoaded) {
729                 texture.addEventListener("load", function (sender) {
730                     var self1 = this;
731                     self1._textureLoaded = true;
732                     self1.texture = sender;
733                     self1.createFontChars();
734                     self1._changeTextureColor();
735                     self1.updateLabel();
736 
737                     self1.dispatchEvent("load");
738                 }, self);
739             } else {
740                 self.createFontChars();
741             }
742         }
743     },
744 
745     /**
746      * Return the fnt file path.
747      * @return {String}
748      */
749     getFntFile: function () {
750         return this._fntFile;
751     },
752 
753     setTexture: function(texture){
754         this._renderCmd.setTexture(texture);
755     },
756 
757     /**
758      * Set the AnchorPoint of the labelBMFont. <br />
759      * In order to change the location of label.
760      * @override
761      * @param {cc.Point|Number} point The anchor point of labelBMFont or The anchor point.x of labelBMFont.
762      * @param {Number} [y] The anchor point.y of labelBMFont.
763      */
764     setAnchorPoint: function (point, y) {
765         cc.Node.prototype.setAnchorPoint.call(this, point, y);
766         this.updateLabel();
767     },
768 
769     _setAnchorX: function (x) {
770         cc.Node.prototype._setAnchorX.call(this, x);
771         this.updateLabel();
772     },
773 
774     _setAnchorY: function (y) {
775         cc.Node.prototype._setAnchorY.call(this, y);
776         this.updateLabel();
777     },
778 
779     _atlasNameFromFntFile: function (fntFile) {},
780 
781     _kerningAmountForFirst: function (first, second) {
782         var ret = 0;
783         var key = (first << 16) | (second & 0xffff);
784         if (this._configuration.kerningDictionary) {
785             var element = this._configuration.kerningDictionary[key.toString()];
786             if (element)
787                 ret = element.amount;
788         }
789         return ret;
790     },
791 
792     _getLetterPosXLeft: function (sp) {
793         return sp.getPositionX() * this._scaleX - (sp._getWidth() * this._scaleX * sp._getAnchorX());
794     },
795 
796     _getLetterPosXRight: function (sp) {
797         return sp.getPositionX() * this._scaleX + (sp._getWidth() * this._scaleX * sp._getAnchorX());
798     },
799 
800     //Checking whether the character is a whitespace
801     _isspace_unicode: function(ch){
802         ch = ch.charCodeAt(0);
803         return  ((ch >= 9 && ch <= 13) || ch === 32 || ch === 133 || ch === 160 || ch === 5760
804             || (ch >= 8192 && ch <= 8202) || ch === 8232 || ch === 8233 || ch === 8239
805             || ch === 8287 || ch === 12288)
806     },
807 
808     _utf8_trim_ws: function(str){
809         var len = str.length;
810 
811         if (len <= 0)
812             return;
813 
814         var last_index = len - 1;
815 
816         // Only start trimming if the last character is whitespace..
817         if (this._isspace_unicode(str[last_index])) {
818             for (var i = last_index - 1; i >= 0; --i) {
819                 if (this._isspace_unicode(str[i])) {
820                     last_index = i;
821                 }
822                 else {
823                     break;
824                 }
825             }
826             this._utf8_trim_from(str, last_index);
827         }
828     },
829 
830     //Trims str st str=[0, index) after the operation.
831     //Return value: the trimmed string.
832     _utf8_trim_from: function(str, index){
833         var len = str.length;
834         if (index >= len || index < 0)
835             return;
836         str.splice(index, len);
837     }
838 });
839 
840 (function(){
841     var p = cc.LabelBMFont.prototype;
842     cc.EventHelper.prototype.apply(p);
843 
844     /** @expose */
845     p.string;
846     cc.defineGetterSetter(p, "string", p.getString, p._setStringForSetter);
847     /** @expose */
848     p.boundingWidth;
849     cc.defineGetterSetter(p, "boundingWidth", p._getBoundingWidth, p.setBoundingWidth);
850     /** @expose */
851     p.textAlign;
852     cc.defineGetterSetter(p, "textAlign", p._getAlignment, p.setAlignment);
853 })();
854 
855 /**
856  * creates a bitmap font atlas with an initial string and the FNT file
857  * @deprecated since v3.0 please use new cc.LabelBMFont
858  * @param {String} str
859  * @param {String} fntFile
860  * @param {Number} [width=-1]
861  * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT]
862  * @param {cc.Point} [imageOffset=cc.p(0,0)]
863  * @return {cc.LabelBMFont|Null}
864  */
865 cc.LabelBMFont.create = function (str, fntFile, width, alignment, imageOffset) {
866     return new cc.LabelBMFont(str, fntFile, width, alignment, imageOffset);
867 };
868 
869 cc._fntLoader = {
870     INFO_EXP: /info [^\n]*(\n|$)/gi,
871     COMMON_EXP: /common [^\n]*(\n|$)/gi,
872     PAGE_EXP: /page [^\n]*(\n|$)/gi,
873     CHAR_EXP: /char [^\n]*(\n|$)/gi,
874     KERNING_EXP: /kerning [^\n]*(\n|$)/gi,
875     ITEM_EXP: /\w+=[^ \r\n]+/gi,
876     INT_EXP: /^[\-]?\d+$/,
877 
878     _parseStrToObj: function (str) {
879         var arr = str.match(this.ITEM_EXP);
880         var obj = {};
881         if (arr) {
882             for (var i = 0, li = arr.length; i < li; i++) {
883                 var tempStr = arr[i];
884                 var index = tempStr.indexOf("=");
885                 var key = tempStr.substring(0, index);
886                 var value = tempStr.substring(index + 1);
887                 if (value.match(this.INT_EXP)) value = parseInt(value);
888                 else if (value[0] === '"') value = value.substring(1, value.length - 1);
889                 obj[key] = value;
890             }
891         }
892         return obj;
893     },
894 
895     /**
896      * Parse Fnt string.
897      * @param fntStr
898      * @param url
899      * @returns {{}}
900      */
901     parseFnt: function (fntStr, url) {
902         var self = this, fnt = {};
903         //padding
904         var infoObj = self._parseStrToObj(fntStr.match(self.INFO_EXP)[0]);
905         var paddingArr = infoObj["padding"].split(",");
906         var padding = {
907             left: parseInt(paddingArr[0]),
908             top: parseInt(paddingArr[1]),
909             right: parseInt(paddingArr[2]),
910             bottom: parseInt(paddingArr[3])
911         };
912 
913         //common
914         var commonObj = self._parseStrToObj(fntStr.match(self.COMMON_EXP)[0]);
915         fnt.commonHeight = commonObj["lineHeight"];
916         if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
917             var texSize = cc.configuration.getMaxTextureSize();
918             if (commonObj["scaleW"] > texSize.width || commonObj["scaleH"] > texSize.height)
919                 cc.log("cc.LabelBMFont._parseCommonArguments(): page can't be larger than supported");
920         }
921         if (commonObj["pages"] !== 1) cc.log("cc.LabelBMFont._parseCommonArguments(): only supports 1 page");
922 
923         //page
924         var pageObj = self._parseStrToObj(fntStr.match(self.PAGE_EXP)[0]);
925         if (pageObj["id"] !== 0) cc.log("cc.LabelBMFont._parseImageFileName() : file could not be found");
926         fnt.atlasName = cc.path.changeBasename(url, pageObj["file"]);
927 
928         //char
929         var charLines = fntStr.match(self.CHAR_EXP);
930         var fontDefDictionary = fnt.fontDefDictionary = {};
931         for (var i = 0, li = charLines.length; i < li; i++) {
932             var charObj = self._parseStrToObj(charLines[i]);
933             var charId = charObj["id"];
934             fontDefDictionary[charId] = {
935                 rect: {x: charObj["x"], y: charObj["y"], width: charObj["width"], height: charObj["height"]},
936                 xOffset: charObj["xoffset"],
937                 yOffset: charObj["yoffset"],
938                 xAdvance: charObj["xadvance"]
939             };
940         }
941 
942         //kerning
943         var kerningDict = fnt.kerningDict = {};
944         var kerningLines = fntStr.match(self.KERNING_EXP);
945         if (kerningLines) {
946             for (var i = 0, li = kerningLines.length; i < li; i++) {
947                 var kerningObj = self._parseStrToObj(kerningLines[i]);
948                 kerningDict[(kerningObj["first"] << 16) | (kerningObj["second"] & 0xffff)] = kerningObj["amount"];
949             }
950         }
951         return fnt;
952     },
953 
954     /**
955      * load the fnt
956      * @param realUrl
957      * @param url
958      * @param res
959      * @param cb
960      */
961     load: function (realUrl, url, res, cb) {
962         var self = this;
963         cc.loader.loadTxt(realUrl, function (err, txt) {
964             if (err) return cb(err);
965             cb(null, self.parseFnt(txt, url));
966         });
967     }
968 };
969 cc.loader.register(["fnt"], cc._fntLoader);
970