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