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                 cc.Node.prototype.updateDisplayedColor.call(locChild, this._displayedColor);
372                 locChild.setNodeDirty();
373             }
374         }
375         this._changeTextureColor();
376     },
377 
378     _changeTextureColor: function () {
379         if (cc._renderType == cc._RENDER_TYPE_WEBGL)
380             return;
381 
382         var locTexture = this.getTexture();
383         if (locTexture && locTexture.getContentSize().width>0) {
384             var element = this._originalTexture.getHtmlElementObj();
385             if(!element)
386                 return;
387             var locElement = locTexture.getHtmlElementObj();
388             var textureRect = cc.rect(0, 0, element.width, element.height);
389             if (locElement instanceof HTMLCanvasElement && !this._rectRotated){
390                 cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect, locElement);
391                 this.setTexture(locTexture);
392             } else {
393                 locElement = cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect);
394                 locTexture = new cc.Texture2D();
395                 locTexture.initWithElement(locElement);
396                 locTexture.handleLoadedTexture();
397                 this.setTexture(locTexture);
398             }
399         }
400     },
401 
402     /**
403      * Checking cascade color enabled
404      * @returns {boolean}
405      */
406     isCascadeColorEnabled: function () {
407         return false;
408     },
409 
410     /**
411      * Override synthesized setOpacity to recurse items
412      * @param {Boolean} cascadeColorEnabled
413      */
414     setCascadeColorEnabled: function (cascadeColorEnabled) {
415         this._cascadeColorEnabled = cascadeColorEnabled;
416     },
417 
418     /**
419      * Initialization of the node, please do not call this function by yourself, you should pass the parameters to constructor to initialize it
.
420      */
421     init: function () {
422         return this.initWithString(null, null, null, null, null);
423     },
424 
425     /**
426      * init a bitmap font atlas with an initial string and the FNT file
427      * @param {String} str
428      * @param {String} fntFile
429      * @param {Number} [width=-1]
430      * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT]
431      * @param {cc.Point} [imageOffset=cc.p(0,0)]
432      * @return {Boolean}
433      */
434     initWithString: function (str, fntFile, width, alignment, imageOffset) {
435         var self = this, theString = str || "";
436 
437         if (self._config)
438             cc.log("cc.LabelBMFont.initWithString(): re-init is no longer supported");
439 
440 
441         var texture;
442         if (fntFile) {
443             var newConf = cc.loader.getRes(fntFile);
444             if (!newConf) {
445                 cc.log("cc.LabelBMFont.initWithString(): Impossible to create font. Please check file");
446                 return false;
447             }
448 
449             self._config = newConf;
450             self._fntFile = fntFile;
451             texture = cc.textureCache.addImage(newConf.atlasName);
452             var locIsLoaded = texture.isLoaded();
453             self._textureLoaded = locIsLoaded;
454             if (!locIsLoaded) {
455                 texture.addLoadedEventListener(function (sender) {
456                     var self1 = this;
457                     self1._textureLoaded = true;
458                     //reset the LabelBMFont
459                     self1.initWithTexture(sender, self1._initialString.length);
460                     self1.setString(self1._initialString, true);
461                     self1._callLoadedEventCallbacks();
462                 }, self);
463             }
464         } else {
465             texture = new cc.Texture2D();
466             var image = new Image();
467             texture.initWithElement(image);
468             self._textureLoaded = false;
469         }
470 
471         if (self.initWithTexture(texture, theString.length)) {
472             self._alignment = alignment || cc.TEXT_ALIGNMENT_LEFT;
473             self._imageOffset = imageOffset || cc.p(0, 0);
474             self._width = (width == null) ? -1 : width;
475 
476             self._displayedOpacity = self._realOpacity = 255;
477             self._displayedColor = cc.color(255, 255, 255, 255);
478             self._realColor = cc.color(255, 255, 255, 255);
479             self._cascadeOpacityEnabled = true;
480             self._cascadeColorEnabled = true;
481 
482             self._contentSize.width = 0;
483             self._contentSize.height = 0;
484 
485             self.setAnchorPoint(0.5, 0.5);
486 
487             if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
488                 var locTexture = self.textureAtlas.texture;
489                 self._opacityModifyRGB = locTexture.hasPremultipliedAlpha();
490 
491                 var reusedChar = self._reusedChar = new cc.Sprite();
492                 reusedChar.initWithTexture(locTexture, cc.rect(0, 0, 0, 0), false);
493                 reusedChar.batchNode = self;
494             }
495             self.setString(theString, true);
496             return true;
497         }
498         return false;
499     },
500 
501     /**
502      * updates the font chars based on the string to render
503      */
504     createFontChars: function () {
505         var self = this;
506         var locContextType = cc._renderType;
507         var locTexture = (locContextType === cc._RENDER_TYPE_CANVAS) ? self.texture : self.textureAtlas.texture;
508 
509         var nextFontPositionX = 0;
510 
511         var tmpSize = cc.size(0, 0);
512 
513         var longestLine = 0;
514 
515         var quantityOfLines = 1;
516 
517         var locStr = self._string;
518         var stringLen = locStr ? locStr.length : 0;
519 
520         if (stringLen === 0)
521             return;
522 
523         var i, locCfg = self._config, locKerningDict = locCfg.kerningDict,
524             locCommonH = locCfg.commonHeight, locFontDict = locCfg.fontDefDictionary;
525         for (i = 0; i < stringLen - 1; i++) {
526             if (locStr.charCodeAt(i) == 10) quantityOfLines++;
527         }
528 
529         var totalHeight = locCommonH * quantityOfLines;
530         var nextFontPositionY = -(locCommonH - locCommonH * quantityOfLines);
531 
532         var prev = -1;
533         for (i = 0; i < stringLen; i++) {
534             var key = locStr.charCodeAt(i);
535             if (key == 0) continue;
536 
537             if (key === 10) {
538                 //new line
539                 nextFontPositionX = 0;
540                 nextFontPositionY -= locCfg.commonHeight;
541                 continue;
542             }
543 
544             var kerningAmount = locKerningDict[(prev << 16) | (key & 0xffff)] || 0;
545             var fontDef = locFontDict[key];
546             if (!fontDef) {
547                 cc.log("cocos2d: LabelBMFont: character not found " + locStr[i]);
548                 continue;
549             }
550 
551             var rect = cc.rect(fontDef.rect.x, fontDef.rect.y, fontDef.rect.width, fontDef.rect.height);
552             rect = cc.rectPixelsToPoints(rect);
553             rect.x += self._imageOffset.x;
554             rect.y += self._imageOffset.y;
555 
556             var fontChar = self.getChildByTag(i);
557             //var hasSprite = true;
558             if (!fontChar) {
559                 fontChar = new cc.Sprite();
560                 if ((key === 32) && (locContextType === cc._RENDER_TYPE_CANVAS)) rect = cc.rect(0, 0, 0, 0);
561                 fontChar.initWithTexture(locTexture, rect, false);
562                 fontChar._newTextureWhenChangeColor = true;
563                 self.addChild(fontChar, 0, i);
564             } else {
565                 if ((key === 32) && (locContextType === cc._RENDER_TYPE_CANVAS)) {
566                     fontChar.setTextureRect(rect, false, cc.size(0, 0));
567                 } else {
568                     // updating previous sprite
569                     fontChar.setTextureRect(rect, false);
570                     // restore to default in case they were modified
571                     fontChar.visible = true;
572                 }
573             }
574             // Apply label properties
575             fontChar.opacityModifyRGB = self._opacityModifyRGB;
576             // Color MUST be set before opacity, since opacity might change color if OpacityModifyRGB is on
577             if (cc._renderType == cc._RENDER_TYPE_WEBGL) {
578                 fontChar.updateDisplayedColor(self._displayedColor);
579                 fontChar.updateDisplayedOpacity(self._displayedOpacity);
580             } else {
581                 cc.Node.prototype.updateDisplayedColor.call(fontChar, self._displayedColor);
582                 cc.Node.prototype.updateDisplayedOpacity.call(fontChar, self._displayedOpacity);
583                 fontChar.setNodeDirty();
584             }
585 
586             var yOffset = locCfg.commonHeight - fontDef.yOffset;
587             var fontPos = cc.p(nextFontPositionX + fontDef.xOffset + fontDef.rect.width * 0.5 + kerningAmount,
588                 nextFontPositionY + yOffset - rect.height * 0.5 * cc.contentScaleFactor());
589             fontChar.setPosition(cc.pointPixelsToPoints(fontPos));
590 
591             // update kerning
592             nextFontPositionX += fontDef.xAdvance + kerningAmount;
593             prev = key;
594 
595             if (longestLine < nextFontPositionX)
596                 longestLine = nextFontPositionX;
597         }
598 
599         tmpSize.width = longestLine;
600         tmpSize.height = totalHeight;
601         self.setContentSize(cc.sizePixelsToPoints(tmpSize));
602     },
603 
604     /**
605      * Update String. <br />
606      * Only update this label displa string.
607      * @param {Boolean} fromUpdate
608      */
609     updateString: function (fromUpdate) {
610         var self = this;
611         var locChildren = self._children;
612         if (locChildren) {
613             for (var i = 0, li = locChildren.length; i < li; i++) {
614                 var node = locChildren[i];
615                 if (node) node.visible = false;
616             }
617         }
618         if (self._config)
619             self.createFontChars();
620 
621         if (!fromUpdate)
622             self.updateLabel();
623     },
624 
625     /**
626      * Gets the text of this label
627      * @return {String}
628      */
629     getString: function () {
630         return this._initialString;
631     },
632 
633     /**
634      * Set the text
635      * @param {String} newString
636      * @param {Boolean|null} needUpdateLabel
637      */
638     setString: function (newString, needUpdateLabel) {
639         newString = String(newString);
640         if (needUpdateLabel == null)
641             needUpdateLabel = true;
642         if (newString == null || typeof(newString) != "string")
643             newString = newString + "";
644 
645         this._initialString = newString;
646         this._setString(newString, needUpdateLabel);
647     },
648 
649     _setStringForSetter: function (newString) {
650         this.setString(newString, false);
651     },
652 
653     /**
654      * Set the text. <br />
655      * Change this Label display string.
656      * @deprecated since v3.0 please use .setString
657      * @param label
658      */
659     setCString: function (label) {
660         this.setString(label, true);
661     },
662 
663     /**
664      * Update Label. <br />
665      * Update this Label display string and more...
666      */
667     updateLabel: function () {
668         var self = this;
669         self.string = self._initialString;
670 
671         // Step 1: Make multiline
672         if (self._width > 0) {
673             var stringLength = self._string.length;
674             var multiline_string = [];
675             var last_word = [];
676 
677             var line = 1, i = 0, start_line = false, start_word = false, startOfLine = -1, startOfWord = -1, skip = 0;
678 
679             var characterSprite;
680             for (var j = 0, lj = self._children.length; j < lj; j++) {
681                 var justSkipped = 0;
682                 while (!(characterSprite = self.getChildByTag(j + skip + justSkipped)))
683                     justSkipped++;
684                 skip += justSkipped;
685 
686                 if (i >= stringLength)
687                     break;
688 
689                 var character = self._string[i];
690                 if (!start_word) {
691                     startOfWord = self._getLetterPosXLeft(characterSprite);
692                     start_word = true;
693                 }
694                 if (!start_line) {
695                     startOfLine = startOfWord;
696                     start_line = true;
697                 }
698 
699                 // Newline.
700                 if (character.charCodeAt(0) == 10) {
701                     last_word.push('\n');
702                     multiline_string = multiline_string.concat(last_word);
703                     last_word.length = 0;
704                     start_word = false;
705                     start_line = false;
706                     startOfWord = -1;
707                     startOfLine = -1;
708                     //i+= justSkipped;
709                     j--;
710                     skip -= justSkipped;
711                     line++;
712 
713                     if (i >= stringLength)
714                         break;
715 
716                     character = self._string[i];
717                     if (!startOfWord) {
718                         startOfWord = self._getLetterPosXLeft(characterSprite);
719                         start_word = true;
720                     }
721                     if (!startOfLine) {
722                         startOfLine = startOfWord;
723                         start_line = true;
724                     }
725                     i++;
726                     continue;
727                 }
728 
729                 // Whitespace.
730                 if (this._isspace_unicode(character)) {
731                     last_word.push(character);
732                     multiline_string = multiline_string.concat(last_word);
733                     last_word.length = 0;
734                     start_word = false;
735                     startOfWord = -1;
736                     i++;
737                     continue;
738                 }
739 
740                 // Out of bounds.
741                 if (self._getLetterPosXRight(characterSprite) - startOfLine > self._width) {
742                     if (!self._lineBreakWithoutSpaces) {
743                         last_word.push(character);
744 
745                         var found = multiline_string.lastIndexOf(" ");
746                         if (found != -1)
747                             this._utf8_trim_ws(multiline_string);
748                         else
749                             multiline_string = [];
750 
751                         if (multiline_string.length > 0)
752                             multiline_string.push('\n');
753 
754                         line++;
755                         start_line = false;
756                         startOfLine = -1;
757                         i++;
758                     } else {
759                         this._utf8_trim_ws(last_word);
760 
761                         last_word.push('\n');
762                         multiline_string = multiline_string.concat(last_word);
763                         last_word.length = 0;
764                         start_word = false;
765                         start_line = false;
766                         startOfWord = -1;
767                         startOfLine = -1;
768                         line++;
769 
770                         if (i >= stringLength)
771                             break;
772 
773                         if (!startOfWord) {
774                             startOfWord = self._getLetterPosXLeft(characterSprite);
775                             start_word = true;
776                         }
777                         if (!startOfLine) {
778                             startOfLine = startOfWord;
779                             start_line = true;
780                         }
781                         j--;
782                     }
783                 } else {
784                     // Character is normal.
785                     last_word.push(character);
786                     i++;
787                 }
788             }
789 
790             multiline_string = multiline_string.concat(last_word);
791             var len = multiline_string.length;
792             var str_new = "";
793 
794             for (i = 0; i < len; ++i)
795                 str_new += multiline_string[i];
796 
797             str_new = str_new + String.fromCharCode(0);
798             //this.updateString(true);
799             self._setString(str_new, false)
800         }
801 
802         // Step 2: Make alignment
803         if (self._alignment != cc.TEXT_ALIGNMENT_LEFT) {
804             i = 0;
805 
806             var lineNumber = 0;
807             var strlen = self._string.length;
808             var last_line = [];
809 
810             for (var ctr = 0; ctr < strlen; ctr++) {
811                 if (self._string[ctr].charCodeAt(0) == 10 || self._string[ctr].charCodeAt(0) == 0) {
812                     var lineWidth = 0;
813                     var line_length = last_line.length;
814                     // if last line is empty we must just increase lineNumber and work with next line
815                     if (line_length == 0) {
816                         lineNumber++;
817                         continue;
818                     }
819                     var index = i + line_length - 1 + lineNumber;
820                     if (index < 0) continue;
821 
822                     var lastChar = self.getChildByTag(index);
823                     if (lastChar == null)
824                         continue;
825                     lineWidth = lastChar.getPositionX() + lastChar._getWidth() / 2;
826 
827                     var shift = 0;
828                     switch (self._alignment) {
829                         case cc.TEXT_ALIGNMENT_CENTER:
830                             shift = self.width / 2 - lineWidth / 2;
831                             break;
832                         case cc.TEXT_ALIGNMENT_RIGHT:
833                             shift = self.width - lineWidth;
834                             break;
835                         default:
836                             break;
837                     }
838 
839                     if (shift != 0) {
840                         for (j = 0; j < line_length; j++) {
841                             index = i + j + lineNumber;
842                             if (index < 0) continue;
843                             characterSprite = self.getChildByTag(index);
844                             if (characterSprite)
845                                 characterSprite.x += shift;
846                         }
847                     }
848 
849                     i += line_length;
850                     lineNumber++;
851 
852                     last_line.length = 0;
853                     continue;
854                 }
855                 last_line.push(self._string[i]);
856             }
857         }
858     },
859 
860     /**
861      * Set text alignment.
862      * @param {Number} alignment
863      */
864     setAlignment: function (alignment) {
865         this._alignment = alignment;
866         this.updateLabel();
867     },
868 
869     _getAlignment: function () {
870         return this._alignment;
871     },
872 
873     /**
874      * Set the bounding width. <br />
875      * max with display width. The exceeding string will be wrapping.
876      * @param {Number} width
877      */
878     setBoundingWidth: function (width) {
879         this._width = width;
880         this.updateLabel();
881     },
882 
883     _getBoundingWidth: function () {
884         return this._width;
885     },
886 
887     /**
888      * Set the param to change English word warp according to whether the space. <br />
889      * default is false.
890      * @param {Boolean}  breakWithoutSpace
891      */
892     setLineBreakWithoutSpace: function (breakWithoutSpace) {
893         this._lineBreakWithoutSpaces = breakWithoutSpace;
894         this.updateLabel();
895     },
896 
897     /**
898      * Set scale. <br />
899      * Input a number, will be decrease or increase the font size. <br />
900      * @param {Number} scale
901      * @param {Number} [scaleY=null] default is scale
902      */
903     setScale: function (scale, scaleY) {
904         cc.Node.prototype.setScale.call(this, scale, scaleY);
905         this.updateLabel();
906     },
907 
908     /**
909      * Set scale of x. <br />
910      * Input a number, will be decrease or increase the font size. <br />
911      * Horizontal scale.
912      * @param {Number} scaleX
913      */
914     setScaleX: function (scaleX) {
915         cc.Node.prototype.setScaleX.call(this, scaleX);
916         this.updateLabel();
917     },
918 
919     /**
920      * Set scale of x. <br />
921      * Input a number, will be decrease or increase the font size. <br />
922      * Longitudinal scale.
923      * @param {Number} scaleY
924      */
925     setScaleY: function (scaleY) {
926         cc.Node.prototype.setScaleY.call(this, scaleY);
927         this.updateLabel();
928     },
929 
930     //TODO
931     /**
932      * set fnt file path. <br />
933      * Change the fnt file path.
934      * @param {String} fntFile
935      */
936     setFntFile: function (fntFile) {
937         var self = this;
938         if (fntFile != null && fntFile != self._fntFile) {
939             var newConf = cc.loader.getRes(fntFile);
940 
941             if (!newConf) {
942                 cc.log("cc.LabelBMFont.setFntFile() : Impossible to create font. Please check file");
943                 return;
944             }
945 
946             self._fntFile = fntFile;
947             self._config = newConf;
948 
949             var texture = cc.textureCache.addImage(newConf.atlasName);
950             var locIsLoaded = texture.isLoaded();
951             self._textureLoaded = locIsLoaded;
952             self.texture = texture;
953             if (cc._renderType === cc._RENDER_TYPE_CANVAS)
954                 self._originalTexture = self.texture;
955             if (!locIsLoaded) {
956                 texture.addLoadedEventListener(function (sender) {
957                     var self1 = this;
958                     self1._textureLoaded = true;
959                     self1.texture = sender;
960                     self1.createFontChars();
961                     self1._changeTextureColor();
962                     self1.updateLabel();
963                     self1._callLoadedEventCallbacks();
964                 }, self);
965             } else {
966                 self.createFontChars();
967             }
968         }
969     },
970 
971     /**
972      * Return the fnt file path.
973      * @return {String}
974      */
975     getFntFile: function () {
976         return this._fntFile;
977     },
978 
979     /**
980      * Set the AnchorPoint of the labelBMFont. <br />
981      * In order to change the location of label.
982      * @override
983      * @param {cc.Point|Number} point The anchor point of labelBMFont or The anchor point.x of labelBMFont.
984      * @param {Number} [y] The anchor point.y of labelBMFont.
985      */
986     setAnchorPoint: function (point, y) {
987         cc.Node.prototype.setAnchorPoint.call(this, point, y);
988         this.updateLabel();
989     },
990 
991     _setAnchor: function (p) {
992         cc.Node.prototype._setAnchor.call(this, p);
993         this.updateLabel();
994     },
995 
996     _setAnchorX: function (x) {
997         cc.Node.prototype._setAnchorX.call(this, x);
998         this.updateLabel();
999     },
1000 
1001     _setAnchorY: function (y) {
1002         cc.Node.prototype._setAnchorY.call(this, y);
1003         this.updateLabel();
1004     },
1005 
1006     _atlasNameFromFntFile: function (fntFile) {},
1007 
1008     _kerningAmountForFirst: function (first, second) {
1009         var ret = 0;
1010         var key = (first << 16) | (second & 0xffff);
1011         if (this._configuration.kerningDictionary) {
1012             var element = this._configuration.kerningDictionary[key.toString()];
1013             if (element)
1014                 ret = element.amount;
1015         }
1016         return ret;
1017     },
1018 
1019     _getLetterPosXLeft: function (sp) {
1020         return sp.getPositionX() * this._scaleX - (sp._getWidth() * this._scaleX * sp._getAnchorX());
1021     },
1022 
1023     _getLetterPosXRight: function (sp) {
1024         return sp.getPositionX() * this._scaleX + (sp._getWidth() * this._scaleX * sp._getAnchorX());
1025     },
1026 
1027     //Checking whether the character is a whitespace
1028     _isspace_unicode: function(ch){
1029         ch = ch.charCodeAt(0);
1030         return  ((ch >= 9 && ch <= 13) || ch == 32 || ch == 133 || ch == 160 || ch == 5760
1031             || (ch >= 8192 && ch <= 8202) || ch == 8232 || ch == 8233 || ch == 8239
1032             || ch == 8287 || ch == 12288)
1033     },
1034 
1035     _utf8_trim_ws: function(str){
1036         var len = str.length;
1037 
1038         if (len <= 0)
1039             return;
1040 
1041         var last_index = len - 1;
1042 
1043         // Only start trimming if the last character is whitespace..
1044         if (this._isspace_unicode(str[last_index])) {
1045             for (var i = last_index - 1; i >= 0; --i) {
1046                 if (this._isspace_unicode(str[i])) {
1047                     last_index = i;
1048                 }
1049                 else {
1050                     break;
1051                 }
1052             }
1053             this._utf8_trim_from(str, last_index);
1054         }
1055     },
1056 
1057     //Trims str st str=[0, index) after the operation.
1058     //Return value: the trimmed string.
1059     _utf8_trim_from: function(str, index){
1060         var len = str.length;
1061         if (index >= len || index < 0)
1062             return;
1063         str.splice(index, len);
1064     }
1065 });
1066 
1067 var _p = cc.LabelBMFont.prototype;
1068 
1069 if(cc._renderType === cc._RENDER_TYPE_CANVAS && !cc.sys._supportCanvasNewBlendModes)
1070     _p._changeTextureColor = function(){
1071         if(cc._renderType == cc._RENDER_TYPE_WEBGL)
1072             return;
1073         var locElement, locTexture = this.getTexture();
1074         if (locTexture && locTexture.getContentSize().width>0) {
1075             locElement = locTexture.getHtmlElementObj();
1076             if (!locElement)
1077                 return;
1078             var cacheTextureForColor = cc.textureCache.getTextureColors(this._originalTexture.getHtmlElementObj());
1079             if (cacheTextureForColor) {
1080                 if (locElement instanceof HTMLCanvasElement && !this._rectRotated)
1081                     cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor, null, locElement);
1082                 else{
1083                     locElement = cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor);
1084                     locTexture = new cc.Texture2D();
1085                     locTexture.initWithElement(locElement);
1086                     locTexture.handleLoadedTexture();
1087                     this.setTexture(locTexture);
1088                 }
1089             }
1090         }
1091     };
1092 
1093 /** @expose */
1094 _p.string;
1095 cc.defineGetterSetter(_p, "string", _p.getString, _p._setStringForSetter);
1096 /** @expose */
1097 _p.boundingWidth;
1098 cc.defineGetterSetter(_p, "boundingWidth", _p._getBoundingWidth, _p.setBoundingWidth);
1099 /** @expose */
1100 _p.textAlign;
1101 cc.defineGetterSetter(_p, "textAlign", _p._getAlignment, _p.setAlignment);
1102 
1103 /**
1104  * creates a bitmap font atlas with an initial string and the FNT file
1105  * @deprecated since v3.0 please use new cc.LabelBMFont
1106  * @param {String} str
1107  * @param {String} fntFile
1108  * @param {Number} [width=-1]
1109  * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT]
1110  * @param {cc.Point} [imageOffset=cc.p(0,0)]
1111  * @return {cc.LabelBMFont|Null}
1112  * @example
1113  * // Example 01
1114  * var label1 = cc.LabelBMFont.create("Test case", "test.fnt");
1115  *
1116  * // Example 02
1117  * var label2 = cc.LabelBMFont.create("test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT);
1118  *
1119  * // Example 03
1120  * var label3 = cc.LabelBMFont.create("This is a \n test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT, cc.p(0,0));
1121  */
1122 cc.LabelBMFont.create = function (str, fntFile, width, alignment, imageOffset) {
1123     return new cc.LabelBMFont(str, fntFile, width, alignment, imageOffset);
1124 };
1125 
1126 cc._fntLoader = {
1127     INFO_EXP: /info [^\n]*(\n|$)/gi,
1128     COMMON_EXP: /common [^\n]*(\n|$)/gi,
1129     PAGE_EXP: /page [^\n]*(\n|$)/gi,
1130     CHAR_EXP: /char [^\n]*(\n|$)/gi,
1131     KERNING_EXP: /kerning [^\n]*(\n|$)/gi,
1132     ITEM_EXP: /\w+=[^ \r\n]+/gi,
1133     INT_EXP: /^[\-]?\d+$/,
1134 
1135     _parseStrToObj: function (str) {
1136         var arr = str.match(this.ITEM_EXP);
1137         var obj = {};
1138         if (arr) {
1139             for (var i = 0, li = arr.length; i < li; i++) {
1140                 var tempStr = arr[i];
1141                 var index = tempStr.indexOf("=");
1142                 var key = tempStr.substring(0, index);
1143                 var value = tempStr.substring(index + 1);
1144                 if (value.match(this.INT_EXP)) value = parseInt(value);
1145                 else if (value[0] == '"') value = value.substring(1, value.length - 1);
1146                 obj[key] = value;
1147             }
1148         }
1149         return obj;
1150     },
1151 
1152     /**
1153      * Parse Fnt string.
1154      * @param fntStr
1155      * @param url
1156      * @returns {{}}
1157      */
1158     parseFnt: function (fntStr, url) {
1159         var self = this, fnt = {};
1160         //padding
1161         var infoObj = self._parseStrToObj(fntStr.match(self.INFO_EXP)[0]);
1162         var paddingArr = infoObj["padding"].split(",");
1163         var padding = {
1164             left: parseInt(paddingArr[0]),
1165             top: parseInt(paddingArr[1]),
1166             right: parseInt(paddingArr[2]),
1167             bottom: parseInt(paddingArr[3])
1168         };
1169 
1170         //common
1171         var commonObj = self._parseStrToObj(fntStr.match(self.COMMON_EXP)[0]);
1172         fnt.commonHeight = commonObj["lineHeight"];
1173         if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
1174             var texSize = cc.configuration.getMaxTextureSize();
1175             if (commonObj["scaleW"] > texSize.width || commonObj["scaleH"] > texSize.height)
1176                 cc.log("cc.LabelBMFont._parseCommonArguments(): page can't be larger than supported");
1177         }
1178         if (commonObj["pages"] !== 1) cc.log("cc.LabelBMFont._parseCommonArguments(): only supports 1 page");
1179 
1180         //page
1181         var pageObj = self._parseStrToObj(fntStr.match(self.PAGE_EXP)[0]);
1182         if (pageObj["id"] !== 0) cc.log("cc.LabelBMFont._parseImageFileName() : file could not be found");
1183         fnt.atlasName = cc.path.changeBasename(url, pageObj["file"]);
1184 
1185         //char
1186         var charLines = fntStr.match(self.CHAR_EXP);
1187         var fontDefDictionary = fnt.fontDefDictionary = {};
1188         for (var i = 0, li = charLines.length; i < li; i++) {
1189             var charObj = self._parseStrToObj(charLines[i]);
1190             var charId = charObj["id"];
1191             fontDefDictionary[charId] = {
1192                 rect: {x: charObj["x"], y: charObj["y"], width: charObj["width"], height: charObj["height"]},
1193                 xOffset: charObj["xoffset"],
1194                 yOffset: charObj["yoffset"],
1195                 xAdvance: charObj["xadvance"]
1196             };
1197         }
1198 
1199         //kerning
1200         var kerningDict = fnt.kerningDict = {};
1201         var kerningLines = fntStr.match(self.KERNING_EXP);
1202         if (kerningLines) {
1203             for (var i = 0, li = kerningLines.length; i < li; i++) {
1204                 var kerningObj = self._parseStrToObj(kerningLines[i]);
1205                 kerningDict[(kerningObj["first"] << 16) | (kerningObj["second"] & 0xffff)] = kerningObj["amount"];
1206             }
1207         }
1208         return fnt;
1209     },
1210 
1211     /**
1212      * load the fnt
1213      * @param realUrl
1214      * @param url
1215      * @param res
1216      * @param cb
1217      */
1218     load: function (realUrl, url, res, cb) {
1219         var self = this;
1220         cc.loader.loadTxt(realUrl, function (err, txt) {
1221             if (err) return cb(err);
1222             cb(null, self.parseFnt(txt, url));
1223         });
1224     }
1225 };
1226 cc.loader.register(["fnt"], cc._fntLoader);
1227