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>- chage 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 
 90     //property string is Getter and Setter.
 91     //property textAlign is Getter and Setter.
 92     //property boundingWidth is Getter and Setter.
 93 
 94     _opacityModifyRGB: false,
 95 
 96     _string: "",
 97     _config: null,
 98 
 99     // name of fntFile
100     _fntFile: "",
101 
102     // initial string without line breaks
103     _initialString: "",
104 
105     // alignment of all lines
106     _alignment: cc.TEXT_ALIGNMENT_CENTER,
107 
108     // max width until a line break is added
109     _width: -1,
110     _lineBreakWithoutSpaces: false,
111     _imageOffset: null,
112 
113     _reusedChar: null,
114 
115     //texture RGBA
116     _displayedOpacity: 255,
117     _realOpacity: 255,
118     _displayedColor: null,
119     _realColor: null,
120     _cascadeColorEnabled: true,
121     _cascadeOpacityEnabled: true,
122 
123     _textureLoaded: false,
124     _loadedEventListeners: null,
125     _className: "LabelBMFont",
126 
127     _setString: function (newString, needUpdateLabel) {
128         if (!needUpdateLabel) {
129             this._string = newString;
130         } else {
131             this._initialString = newString;
132         }
133         var locChildren = this._children;
134         if (locChildren) {
135             for (var i = 0; i < locChildren.length; i++) {
136                 var selNode = locChildren[i];
137                 if (selNode)
138                     selNode.setVisible(false);
139             }
140         }
141         if (this._textureLoaded) {
142             this.createFontChars();
143 
144             if (needUpdateLabel)
145                 this.updateLabel();
146         }
147     },
148 
149     /**
150      * Constructor function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. <br />
151      * creates a bitmap font atlas with an initial string and the FNT file.
152      * @param {String} str
153      * @param {String} fntFile
154      * @param {Number} [width=-1]
155      * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT]
156      * @param {cc.Point} [imageOffset=cc.p(0,0)]
157      */
158     ctor: function (str, fntFile, width, alignment, imageOffset) {
159         var self = this;
160         cc.SpriteBatchNode.prototype.ctor.call(self);
161         self._imageOffset = cc.p(0, 0);
162         self._displayedColor = cc.color(255, 255, 255, 255);
163         self._realColor = cc.color(255, 255, 255, 255);
164         self._reusedChar = [];
165 
166         this.initWithString(str, fntFile, width, alignment, imageOffset);
167     },
168 
169     /**
170      * return  texture is loaded
171      * @returns {boolean}
172      */
173     textureLoaded: function () {
174         return this._textureLoaded;
175     },
176 
177     /**
178      * add texture loaded event listener. <br />
179      * Will execute the callback in the loaded.
180      * @param {Function} callback
181      * @param {Object} target
182      */
183     addLoadedEventListener: function (callback, target) {
184         if (!this._loadedEventListeners)
185             this._loadedEventListeners = [];
186         this._loadedEventListeners.push({eventCallback: callback, eventTarget: target});
187     },
188 
189     _callLoadedEventCallbacks: function () {
190         if (!this._loadedEventListeners)
191             return;
192         var locListeners = this._loadedEventListeners;
193         for (var i = 0, len = locListeners.length; i < len; i++) {
194             var selCallback = locListeners[i];
195             selCallback.eventCallback.call(selCallback.eventTarget, this);
196         }
197         locListeners.length = 0;
198     },
199 
200     /**
201      * Draw this font.
202      * @param {CanvasRenderingContext2D} ctx
203      */
204     draw: function (ctx) {
205         cc.SpriteBatchNode.prototype.draw.call(this, ctx);
206 
207         //LabelBMFont - Debug draw
208         if (cc.LABELBMFONT_DEBUG_DRAW) {
209             var size = this.getContentSize();
210             var pos = cc.p(0 | ( -this._anchorPointInPoints.x), 0 | ( -this._anchorPointInPoints.y));
211             var vertices = [cc.p(pos.x, pos.y), cc.p(pos.x + size.width, pos.y), cc.p(pos.x + size.width, pos.y + size.height), cc.p(pos.x, pos.y + size.height)];
212             cc._drawingUtil.setDrawColor(0, 255, 0, 255);
213             cc._drawingUtil.drawPoly(vertices, 4, true);
214         }
215     },
216 
217     //TODO
218     /**
219      * tint this label
220      * @param {cc.Color} color
221      */
222     setColor: function (color) {
223         var locDisplayed = this._displayedColor, locRealColor = this._realColor;
224         if ((locRealColor.r == color.r) && (locRealColor.g == color.g) && (locRealColor.b == color.b) && (locRealColor.a == color.a))
225             return;
226         locDisplayed.r = locRealColor.r = color.r;
227         locDisplayed.g = locRealColor.g = color.g;
228         locDisplayed.b = locRealColor.b = color.b;
229 
230         if (this._textureLoaded) {
231             if (this._cascadeColorEnabled) {
232                 var parentColor = cc.color.WHITE;
233                 var locParent = this._parent;
234                 if (locParent && locParent.cascadeColor)
235                     parentColor = locParent.getDisplayedColor();
236                 this.updateDisplayedColor(parentColor);
237             }
238         }
239     },
240 
241     /**
242      * Conforms to cc.RGBAProtocol protocol.
243      * @return {Boolean}
244      */
245     isOpacityModifyRGB: function () {
246         return this._opacityModifyRGB;
247     },
248 
249     /**
250      * Set whether to support cc.RGBAProtocol protocol
251      * @param {Boolean} opacityModifyRGB
252      */
253     setOpacityModifyRGB: function (opacityModifyRGB) {
254         this._opacityModifyRGB = opacityModifyRGB;
255         var locChildren = this._children;
256         if (locChildren) {
257             for (var i = 0; i < locChildren.length; i++) {
258                 var node = locChildren[i];
259                 if (node)
260                     node.opacityModifyRGB = this._opacityModifyRGB;
261             }
262         }
263     },
264 
265     /**
266      * Gets the real opacity.
267      * @returns {number}
268      */
269     getOpacity: function () {
270         return this._realOpacity;
271     },
272 
273     /**
274      * Gets the display opacity.
275      * @returns {number}
276      */
277     getDisplayedOpacity: function () {
278         return this._displayedOpacity;
279     },
280 
281     /**
282      * Override synthesized setOpacity to recurse items
283      * @param {Number} opacity
284      */
285     setOpacity: function (opacity) {
286         this._displayedOpacity = this._realOpacity = opacity;
287         if (this._cascadeOpacityEnabled) {
288             var parentOpacity = 255;
289             var locParent = this._parent;
290             if (locParent && locParent.cascadeOpacity)
291                 parentOpacity = locParent.getDisplayedOpacity();
292             this.updateDisplayedOpacity(parentOpacity);
293         }
294 
295         this._displayedColor.a = this._realColor.a = opacity;
296     },
297 
298     /**
299      * Override synthesized update pacity to recurse items
300      * @param parentOpacity
301      */
302     updateDisplayedOpacity: function (parentOpacity) {
303         this._displayedOpacity = this._realOpacity * parentOpacity / 255.0;
304         var locChildren = this._children;
305         for (var i = 0; i < locChildren.length; i++) {
306             var locChild = locChildren[i];
307             if (cc._renderType == cc._RENDER_TYPE_WEBGL) {
308                 locChild.updateDisplayedOpacity(this._displayedOpacity);
309             } else {
310                 cc.Node.prototype.updateDisplayedOpacity.call(locChild, this._displayedOpacity);
311                 locChild.setNodeDirty();
312             }
313         }
314         this._changeTextureColor();
315     },
316 
317     /**
318      * Checking cascade opacity enabled
319      * @returns {boolean}
320      */
321     isCascadeOpacityEnabled: function () {
322         return false;
323     },
324 
325     /**
326      * Set cascade opacity enabled
327      * @param {Boolean} cascadeOpacityEnabled
328      */
329     setCascadeOpacityEnabled: function (cascadeOpacityEnabled) {
330         this._cascadeOpacityEnabled = cascadeOpacityEnabled;
331     },
332 
333     /**
334      * Gets the real color. <br />
335      * Create a new cc.Color clone in this real color.
336      * @returns {cc.Color}
337      */
338     getColor: function () {
339         var locRealColor = this._realColor;
340         return cc.color(locRealColor.r, locRealColor.g, locRealColor.b, locRealColor.a);
341     },
342 
343     /**
344      * Gets the display color. <br />
345      * Create a new cc.Color clone in this display color.
346      * @returns {cc.Color}
347      */
348     getDisplayedColor: function () {
349         var dc = this._displayedColor;
350         return cc.color(dc.r, dc.g, dc.b, dc.a);
351     },
352 
353     /**
354      * Update the display color. <br />
355      * Only update this label display color.
356      * @returns {cc.Color}
357      */
358     updateDisplayedColor: function (parentColor) {
359         var locDispColor = this._displayedColor;
360         var locRealColor = this._realColor;
361         locDispColor.r = locRealColor.r * parentColor.r / 255.0;
362         locDispColor.g = locRealColor.g * parentColor.g / 255.0;
363         locDispColor.b = locRealColor.b * parentColor.b / 255.0;
364 
365         var locChildren = this._children;
366         for (var i = 0; i < locChildren.length; i++) {
367             var locChild = locChildren[i];
368             if (cc._renderType == cc._RENDER_TYPE_WEBGL) {
369                 locChild.updateDisplayedColor(this._displayedColor);
370             } else {
371                 if(cc.sys._supportCanvasNewBlendModes)
372                     cc.Node.prototype.updateDisplayedColor.call(locChild, this._displayedColor);
373                 else
374                     locChild.updateDisplayedColor(this._displayedColor);
375                 locChild.setNodeDirty();
376             }
377         }
378         this._changeTextureColor();
379     },
380 
381     _changeTextureColor: function () {
382         if (cc._renderType == cc._RENDER_TYPE_WEBGL)
383             return;
384 
385         var locTexture = this.getTexture();
386         if (locTexture && locTexture.getContentSize().width>0) {
387             var element = this._originalTexture.getHtmlElementObj();
388             if(!element)
389                 return;
390             var locElement = locTexture.getHtmlElementObj();
391             var textureRect = cc.rect(0, 0, element.width, element.height);
392             if (locElement instanceof HTMLCanvasElement && !this._rectRotated){
393                 cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect, locElement);
394                 this.setTexture(locTexture);
395             } else {
396                 locElement = cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect);
397                 locTexture = new cc.Texture2D();
398                 locTexture.initWithElement(locElement);
399                 locTexture.handleLoadedTexture();
400                 this.setTexture(locTexture);
401             }
402         }
403     },
404 
405     /**
406      * Checking cascade color enabled
407      * @returns {boolean}
408      */
409     isCascadeColorEnabled: function () {
410         return false;
411     },
412 
413     /**
414      * Override synthesized setOpacity to recurse items
415      * @param {Boolean} cascadeColorEnabled
416      */
417     setCascadeColorEnabled: function (cascadeColorEnabled) {
418         this._cascadeColorEnabled = cascadeColorEnabled;
419     },
420 
421     /**
422      * Initialization of the node, please do not call this function by yourself, you should pass the parameters to constructor to initialize it
.
423      */
424     init: function () {
425         return this.initWithString(null, null, null, null, null);
426     },
427 
428     /**
429      * init a bitmap font atlas with an initial string and the FNT file
430      * @param {String} str
431      * @param {String} fntFile
432      * @param {Number} [width=-1]
433      * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT]
434      * @param {cc.Point} [imageOffset=cc.p(0,0)]
435      * @return {Boolean}
436      */
437     initWithString: function (str, fntFile, width, alignment, imageOffset) {
438         var self = this, theString = str || "";
439 
440         if (self._config)
441             cc.log("cc.LabelBMFont.initWithString(): re-init is no longer supported");
442 
443 
444         var texture;
445         if (fntFile) {
446             var newConf = cc.loader.getRes(fntFile);
447             if (!newConf) {
448                 cc.log("cc.LabelBMFont.initWithString(): Impossible to create font. Please check file");
449                 return false;
450             }
451 
452             self._config = newConf;
453             self._fntFile = fntFile;
454             texture = cc.textureCache.addImage(newConf.atlasName);
455             var locIsLoaded = texture.isLoaded();
456             self._textureLoaded = locIsLoaded;
457             if (!locIsLoaded) {
458                 texture.addLoadedEventListener(function (sender) {
459                     var self1 = this;
460                     self1._textureLoaded = true;
461                     //reset the LabelBMFont
462                     self1.initWithTexture(sender, self1._initialString.length);
463                     self1.setString(self1._initialString, true);
464                     self1._callLoadedEventCallbacks();
465                 }, self);
466             }
467         } else {
468             texture = new cc.Texture2D();
469             var image = new Image();
470             texture.initWithElement(image);
471             self._textureLoaded = false;
472         }
473 
474         if (self.initWithTexture(texture, theString.length)) {
475             self._alignment = alignment || cc.TEXT_ALIGNMENT_LEFT;
476             self._imageOffset = imageOffset || cc.p(0, 0);
477             self._width = (width == null) ? -1 : width;
478 
479             self._displayedOpacity = self._realOpacity = 255;
480             self._displayedColor = cc.color(255, 255, 255, 255);
481             self._realColor = cc.color(255, 255, 255, 255);
482             self._cascadeOpacityEnabled = true;
483             self._cascadeColorEnabled = true;
484 
485             self._contentSize.width = 0;
486             self._contentSize.height = 0;
487 
488             self.setAnchorPoint(0.5, 0.5);
489 
490             if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
491                 var locTexture = self.textureAtlas.texture;
492                 self._opacityModifyRGB = locTexture.hasPremultipliedAlpha();
493 
494                 var reusedChar = self._reusedChar = new cc.Sprite();
495                 reusedChar.initWithTexture(locTexture, cc.rect(0, 0, 0, 0), false);
496                 reusedChar.batchNode = self;
497             }
498             self.setString(theString, true);
499             return true;
500         }
501         return false;
502     },
503 
504     /**
505      * updates the font chars based on the string to render
506      */
507     createFontChars: function () {
508         var self = this;
509         var locContextType = cc._renderType;
510         var locTexture = (locContextType === cc._RENDER_TYPE_CANVAS) ? self.texture : self.textureAtlas.texture;
511 
512         var nextFontPositionX = 0;
513 
514         var tmpSize = cc.size(0, 0);
515 
516         var longestLine = 0;
517 
518         var quantityOfLines = 1;
519 
520         var locStr = self._string;
521         var stringLen = locStr ? locStr.length : 0;
522 
523         if (stringLen === 0)
524             return;
525 
526         var i, locCfg = self._config, locKerningDict = locCfg.kerningDict,
527             locCommonH = locCfg.commonHeight, locFontDict = locCfg.fontDefDictionary;
528         for (i = 0; i < stringLen - 1; i++) {
529             if (locStr.charCodeAt(i) == 10) quantityOfLines++;
530         }
531 
532         var totalHeight = locCommonH * quantityOfLines;
533         var nextFontPositionY = -(locCommonH - locCommonH * quantityOfLines);
534 
535         var prev = -1;
536         for (i = 0; i < stringLen; i++) {
537             var key = locStr.charCodeAt(i);
538             if (key == 0) continue;
539 
540             if (key === 10) {
541                 //new line
542                 nextFontPositionX = 0;
543                 nextFontPositionY -= locCfg.commonHeight;
544                 continue;
545             }
546 
547             var kerningAmount = locKerningDict[(prev << 16) | (key & 0xffff)] || 0;
548             var fontDef = locFontDict[key];
549             if (!fontDef) {
550                 cc.log("cocos2d: LabelBMFont: character not found " + locStr[i]);
551                 continue;
552             }
553 
554             var rect = cc.rect(fontDef.rect.x, fontDef.rect.y, fontDef.rect.width, fontDef.rect.height);
555             rect = cc.rectPixelsToPoints(rect);
556             rect.x += self._imageOffset.x;
557             rect.y += self._imageOffset.y;
558 
559             var fontChar = self.getChildByTag(i);
560             //var hasSprite = true;
561             if (!fontChar) {
562                 fontChar = new cc.Sprite();
563                 if ((key === 32) && (locContextType === cc._RENDER_TYPE_CANVAS)) rect = cc.rect(0, 0, 0, 0);
564                 fontChar.initWithTexture(locTexture, rect, false);
565                 fontChar._newTextureWhenChangeColor = true;
566                 self.addChild(fontChar, 0, i);
567             } else {
568                 if ((key === 32) && (locContextType === cc._RENDER_TYPE_CANVAS)) {
569                     fontChar.setTextureRect(rect, false, cc.size(0, 0));
570                 } else {
571                     // updating previous sprite
572                     fontChar.setTextureRect(rect, false);
573                     // restore to default in case they were modified
574                     fontChar.visible = true;
575                 }
576             }
577             // Apply label properties
578             fontChar.opacityModifyRGB = self._opacityModifyRGB;
579             // Color MUST be set before opacity, since opacity might change color if OpacityModifyRGB is on
580             if (cc._renderType == cc._RENDER_TYPE_WEBGL) {
581                 fontChar.updateDisplayedColor(self._displayedColor);
582                 fontChar.updateDisplayedOpacity(self._displayedOpacity);
583             } else {
584                 cc.Node.prototype.updateDisplayedColor.call(fontChar, self._displayedColor);
585                 cc.Node.prototype.updateDisplayedOpacity.call(fontChar, self._displayedOpacity);
586                 fontChar.setNodeDirty();
587             }
588 
589             var yOffset = locCfg.commonHeight - fontDef.yOffset;
590             var fontPos = cc.p(nextFontPositionX + fontDef.xOffset + fontDef.rect.width * 0.5 + kerningAmount,
591                 nextFontPositionY + yOffset - rect.height * 0.5 * cc.contentScaleFactor());
592             fontChar.setPosition(cc.pointPixelsToPoints(fontPos));
593 
594             // update kerning
595             nextFontPositionX += fontDef.xAdvance + kerningAmount;
596             prev = key;
597 
598             if (longestLine < nextFontPositionX)
599                 longestLine = nextFontPositionX;
600         }
601 
602         //If the last character processed has an xAdvance which is less that the width of the characters image, then we need
603         // to adjust the width of the string to take this into account, or the character will overlap the end of the bounding box
604         //TODO sync to -x
605         if(fontDef && fontDef.xAdvance < fontDef.rect.width)
606             tmpSize.width = longestLine - fontDef.xAdvance + fontDef.rect.width;
607         else
608             tmpSize.width = longestLine;
609         tmpSize.height = totalHeight;
610         self.setContentSize(cc.sizePixelsToPoints(tmpSize));
611     },
612 
613     /**
614      * Update String. <br />
615      * Only update this label display string.
616      * @param {Boolean} fromUpdate
617      */
618     updateString: function (fromUpdate) {
619         var self = this;
620         var locChildren = self._children;
621         if (locChildren) {
622             for (var i = 0, li = locChildren.length; i < li; i++) {
623                 var node = locChildren[i];
624                 if (node) node.visible = false;
625             }
626         }
627         if (self._config)
628             self.createFontChars();
629 
630         if (!fromUpdate)
631             self.updateLabel();
632     },
633 
634     /**
635      * Gets the text of this label
636      * @return {String}
637      */
638     getString: function () {
639         return this._initialString;
640     },
641 
642     /**
643      * Set the text
644      * @param {String} newString
645      * @param {Boolean|null} needUpdateLabel
646      */
647     setString: function (newString, needUpdateLabel) {
648         newString = String(newString);
649         if (needUpdateLabel == null)
650             needUpdateLabel = true;
651         if (newString == null || !cc.isString(newString))
652             newString = newString + "";
653 
654         this._initialString = newString;
655         this._setString(newString, needUpdateLabel);
656     },
657 
658     _setStringForSetter: function (newString) {
659         this.setString(newString, false);
660     },
661 
662     /**
663      * Set the text. <br />
664      * Change this Label display string.
665      * @deprecated since v3.0 please use .setString
666      * @param label
667      */
668     setCString: function (label) {
669         this.setString(label, true);
670     },
671 
672     /**
673      * Update Label. <br />
674      * Update this Label display string and more...
675      */
676     updateLabel: function () {
677         var self = this;
678         self.string = self._initialString;
679 
680         // Step 1: Make multiline
681         if (self._width > 0) {
682             var stringLength = self._string.length;
683             var multiline_string = [];
684             var last_word = [];
685 
686             var line = 1, i = 0, start_line = false, start_word = false, startOfLine = -1, startOfWord = -1, skip = 0;
687 
688             var characterSprite;
689             for (var j = 0, lj = self._children.length; j < lj; j++) {
690                 var justSkipped = 0;
691                 while (!(characterSprite = self.getChildByTag(j + skip + justSkipped)))
692                     justSkipped++;
693                 skip += justSkipped;
694 
695                 if (i >= stringLength)
696                     break;
697 
698                 var character = self._string[i];
699                 if (!start_word) {
700                     startOfWord = self._getLetterPosXLeft(characterSprite);
701                     start_word = true;
702                 }
703                 if (!start_line) {
704                     startOfLine = startOfWord;
705                     start_line = true;
706                 }
707 
708                 // Newline.
709                 if (character.charCodeAt(0) == 10) {
710                     last_word.push('\n');
711                     multiline_string = multiline_string.concat(last_word);
712                     last_word.length = 0;
713                     start_word = false;
714                     start_line = false;
715                     startOfWord = -1;
716                     startOfLine = -1;
717                     //i+= justSkipped;
718                     j--;
719                     skip -= justSkipped;
720                     line++;
721 
722                     if (i >= stringLength)
723                         break;
724 
725                     character = self._string[i];
726                     if (!startOfWord) {
727                         startOfWord = self._getLetterPosXLeft(characterSprite);
728                         start_word = true;
729                     }
730                     if (!startOfLine) {
731                         startOfLine = startOfWord;
732                         start_line = true;
733                     }
734                     i++;
735                     continue;
736                 }
737 
738                 // Whitespace.
739                 if (this._isspace_unicode(character)) {
740                     last_word.push(character);
741                     multiline_string = multiline_string.concat(last_word);
742                     last_word.length = 0;
743                     start_word = false;
744                     startOfWord = -1;
745                     i++;
746                     continue;
747                 }
748 
749                 // Out of bounds.
750                 if (self._getLetterPosXRight(characterSprite) - startOfLine > self._width) {
751                     if (!self._lineBreakWithoutSpaces) {
752                         last_word.push(character);
753 
754                         var found = multiline_string.lastIndexOf(" ");
755                         if (found != -1)
756                             this._utf8_trim_ws(multiline_string);
757                         else
758                             multiline_string = [];
759 
760                         if (multiline_string.length > 0)
761                             multiline_string.push('\n');
762 
763                         line++;
764                         start_line = false;
765                         startOfLine = -1;
766                         i++;
767                     } else {
768                         this._utf8_trim_ws(last_word);
769 
770                         last_word.push('\n');
771                         multiline_string = multiline_string.concat(last_word);
772                         last_word.length = 0;
773                         start_word = false;
774                         start_line = false;
775                         startOfWord = -1;
776                         startOfLine = -1;
777                         line++;
778 
779                         if (i >= stringLength)
780                             break;
781 
782                         if (!startOfWord) {
783                             startOfWord = self._getLetterPosXLeft(characterSprite);
784                             start_word = true;
785                         }
786                         if (!startOfLine) {
787                             startOfLine = startOfWord;
788                             start_line = true;
789                         }
790                         j--;
791                     }
792                 } else {
793                     // Character is normal.
794                     last_word.push(character);
795                     i++;
796                 }
797             }
798 
799             multiline_string = multiline_string.concat(last_word);
800             var len = multiline_string.length;
801             var str_new = "";
802 
803             for (i = 0; i < len; ++i)
804                 str_new += multiline_string[i];
805 
806             str_new = str_new + String.fromCharCode(0);
807             //this.updateString(true);
808             self._setString(str_new, false)
809         }
810 
811         // Step 2: Make alignment
812         if (self._alignment != cc.TEXT_ALIGNMENT_LEFT) {
813             i = 0;
814 
815             var lineNumber = 0;
816             var strlen = self._string.length;
817             var last_line = [];
818 
819             for (var ctr = 0; ctr < strlen; ctr++) {
820                 if (self._string[ctr].charCodeAt(0) == 10 || self._string[ctr].charCodeAt(0) == 0) {
821                     var lineWidth = 0;
822                     var line_length = last_line.length;
823                     // if last line is empty we must just increase lineNumber and work with next line
824                     if (line_length == 0) {
825                         lineNumber++;
826                         continue;
827                     }
828                     var index = i + line_length - 1 + lineNumber;
829                     if (index < 0) continue;
830 
831                     var lastChar = self.getChildByTag(index);
832                     if (lastChar == null)
833                         continue;
834                     lineWidth = lastChar.getPositionX() + lastChar._getWidth() / 2;
835 
836                     var shift = 0;
837                     switch (self._alignment) {
838                         case cc.TEXT_ALIGNMENT_CENTER:
839                             shift = self.width / 2 - lineWidth / 2;
840                             break;
841                         case cc.TEXT_ALIGNMENT_RIGHT:
842                             shift = self.width - lineWidth;
843                             break;
844                         default:
845                             break;
846                     }
847 
848                     if (shift != 0) {
849                         for (j = 0; j < line_length; j++) {
850                             index = i + j + lineNumber;
851                             if (index < 0) continue;
852                             characterSprite = self.getChildByTag(index);
853                             if (characterSprite)
854                                 characterSprite.x += shift;
855                         }
856                     }
857 
858                     i += line_length;
859                     lineNumber++;
860 
861                     last_line.length = 0;
862                     continue;
863                 }
864                 last_line.push(self._string[i]);
865             }
866         }
867     },
868 
869     /**
870      * Set text alignment.
871      * @param {Number} alignment
872      */
873     setAlignment: function (alignment) {
874         this._alignment = alignment;
875         this.updateLabel();
876     },
877 
878     _getAlignment: function () {
879         return this._alignment;
880     },
881 
882     /**
883      * Set the bounding width. <br />
884      * max with display width. The exceeding string will be wrapping.
885      * @param {Number} width
886      */
887     setBoundingWidth: function (width) {
888         this._width = width;
889         this.updateLabel();
890     },
891 
892     _getBoundingWidth: function () {
893         return this._width;
894     },
895 
896     /**
897      * Set the param to change English word warp according to whether the space. <br />
898      * default is false.
899      * @param {Boolean}  breakWithoutSpace
900      */
901     setLineBreakWithoutSpace: function (breakWithoutSpace) {
902         this._lineBreakWithoutSpaces = breakWithoutSpace;
903         this.updateLabel();
904     },
905 
906     /**
907      * Set scale. <br />
908      * Input a number, will be decrease or increase the font size. <br />
909      * @param {Number} scale
910      * @param {Number} [scaleY=null] default is scale
911      */
912     setScale: function (scale, scaleY) {
913         cc.Node.prototype.setScale.call(this, scale, scaleY);
914         this.updateLabel();
915     },
916 
917     /**
918      * Set scale of x. <br />
919      * Input a number, will be decrease or increase the font size. <br />
920      * Horizontal scale.
921      * @param {Number} scaleX
922      */
923     setScaleX: function (scaleX) {
924         cc.Node.prototype.setScaleX.call(this, scaleX);
925         this.updateLabel();
926     },
927 
928     /**
929      * Set scale of x. <br />
930      * Input a number, will be decrease or increase the font size. <br />
931      * Longitudinal scale.
932      * @param {Number} scaleY
933      */
934     setScaleY: function (scaleY) {
935         cc.Node.prototype.setScaleY.call(this, scaleY);
936         this.updateLabel();
937     },
938 
939     //TODO
940     /**
941      * set fnt file path. <br />
942      * Change the fnt file path.
943      * @param {String} fntFile
944      */
945     setFntFile: function (fntFile) {
946         var self = this;
947         if (fntFile != null && fntFile != self._fntFile) {
948             var newConf = cc.loader.getRes(fntFile);
949 
950             if (!newConf) {
951                 cc.log("cc.LabelBMFont.setFntFile() : Impossible to create font. Please check file");
952                 return;
953             }
954 
955             self._fntFile = fntFile;
956             self._config = newConf;
957 
958             var texture = cc.textureCache.addImage(newConf.atlasName);
959             var locIsLoaded = texture.isLoaded();
960             self._textureLoaded = locIsLoaded;
961             self.texture = texture;
962             if (cc._renderType === cc._RENDER_TYPE_CANVAS)
963                 self._originalTexture = self.texture;
964             if (!locIsLoaded) {
965                 texture.addLoadedEventListener(function (sender) {
966                     var self1 = this;
967                     self1._textureLoaded = true;
968                     self1.texture = sender;
969                     self1.createFontChars();
970                     self1._changeTextureColor();
971                     self1.updateLabel();
972                     self1._callLoadedEventCallbacks();
973                 }, self);
974             } else {
975                 self.createFontChars();
976             }
977         }
978     },
979 
980     /**
981      * Return the fnt file path.
982      * @return {String}
983      */
984     getFntFile: function () {
985         return this._fntFile;
986     },
987 
988     /**
989      * Set the AnchorPoint of the labelBMFont. <br />
990      * In order to change the location of label.
991      * @override
992      * @param {cc.Point|Number} point The anchor point of labelBMFont or The anchor point.x of labelBMFont.
993      * @param {Number} [y] The anchor point.y of labelBMFont.
994      */
995     setAnchorPoint: function (point, y) {
996         cc.Node.prototype.setAnchorPoint.call(this, point, y);
997         this.updateLabel();
998     },
999 
1000     _setAnchor: function (p) {
1001         cc.Node.prototype._setAnchor.call(this, p);
1002         this.updateLabel();
1003     },
1004 
1005     _setAnchorX: function (x) {
1006         cc.Node.prototype._setAnchorX.call(this, x);
1007         this.updateLabel();
1008     },
1009 
1010     _setAnchorY: function (y) {
1011         cc.Node.prototype._setAnchorY.call(this, y);
1012         this.updateLabel();
1013     },
1014 
1015     _atlasNameFromFntFile: function (fntFile) {},
1016 
1017     _kerningAmountForFirst: function (first, second) {
1018         var ret = 0;
1019         var key = (first << 16) | (second & 0xffff);
1020         if (this._configuration.kerningDictionary) {
1021             var element = this._configuration.kerningDictionary[key.toString()];
1022             if (element)
1023                 ret = element.amount;
1024         }
1025         return ret;
1026     },
1027 
1028     _getLetterPosXLeft: function (sp) {
1029         return sp.getPositionX() * this._scaleX - (sp._getWidth() * this._scaleX * sp._getAnchorX());
1030     },
1031 
1032     _getLetterPosXRight: function (sp) {
1033         return sp.getPositionX() * this._scaleX + (sp._getWidth() * this._scaleX * sp._getAnchorX());
1034     },
1035 
1036     //Checking whether the character is a whitespace
1037     _isspace_unicode: function(ch){
1038         ch = ch.charCodeAt(0);
1039         return  ((ch >= 9 && ch <= 13) || ch == 32 || ch == 133 || ch == 160 || ch == 5760
1040             || (ch >= 8192 && ch <= 8202) || ch == 8232 || ch == 8233 || ch == 8239
1041             || ch == 8287 || ch == 12288)
1042     },
1043 
1044     _utf8_trim_ws: function(str){
1045         var len = str.length;
1046 
1047         if (len <= 0)
1048             return;
1049 
1050         var last_index = len - 1;
1051 
1052         // Only start trimming if the last character is whitespace..
1053         if (this._isspace_unicode(str[last_index])) {
1054             for (var i = last_index - 1; i >= 0; --i) {
1055                 if (this._isspace_unicode(str[i])) {
1056                     last_index = i;
1057                 }
1058                 else {
1059                     break;
1060                 }
1061             }
1062             this._utf8_trim_from(str, last_index);
1063         }
1064     },
1065 
1066     //Trims str st str=[0, index) after the operation.
1067     //Return value: the trimmed string.
1068     _utf8_trim_from: function(str, index){
1069         var len = str.length;
1070         if (index >= len || index < 0)
1071             return;
1072         str.splice(index, len);
1073     }
1074 });
1075 
1076 var _p = cc.LabelBMFont.prototype;
1077 
1078 if(cc._renderType === cc._RENDER_TYPE_CANVAS && !cc.sys._supportCanvasNewBlendModes)
1079     _p._changeTextureColor = function(){
1080         if(cc._renderType == cc._RENDER_TYPE_WEBGL)
1081             return;
1082         var locElement, locTexture = this.getTexture();
1083         if (locTexture && locTexture.getContentSize().width>0) {
1084             locElement = locTexture.getHtmlElementObj();
1085             if (!locElement)
1086                 return;
1087             var cacheTextureForColor = cc.textureCache.getTextureColors(this._originalTexture.getHtmlElementObj());
1088             if (cacheTextureForColor) {
1089                 if (locElement instanceof HTMLCanvasElement && !this._rectRotated)
1090                     cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor, null, locElement);
1091                 else{
1092                     locElement = cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor);
1093                     locTexture = new cc.Texture2D();
1094                     locTexture.initWithElement(locElement);
1095                     locTexture.handleLoadedTexture();
1096                     this.setTexture(locTexture);
1097                 }
1098             }
1099         }
1100     };
1101 
1102 /** @expose */
1103 _p.string;
1104 cc.defineGetterSetter(_p, "string", _p.getString, _p._setStringForSetter);
1105 /** @expose */
1106 _p.boundingWidth;
1107 cc.defineGetterSetter(_p, "boundingWidth", _p._getBoundingWidth, _p.setBoundingWidth);
1108 /** @expose */
1109 _p.textAlign;
1110 cc.defineGetterSetter(_p, "textAlign", _p._getAlignment, _p.setAlignment);
1111 
1112 /**
1113  * creates a bitmap font atlas with an initial string and the FNT file
1114  * @deprecated since v3.0 please use new cc.LabelBMFont
1115  * @param {String} str
1116  * @param {String} fntFile
1117  * @param {Number} [width=-1]
1118  * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT]
1119  * @param {cc.Point} [imageOffset=cc.p(0,0)]
1120  * @return {cc.LabelBMFont|Null}
1121  * @example
1122  * // Example 01
1123  * var label1 = cc.LabelBMFont.create("Test case", "test.fnt");
1124  *
1125  * // Example 02
1126  * var label2 = cc.LabelBMFont.create("test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT);
1127  *
1128  * // Example 03
1129  * var label3 = cc.LabelBMFont.create("This is a \n test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT, cc.p(0,0));
1130  */
1131 cc.LabelBMFont.create = function (str, fntFile, width, alignment, imageOffset) {
1132     return new cc.LabelBMFont(str, fntFile, width, alignment, imageOffset);
1133 };
1134 
1135 cc._fntLoader = {
1136     INFO_EXP: /info [^\n]*(\n|$)/gi,
1137     COMMON_EXP: /common [^\n]*(\n|$)/gi,
1138     PAGE_EXP: /page [^\n]*(\n|$)/gi,
1139     CHAR_EXP: /char [^\n]*(\n|$)/gi,
1140     KERNING_EXP: /kerning [^\n]*(\n|$)/gi,
1141     ITEM_EXP: /\w+=[^ \r\n]+/gi,
1142     INT_EXP: /^[\-]?\d+$/,
1143 
1144     _parseStrToObj: function (str) {
1145         var arr = str.match(this.ITEM_EXP);
1146         var obj = {};
1147         if (arr) {
1148             for (var i = 0, li = arr.length; i < li; i++) {
1149                 var tempStr = arr[i];
1150                 var index = tempStr.indexOf("=");
1151                 var key = tempStr.substring(0, index);
1152                 var value = tempStr.substring(index + 1);
1153                 if (value.match(this.INT_EXP)) value = parseInt(value);
1154                 else if (value[0] == '"') value = value.substring(1, value.length - 1);
1155                 obj[key] = value;
1156             }
1157         }
1158         return obj;
1159     },
1160 
1161     /**
1162      * Parse Fnt string.
1163      * @param fntStr
1164      * @param url
1165      * @returns {{}}
1166      */
1167     parseFnt: function (fntStr, url) {
1168         var self = this, fnt = {};
1169         //padding
1170         var infoObj = self._parseStrToObj(fntStr.match(self.INFO_EXP)[0]);
1171         var paddingArr = infoObj["padding"].split(",");
1172         var padding = {
1173             left: parseInt(paddingArr[0]),
1174             top: parseInt(paddingArr[1]),
1175             right: parseInt(paddingArr[2]),
1176             bottom: parseInt(paddingArr[3])
1177         };
1178 
1179         //common
1180         var commonObj = self._parseStrToObj(fntStr.match(self.COMMON_EXP)[0]);
1181         fnt.commonHeight = commonObj["lineHeight"];
1182         if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
1183             var texSize = cc.configuration.getMaxTextureSize();
1184             if (commonObj["scaleW"] > texSize.width || commonObj["scaleH"] > texSize.height)
1185                 cc.log("cc.LabelBMFont._parseCommonArguments(): page can't be larger than supported");
1186         }
1187         if (commonObj["pages"] !== 1) cc.log("cc.LabelBMFont._parseCommonArguments(): only supports 1 page");
1188 
1189         //page
1190         var pageObj = self._parseStrToObj(fntStr.match(self.PAGE_EXP)[0]);
1191         if (pageObj["id"] !== 0) cc.log("cc.LabelBMFont._parseImageFileName() : file could not be found");
1192         fnt.atlasName = cc.path.changeBasename(url, pageObj["file"]);
1193 
1194         //char
1195         var charLines = fntStr.match(self.CHAR_EXP);
1196         var fontDefDictionary = fnt.fontDefDictionary = {};
1197         for (var i = 0, li = charLines.length; i < li; i++) {
1198             var charObj = self._parseStrToObj(charLines[i]);
1199             var charId = charObj["id"];
1200             fontDefDictionary[charId] = {
1201                 rect: {x: charObj["x"], y: charObj["y"], width: charObj["width"], height: charObj["height"]},
1202                 xOffset: charObj["xoffset"],
1203                 yOffset: charObj["yoffset"],
1204                 xAdvance: charObj["xadvance"]
1205             };
1206         }
1207 
1208         //kerning
1209         var kerningDict = fnt.kerningDict = {};
1210         var kerningLines = fntStr.match(self.KERNING_EXP);
1211         if (kerningLines) {
1212             for (var i = 0, li = kerningLines.length; i < li; i++) {
1213                 var kerningObj = self._parseStrToObj(kerningLines[i]);
1214                 kerningDict[(kerningObj["first"] << 16) | (kerningObj["second"] & 0xffff)] = kerningObj["amount"];
1215             }
1216         }
1217         return fnt;
1218     },
1219 
1220     /**
1221      * load the fnt
1222      * @param realUrl
1223      * @param url
1224      * @param res
1225      * @param cb
1226      */
1227     load: function (realUrl, url, res, cb) {
1228         var self = this;
1229         cc.loader.loadTxt(realUrl, function (err, txt) {
1230             if (err) return cb(err);
1231             cb(null, self.parseFnt(txt, url));
1232         });
1233     }
1234 };
1235 cc.loader.register(["fnt"], cc._fntLoader);
1236