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