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