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.game.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             if (!locIsLoaded) {
728                 texture.addEventListener("load", function (sender) {
729                     var self1 = this;
730                     self1._textureLoaded = true;
731                     self1.texture = sender;
732                     self1.createFontChars();
733                     self1._changeTextureColor();
734                     self1.updateLabel();
735 
736                     self1.dispatchEvent("load");
737                 }, self);
738             } else {
739                 self.createFontChars();
740             }
741         }
742     },
743 
744     /**
745      * Return the fnt file path.
746      * @return {String}
747      */
748     getFntFile: function () {
749         return this._fntFile;
750     },
751 
752     setTexture: function(texture){
753         this._renderCmd.setTexture(texture);
754     },
755 
756     /**
757      * Set the AnchorPoint of the labelBMFont. <br />
758      * In order to change the location of label.
759      * @override
760      * @param {cc.Point|Number} point The anchor point of labelBMFont or The anchor point.x of labelBMFont.
761      * @param {Number} [y] The anchor point.y of labelBMFont.
762      */
763     setAnchorPoint: function (point, y) {
764         cc.Node.prototype.setAnchorPoint.call(this, point, y);
765         this.updateLabel();
766     },
767 
768     _setAnchorX: function (x) {
769         cc.Node.prototype._setAnchorX.call(this, x);
770         this.updateLabel();
771     },
772 
773     _setAnchorY: function (y) {
774         cc.Node.prototype._setAnchorY.call(this, y);
775         this.updateLabel();
776     },
777 
778     _atlasNameFromFntFile: function (fntFile) {},
779 
780     _kerningAmountForFirst: function (first, second) {
781         var ret = 0;
782         var key = (first << 16) | (second & 0xffff);
783         if (this._configuration.kerningDictionary) {
784             var element = this._configuration.kerningDictionary[key.toString()];
785             if (element)
786                 ret = element.amount;
787         }
788         return ret;
789     },
790 
791     _getLetterPosXLeft: function (sp) {
792         return sp.getPositionX() * this._scaleX - (sp._getWidth() * this._scaleX * sp._getAnchorX());
793     },
794 
795     _getLetterPosXRight: function (sp) {
796         return sp.getPositionX() * this._scaleX + (sp._getWidth() * this._scaleX * sp._getAnchorX());
797     },
798 
799     //Checking whether the character is a whitespace
800     _isspace_unicode: function(ch){
801         ch = ch.charCodeAt(0);
802         return  ((ch >= 9 && ch <= 13) || ch === 32 || ch === 133 || ch === 160 || ch === 5760
803             || (ch >= 8192 && ch <= 8202) || ch === 8232 || ch === 8233 || ch === 8239
804             || ch === 8287 || ch === 12288)
805     },
806 
807     _utf8_trim_ws: function(str){
808         var len = str.length;
809 
810         if (len <= 0)
811             return;
812 
813         var last_index = len - 1;
814 
815         // Only start trimming if the last character is whitespace..
816         if (this._isspace_unicode(str[last_index])) {
817             for (var i = last_index - 1; i >= 0; --i) {
818                 if (this._isspace_unicode(str[i])) {
819                     last_index = i;
820                 }
821                 else {
822                     break;
823                 }
824             }
825             this._utf8_trim_from(str, last_index);
826         }
827     },
828 
829     //Trims str st str=[0, index) after the operation.
830     //Return value: the trimmed string.
831     _utf8_trim_from: function(str, index){
832         var len = str.length;
833         if (index >= len || index < 0)
834             return;
835         str.splice(index, len);
836     }
837 });
838 
839 (function(){
840     var p = cc.LabelBMFont.prototype;
841     cc.EventHelper.prototype.apply(p);
842 
843     /** @expose */
844     p.string;
845     cc.defineGetterSetter(p, "string", p.getString, p._setStringForSetter);
846     /** @expose */
847     p.boundingWidth;
848     cc.defineGetterSetter(p, "boundingWidth", p._getBoundingWidth, p.setBoundingWidth);
849     /** @expose */
850     p.textAlign;
851     cc.defineGetterSetter(p, "textAlign", p._getAlignment, p.setAlignment);
852 })();
853 
854 /**
855  * creates a bitmap font atlas with an initial string and the FNT file
856  * @deprecated since v3.0 please use new cc.LabelBMFont
857  * @param {String} str
858  * @param {String} fntFile
859  * @param {Number} [width=-1]
860  * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT]
861  * @param {cc.Point} [imageOffset=cc.p(0,0)]
862  * @return {cc.LabelBMFont|Null}
863  */
864 cc.LabelBMFont.create = function (str, fntFile, width, alignment, imageOffset) {
865     return new cc.LabelBMFont(str, fntFile, width, alignment, imageOffset);
866 };
867 
868 cc._fntLoader = {
869     INFO_EXP: /info [^\n]*(\n|$)/gi,
870     COMMON_EXP: /common [^\n]*(\n|$)/gi,
871     PAGE_EXP: /page [^\n]*(\n|$)/gi,
872     CHAR_EXP: /char [^\n]*(\n|$)/gi,
873     KERNING_EXP: /kerning [^\n]*(\n|$)/gi,
874     ITEM_EXP: /\w+=[^ \r\n]+/gi,
875     INT_EXP: /^[\-]?\d+$/,
876 
877     _parseStrToObj: function (str) {
878         var arr = str.match(this.ITEM_EXP);
879         var obj = {};
880         if (arr) {
881             for (var i = 0, li = arr.length; i < li; i++) {
882                 var tempStr = arr[i];
883                 var index = tempStr.indexOf("=");
884                 var key = tempStr.substring(0, index);
885                 var value = tempStr.substring(index + 1);
886                 if (value.match(this.INT_EXP)) value = parseInt(value);
887                 else if (value[0] === '"') value = value.substring(1, value.length - 1);
888                 obj[key] = value;
889             }
890         }
891         return obj;
892     },
893 
894     /**
895      * Parse Fnt string.
896      * @param fntStr
897      * @param url
898      * @returns {{}}
899      */
900     parseFnt: function (fntStr, url) {
901         var self = this, fnt = {};
902         //padding
903         var infoObj = self._parseStrToObj(fntStr.match(self.INFO_EXP)[0]);
904         var paddingArr = infoObj["padding"].split(",");
905         var padding = {
906             left: parseInt(paddingArr[0]),
907             top: parseInt(paddingArr[1]),
908             right: parseInt(paddingArr[2]),
909             bottom: parseInt(paddingArr[3])
910         };
911 
912         //common
913         var commonObj = self._parseStrToObj(fntStr.match(self.COMMON_EXP)[0]);
914         fnt.commonHeight = commonObj["lineHeight"];
915         if (cc._renderType === cc.game.RENDER_TYPE_WEBGL) {
916             var texSize = cc.configuration.getMaxTextureSize();
917             if (commonObj["scaleW"] > texSize.width || commonObj["scaleH"] > texSize.height)
918                 cc.log("cc.LabelBMFont._parseCommonArguments(): page can't be larger than supported");
919         }
920         if (commonObj["pages"] !== 1) cc.log("cc.LabelBMFont._parseCommonArguments(): only supports 1 page");
921 
922         //page
923         var pageObj = self._parseStrToObj(fntStr.match(self.PAGE_EXP)[0]);
924         if (pageObj["id"] !== 0) cc.log("cc.LabelBMFont._parseImageFileName() : file could not be found");
925         fnt.atlasName = cc.path.changeBasename(url, pageObj["file"]);
926 
927         //char
928         var charLines = fntStr.match(self.CHAR_EXP);
929         var fontDefDictionary = fnt.fontDefDictionary = {};
930         for (var i = 0, li = charLines.length; i < li; i++) {
931             var charObj = self._parseStrToObj(charLines[i]);
932             var charId = charObj["id"];
933             fontDefDictionary[charId] = {
934                 rect: {x: charObj["x"], y: charObj["y"], width: charObj["width"], height: charObj["height"]},
935                 xOffset: charObj["xoffset"],
936                 yOffset: charObj["yoffset"],
937                 xAdvance: charObj["xadvance"]
938             };
939         }
940 
941         //kerning
942         var kerningDict = fnt.kerningDict = {};
943         var kerningLines = fntStr.match(self.KERNING_EXP);
944         if (kerningLines) {
945             for (var i = 0, li = kerningLines.length; i < li; i++) {
946                 var kerningObj = self._parseStrToObj(kerningLines[i]);
947                 kerningDict[(kerningObj["first"] << 16) | (kerningObj["second"] & 0xffff)] = kerningObj["amount"];
948             }
949         }
950         return fnt;
951     },
952 
953     /**
954      * load the fnt
955      * @param realUrl
956      * @param url
957      * @param res
958      * @param cb
959      */
960     load: function (realUrl, url, res, cb) {
961         var self = this;
962         cc.loader.loadTxt(realUrl, function (err, txt) {
963             if (err) return cb(err);
964             cb(null, self.parseFnt(txt, url));
965         });
966     }
967 };
968 cc.loader.register(["fnt"], cc._fntLoader);
969