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