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 cc.KerningHashElement = function (key, amount) {
 39     this.key = key || 0;   //key for the hash. 16-bit for 1st element, 16-bit for 2nd element
 40     this.amount = amount || 0;
 41 };
 42 
 43 cc.FontDefHashElement = function (key, fontDef) {
 44     this.key = key || 0;        // key. Font Unicode value
 45     this.fontDef = fontDef || new cc.BMFontDef();    // font definition
 46 };
 47 
 48 cc.BMFontDef = function (charID, rect, xOffset, yOffset, xAdvance) {
 49     //! ID of the character
 50     this.charID = charID || 0;
 51     //! origin and size of the font
 52     this.rect = rect || cc.rect(0, 0, 0.1, 0.1);
 53     //! The X amount the image should be offset when drawing the image (in pixels)
 54     this.xOffset = xOffset || 0;
 55     //! The Y amount the image should be offset when drawing the image (in pixels)
 56     this.yOffset = yOffset || 0;
 57     //! The amount to move the current position after drawing the character (in pixels)
 58     this.xAdvance = xAdvance || 0;
 59 };
 60 
 61 cc.BMFontPadding = function (left, top, right, bottom) {
 62     /// padding left
 63     this.left = left || 0;
 64     /// padding top
 65     this.top = top || 0;
 66     /// padding right
 67     this.right = right || 0;
 68     /// padding bottom
 69     this.bottom = bottom || 0;
 70 };
 71 
 72 /**
 73  * cc.BMFontConfiguration has parsed _configuration of the the .fnt file
 74  * @class
 75  * @extends cc.Class
 76  */
 77 cc.BMFontConfiguration = cc.Class.extend(/** @lends cc.BMFontConfiguration# */{
 78     // XXX: Creating a public interface so that the bitmapFontArray[] is acc.esible
 79     //@public
 80     /**
 81      * FNTConfig: Common Height
 82      * @type Number
 83      */
 84     commonHeight:0,
 85 
 86     /**
 87      *  Padding
 88      *  @type cc.BMFontPadding
 89      */
 90     padding:null,
 91 
 92     /**
 93      * atlas name
 94      * @type String
 95      */
 96     atlasName:null,
 97 
 98     /**
 99      * values for kerning
100      * @type cc.KerningHashElement
101      */
102     kerningDictionary:null,
103 
104     /**
105      * values for FontDef
106      * @type cc.FontDefHashElement
107      */
108     fontDefDictionary:null,
109 
110     /**
111      * Character Set defines the letters that actually exist in the font
112      * @type Array
113      */
114     characterSet:null,
115 
116     /**
117      * Constructor
118      */
119     ctor:function () {
120         this.padding = new cc.BMFontPadding();
121         this.atlasName = "";
122         this.kerningDictionary = new cc.KerningHashElement();
123         this.fontDefDictionary = {};
124         this.characterSet = [];
125     },
126 
127     /**
128      * Description of BMFontConfiguration
129      * @return {String}
130      */
131     description:function () {
132         return "<cc.BMFontConfiguration | Kernings:" + this.kerningDictionary.amount + " | Image = " + this.atlasName.toString() + ">";
133     },
134 
135     /**
136      * @return {String}
137      */
138     getAtlasName:function () {
139         return this.atlasName;
140     },
141 
142     /**
143      * @param {String} atlasName
144      */
145     setAtlasName:function (atlasName) {
146         this.atlasName = atlasName;
147     },
148 
149     /**
150      * @return {Object}
151      */
152     getCharacterSet:function () {
153         return this.characterSet;
154     },
155 
156     /**
157      * initializes a BitmapFontConfiguration with a FNT file
158      * @param {String} FNTfile file path
159      * @return {Boolean}
160      */
161     initWithFNTfile:function (FNTfile) {
162         if(!FNTfile || FNTfile.length == 0)
163             throw "cc.BMFontConfiguration.initWithFNTfile(): FNTfile must be non-null and must not be a empty string";
164         this.characterSet = this._parseConfigFile(FNTfile);
165         return this.characterSet != null;
166     },
167 
168     _parseConfigFile:function (controlFile) {
169         var fullpath = cc.FileUtils.getInstance().fullPathForFilename(controlFile);
170         var data = cc.SAXParser.getInstance().getList(fullpath);
171 
172         if (!data) {
173             cc.log("cc.BMFontConfiguration._parseConfigFile(): Error parsing FNTfile " + controlFile);
174             return null;
175         }
176 
177         var validCharsString = [];
178 
179         // parse spacing / padding
180         var line, re, i;
181 
182         re = /padding+[a-z0-9\-= ",]+/gi;
183         line = re.exec(data)[0];
184         if (line) {
185             this._parseInfoArguments(line);
186         }
187 
188         re = /common lineHeight+[a-z0-9\-= ",]+/gi;
189         line = re.exec(data)[0];
190         if (line) {
191             this._parseCommonArguments(line);
192         }
193 
194         //re = /page id=[a-zA-Z0-9\.\-= ",]+/gi;
195         re = /page id=[0-9]+ file="[\w\-\.]+/gi;
196         line = re.exec(data)[0];
197         if (line) {
198             this._parseImageFileName(line, controlFile);
199         }
200 
201         re = /chars c+[a-z0-9\-= ",]+/gi;
202         line = re.exec(data)[0];
203         if (line) {
204             // Ignore this line
205         }
206 
207         re = /char id=\w[a-z0-9\-= ]+/gi;
208         line = data.match(re);
209         if (line) {
210             // Parse the current line and create a new CharDef
211             for (i = 0; i < line.length; i++) {
212                 var element = new cc.FontDefHashElement();
213                 this._parseCharacterDefinition(line[i], element.fontDef);
214                 element.key = element.fontDef.charID;
215                 this.fontDefDictionary[element.key] = element;
216                 validCharsString.push(element.fontDef.charID);
217             }
218         }
219 
220         /*
221          re = /kernings count+[a-z0-9\-= ",]+/gi;
222          if (re.test(data)) {
223          line = RegExp.$1[0];
224          if (line)
225          this._parseKerningCapacity(line);
226          }*/
227 
228         re = /kerning first=\w[a-z0-9\-= ]+/gi;
229         line = data.match(re);
230         if (line) {
231             for (i = 0; i < line.length; i++)
232                 this._parseKerningEntry(line[i]);
233         }
234 
235         return validCharsString;
236     },
237 
238     _parseCharacterDefinition:function (line, characterDefinition) {
239         //////////////////////////////////////////////////////////////////////////
240         // line to parse:
241         // char id=32   x=0     y=0     width=0     height=0     xoffset=0     yoffset=44    xadvance=14     page=0  chnl=0
242         //////////////////////////////////////////////////////////////////////////
243         // Character ID
244         var value = /id=(\d+)/gi.exec(line)[1];
245         characterDefinition.charID = value.toString();
246 
247         // Character x
248         value = /x=([\-\d]+)/gi.exec(line)[1];
249         characterDefinition.rect.x = parseInt(value);
250 
251         // Character y
252         value = /y=([\-\d]+)/gi.exec(line)[1];
253         characterDefinition.rect.y = parseInt(value);
254 
255         // Character width
256         value = /width=([\-\d]+)/gi.exec(line)[1];
257         characterDefinition.rect.width = parseInt(value);
258 
259         // Character height
260         value = /height=([\-\d]+)/gi.exec(line)[1];
261         characterDefinition.rect.height = parseInt(value);
262 
263         // Character xoffset
264         value = /xoffset=([\-\d]+)/gi.exec(line)[1];
265         characterDefinition.xOffset = parseInt(value);
266 
267         // Character yoffset
268         value = /yoffset=([\-\d]+)/gi.exec(line)[1];
269         characterDefinition.yOffset = parseInt(value);
270 
271         // Character xadvance
272         value = /xadvance=([\-\d]+)/gi.exec(line)[1];
273         characterDefinition.xAdvance = parseInt(value);
274 
275     },
276 
277     _parseInfoArguments:function (line) {
278         //////////////////////////////////////////////////////////////////////////
279         // possible lines to parse:
280         // info face="Script" size=32 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=1,4,3,2 spacing=0,0 outline=0
281         // info face="Cracked" size=36 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1
282         //////////////////////////////////////////////////////////////////////////
283 
284         // padding
285         var tmpPadding = /padding=(\d+)[,](\d+)[,](\d+)[,](\d+)/gi.exec(line);
286         this.padding.left = tmpPadding[1];
287         this.padding.top = tmpPadding[2];
288         this.padding.right = tmpPadding[3];
289         this.padding.bottom = tmpPadding[4];
290         cc.log("cocos2d: padding: " + this.padding.left + "," + this.padding.top + "," + this.padding.right + "," + this.padding.bottom);
291     },
292 
293     _parseCommonArguments:function (line) {
294         //////////////////////////////////////////////////////////////////////////
295         // line to parse:
296         // common lineHeight=104 base=26 scaleW=1024 scaleH=512 pages=1 packed=0
297         //////////////////////////////////////////////////////////////////////////
298 
299         var value;
300         // Height
301         this.commonHeight = parseInt(/lineHeight=(\d+)/gi.exec(line)[1]);
302 
303         if (cc.renderContextType === cc.WEBGL) {
304             var texSize = cc.Configuration.getInstance().getMaxTextureSize();
305             var scaleW = parseInt(/scaleW=(\d+)/gi.exec(line)[1]);
306             var scaleH = parseInt(/scaleH=(\d+)/gi.exec(line)[1]);
307             if(scaleW > texSize.width || scaleH > texSize.height)
308                 cc.log("cc.LabelBMFont._parseCommonArguments(): page can't be larger than supported");
309         }
310 
311         // pages. sanity check
312         value = /pages=(\d+)/gi.exec(line)[1];
313         if(parseInt(value) !== 1)
314             cc.log("cc.LabelBMFont._parseCommonArguments(): only supports 1 page");
315 
316         // packed (ignore) What does this mean ??
317     },
318 
319     _parseImageFileName:function (line, fntFile) {
320         //////////////////////////////////////////////////////////////////////////
321         // line to parse:
322         // page id=0 file="bitmapFontTest.png"
323         //////////////////////////////////////////////////////////////////////////
324         var value;
325         // page ID. Sanity check
326         value = /id=(\d+)/gi.exec(line)[1];
327         if(parseInt(value) !== 0)
328             cc.log("cc.LabelBMFont._parseImageFileName() : file could not be found");
329 
330         // file
331         value = /file="([a-zA-Z0-9\-\._]+)/gi.exec(line)[1];
332 
333         this.atlasName = cc.FileUtils.getInstance().fullPathFromRelativeFile(value, fntFile);
334     },
335 
336     _parseKerningCapacity:function (line) {
337     },
338 
339     _parseKerningEntry:function (line) {
340         //////////////////////////////////////////////////////////////////////////
341         // line to parse:
342         // kerning first=121  second=44  amount=-7
343         //////////////////////////////////////////////////////////////////////////
344         // first
345         var value = /first=([\-\d]+)/gi.exec(line)[1];
346         var first = parseInt(value);
347 
348         // second
349         value = /second=([\-\d]+)/gi.exec(line)[1];
350         var second = parseInt(value);
351 
352         // amount
353         value = /amount=([\-\d]+)/gi.exec(line)[1];
354         var amount = parseInt(value);
355 
356         var element = new cc.KerningHashElement();
357         element.amount = amount;
358         element.key = (first << 16) | (second & 0xffff);
359 
360         this.kerningDictionary[element.key] = element;
361     },
362 
363     _purgeKerningDictionary:function () {
364         this.kerningDictionary = null;
365     },
366 
367     _purgeFontDefDictionary:function () {
368         this.fontDefDictionary = null;
369     }
370 });
371 
372 /**
373  * Create a cc.BMFontConfiguration
374  * @param {String} FNTfile
375  * @return {cc.BMFontConfiguration|Null} returns the configuration or null if error
376  * @example
377  * // Example
378  * var conf = cc.BMFontConfiguration.create('myfont.fnt');
379  */
380 cc.BMFontConfiguration.create = function (FNTfile) {
381     var ret = new cc.BMFontConfiguration();
382     if (ret.initWithFNTfile(FNTfile)) {
383         return ret;
384     }
385     return null;
386 };
387 
388 /**
389  * <p>cc.LabelBMFont is a subclass of cc.SpriteBatchNode.</p>
390  *
391  * <p>Features:<br/>
392  * <ul><li>- Treats each character like a cc.Sprite. This means that each individual character can be:</li>
393  * <li>- rotated</li>
394  * <li>- scaled</li>
395  * <li>- translated</li>
396  * <li>- tinted</li>
397  * <li>- chage the opacity</li>
398  * <li>- It can be used as part of a menu item.</li>
399  * <li>- anchorPoint can be used to align the "label"</li>
400  * <li>- Supports AngelCode text format</li></ul></p>
401  *
402  * <p>Limitations:<br/>
403  * - All inner characters are using an anchorPoint of (0.5, 0.5) and it is not recommend to change it
404  * because it might affect the rendering</p>
405  *
406  * <p>cc.LabelBMFont implements the protocol cc.LabelProtocol, like cc.Label and cc.LabelAtlas.<br/>
407  * cc.LabelBMFont has the flexibility of cc.Label, the speed of cc.LabelAtlas and all the features of cc.Sprite.<br/>
408  * If in doubt, use cc.LabelBMFont instead of cc.LabelAtlas / cc.Label.</p>
409  *
410  * <p>Supported editors:<br/>
411  * http://glyphdesigner.71squared.com/ (Commercial, Mac OS X)<br/>
412  * http://www.n4te.com/hiero/hiero.jnlp (Free, Java)<br/>
413  * http://slick.cokeandcode.com/demos/hiero.jnlp (Free, Java)<br/>
414  * http://www.angelcode.com/products/bmfont/ (Free, Windows only)</p>
415  * @class
416  * @extends cc.SpriteBatchNode
417  */
418 cc.LabelBMFont = cc.SpriteBatchNode.extend(/** @lends cc.LabelBMFont# */{
419     RGBAProtocol:true,
420 
421     _opacityModifyRGB:false,
422 
423     _string:null,
424     _configuration:null,
425 
426     // name of fntFile
427     _fntFile:null,
428 
429     // initial string without line breaks
430     _initialString : "",
431 
432     // alignment of all lines
433     _alignment:null,
434 
435     // max width until a line break is added
436     _width:0,
437     _lineBreakWithoutSpaces:false,
438     _imageOffset:null,
439 
440     _reusedChar:null,
441 
442     //texture RGBA
443     _displayedOpacity:255,
444     _realOpacity:255,
445     _displayedColor:null,
446     _realColor:null,
447     _cascadeColorEnabled:false,
448     _cascadeOpacityEnabled:false,
449 
450     _textureLoaded: false,
451     _loadedEventListeners: null,
452 
453     _setString:function(newString, needUpdateLabel){
454         if(!needUpdateLabel){
455             this._string = newString;
456         } else {
457             this._initialString = newString;
458         }
459         var locChildren = this._children;
460         if(locChildren){
461             for(var i = 0; i< locChildren.length;i++){
462                 var selNode = locChildren[i];
463                 if(selNode)
464                     selNode.setVisible(false);
465             }
466         }
467         if(this._textureLoaded){
468             this.createFontChars();
469 
470             if(needUpdateLabel)
471                 this.updateLabel();
472         }
473     },
474     /**
475      * Constructor
476      */
477     ctor:function () {
478         cc.SpriteBatchNode.prototype.ctor.call(this);
479         this._imageOffset = cc.PointZero();
480         this._string = "";
481         this._initialString = "";
482         this._alignment = cc.TEXT_ALIGNMENT_CENTER;
483         this._width = -1;
484         this._configuration = null;
485         this._lineBreakWithoutSpaces = false;
486 
487         this._displayedOpacity = 255;
488         this._realOpacity = 255;
489         this._displayedColor = cc.white();
490         this._realColor = cc.white();
491         this._cascadeColorEnabled = true;
492         this._cascadeOpacityEnabled = true;
493         this._opacityModifyRGB = false;
494 
495         this._fntFile = "";
496         this._reusedChar = [];
497     },
498     /**
499      * return  texture is loaded
500      * @returns {boolean}
501      */
502     textureLoaded:function(){
503         return this._textureLoaded;
504     },
505 
506     /**
507      * add texture loaded event listener
508      * @param {Function} callback
509      * @param {Object} target
510      */
511     addLoadedEventListener:function(callback, target){
512         if(!this._loadedEventListeners)
513             this._loadedEventListeners = [];
514         this._loadedEventListeners.push({eventCallback:callback, eventTarget:target});
515     },
516 
517     _callLoadedEventCallbacks:function(){
518         if(!this._loadedEventListeners)
519             return;
520         var locListeners = this._loadedEventListeners;
521         for(var i = 0, len = locListeners.length;  i < len; i++){
522             var selCallback = locListeners[i];
523             cc.doCallback(selCallback.eventCallback, selCallback.eventTarget, this); 
524         }
525         locListeners.length = 0;
526     },
527 
528     /**
529      * @param {CanvasRenderingContext2D} ctx
530      */
531     draw:function (ctx) {
532         cc.SpriteBatchNode.prototype.draw.call(this, ctx);
533 
534         //LabelBMFont - Debug draw
535         if (cc.LABELBMFONT_DEBUG_DRAW) {
536             var size = this.getContentSize();
537             var pos = cc.p(0 | ( -this._anchorPointInPoints.x), 0 | ( -this._anchorPointInPoints.y));
538             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)];
539             cc.drawingUtil.setDrawColor4B(0,255,0,255);
540             cc.drawingUtil.drawPoly(vertices, 4, true);
541         }
542     },
543 
544     //TODO
545     /**
546      * tint this label
547      * @param {cc.Color3B} color3
548      */
549     setColor:function (color3) {
550         if (((this._realColor.r == color3.r) && (this._realColor.g == color3.g) && (this._realColor.b == color3.b)))
551             return;
552         this._displayedColor = {r:color3.r, g:color3.g, b:color3.b};
553         this._realColor = {r:color3.r, g:color3.g, b:color3.b};
554 
555         if(this._textureLoaded){
556             if(this._cascadeColorEnabled){
557                 var parentColor = cc.white();
558                 var locParent = this._parent;
559                 if(locParent && locParent.RGBAProtocol && locParent.isCascadeColorEnabled())
560                     parentColor = locParent.getDisplayedColor();
561                 this.updateDisplayedColor(parentColor);
562             }
563         }
564     },
565 
566     /**
567      * conforms to cc.RGBAProtocol protocol
568      * @return {Boolean}
569      */
570     isOpacityModifyRGB:function () {
571         return this._opacityModifyRGB;
572     },
573 
574     /**
575      * @param {Boolean} opacityModifyRGB
576      */
577     setOpacityModifyRGB:function (opacityModifyRGB) {
578         this._opacityModifyRGB = opacityModifyRGB;
579         var locChildren = this._children;
580         if (locChildren) {
581             for (var i = 0; i < locChildren.length; i++) {
582                 var node = locChildren[i];
583                 if (node && node.RGBAProtocol)
584                     node.setOpacityModifyRGB(this._opacityModifyRGB);
585             }
586         }
587     },
588 
589     getOpacity:function(){
590         return this._realOpacity;
591     },
592 
593     getDisplayedOpacity:function(){
594         return this._displayedOpacity;
595     },
596 
597     /**
598      * Override synthesized setOpacity to recurse items
599      * @param {Number} opacity
600      */
601     setOpacity:function(opacity){
602         this._displayedOpacity = this._realOpacity = opacity;
603         if(this._cascadeOpacityEnabled){
604             var parentOpacity = 255;
605             var locParent = this._parent;
606             if(locParent && locParent.RGBAProtocol && locParent.isCascadeOpacityEnabled())
607                parentOpacity = locParent.getDisplayedOpacity();
608             this.updateDisplayedOpacity(parentOpacity);
609         }
610     },
611 
612     updateDisplayedOpacity:function(parentOpacity){
613         this._displayedOpacity = this._realOpacity * parentOpacity/255.0;
614         var locChildren = this._children;
615         for(var i = 0; i< locChildren.length; i++){
616             var locChild = locChildren[i];
617             if(cc.Browser.supportWebGL){
618                 locChild.updateDisplayedOpacity(this._displayedOpacity);
619             }else{
620                 cc.NodeRGBA.prototype.updateDisplayedOpacity.call(locChild, this._displayedOpacity);
621                 locChild.setNodeDirty();
622             }
623         }
624         this._changeTextureColor();
625     },
626 
627     isCascadeOpacityEnabled:function(){
628         return false;
629     },
630 
631     setCascadeOpacityEnabled:function(cascadeOpacityEnabled){
632         this._cascadeOpacityEnabled = cascadeOpacityEnabled;
633     },
634 
635     getColor:function(){
636         return this._realColor;
637     },
638 
639     getDisplayedColor:function(){
640         return this._displayedColor;
641     },
642 
643     updateDisplayedColor:function(parentColor){
644         var locDispColor = this._displayedColor;
645         var locRealColor = this._realColor;
646         locDispColor.r = locRealColor.r * parentColor.r/255.0;
647         locDispColor.g = locRealColor.g * parentColor.g/255.0;
648         locDispColor.b = locRealColor.b * parentColor.b/255.0;
649 
650         var locChildren = this._children;
651         for(var i = 0;i < locChildren.length;i++){
652             var locChild = locChildren[i];
653             if(cc.Browser.supportWebGL){
654                 locChild.updateDisplayedColor(this._displayedColor);
655             }else{
656                 cc.NodeRGBA.prototype.updateDisplayedColor.call(locChild, this._displayedColor);
657                 locChild.setNodeDirty();
658             }
659         }
660         this._changeTextureColor();
661     },
662 
663     _changeTextureColor:function(){
664         if(cc.Browser.supportWebGL){
665             return;
666         }
667         var locElement, locTexture = this.getTexture();
668         if (locTexture && locTexture.getContentSize().width>0) {
669             locElement = locTexture.getHtmlElementObj();
670             if (!locElement)
671                 return;
672             var cacheTextureForColor = cc.TextureCache.getInstance().getTextureColors(this._originalTexture.getHtmlElementObj());
673             if (cacheTextureForColor) {
674                 if (locElement instanceof HTMLCanvasElement && !this._rectRotated)
675                     cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor, null, locElement);
676                 else{
677                     locElement = cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor);
678                     locTexture = new cc.Texture2D();
679                     locTexture.initWithElement(locElement);
680                     locTexture.handleLoadedTexture();
681                     this.setTexture(locTexture);
682                 }
683             }
684         }
685     },
686 
687     isCascadeColorEnabled:function(){
688         return false;
689     },
690 
691     setCascadeColorEnabled:function(cascadeColorEnabled){
692         this._cascadeColorEnabled = cascadeColorEnabled;
693     },
694 
695     /**
696      *  init LabelBMFont
697      */
698     init:function () {
699         return this.initWithString(null, null, null, null, null);
700     },
701 
702     /**
703      * init a bitmap font altas with an initial string and the FNT file
704      * @param {String} str
705      * @param {String} fntFile
706      * @param {Number} width
707      * @param {Number} alignment
708      * @param {cc.Point} imageOffset
709      * @return {Boolean}
710      */
711     initWithString:function (str, fntFile, width, alignment, imageOffset) {
712         var theString = str || "";
713 
714         if(this._configuration)
715             cc.log("cc.LabelBMFont.initWithString(): re-init is no longer supported");
716 
717         var texture;
718         if (fntFile) {
719             var newConf = cc.FNTConfigLoadFile(fntFile);
720             if(!newConf){
721                 cc.log("cc.LabelBMFont.initWithString(): Impossible to create font. Please check file");
722                 return false;
723             }
724 
725             this._configuration = newConf;
726             this._fntFile = fntFile;
727             texture = cc.TextureCache.getInstance().addImage(this._configuration.getAtlasName());
728             var locIsLoaded = texture.isLoaded();
729             this._textureLoaded = locIsLoaded;
730             if(!locIsLoaded){
731                 texture.addLoadedEventListener(function(sender){
732                     this._textureLoaded = true;
733                     //reset the LabelBMFont
734                     this.initWithTexture(sender, this._initialString.length);
735                     this.setString(this._initialString,true);
736                     this._callLoadedEventCallbacks();
737                 }, this);
738             }
739         } else{
740             texture = new cc.Texture2D();
741             var image = new Image();
742             texture.initWithElement(image);
743             this._textureLoaded = false;
744         }
745 
746         if (this.initWithTexture(texture, theString.length)) {
747             this._alignment = alignment || cc.TEXT_ALIGNMENT_LEFT;
748             this._imageOffset = imageOffset || cc.PointZero();
749             this._width = (width == null) ? -1 : width;
750 
751             this._displayedOpacity = this._realOpacity = 255;
752             this._displayedColor = cc.white();
753             this._realColor = cc.white();
754             this._cascadeOpacityEnabled = true;
755             this._cascadeColorEnabled = true;
756 
757             this._contentSize.width = 0;
758             this._contentSize.height = 0;
759 
760             this.setAnchorPoint(0.5, 0.5);
761 
762             if (cc.renderContextType === cc.WEBGL) {
763                 var locTexture = this._textureAtlas.getTexture();
764                 this._opacityModifyRGB = locTexture.hasPremultipliedAlpha();
765 
766                 this._reusedChar = new cc.Sprite();
767                 this._reusedChar.initWithTexture(locTexture, cc.rect(0, 0, 0, 0), false);
768                 this._reusedChar.setBatchNode(this);
769             }
770             this.setString(theString,true);
771             return true;
772         }
773         return false;
774     },
775 
776     /**
777      * updates the font chars based on the string to render
778      */
779     createFontChars:function () {
780         var locContextType = cc.renderContextType;
781         var locTexture = (locContextType === cc.CANVAS) ? this.getTexture() : this._textureAtlas.getTexture();
782 
783         var nextFontPositionX = 0;
784         var prev = -1;
785         var kerningAmount = 0;
786 
787         var tmpSize = cc.SizeZero();
788 
789         var longestLine = 0;
790 
791         var quantityOfLines = 1;
792 
793         var stringLen = this._string ? this._string.length : 0;
794 
795         if (stringLen === 0)
796             return;
797 
798         var i, charSet = this._configuration.getCharacterSet();
799         for (i = 0; i < stringLen - 1; i++) {
800             if (this._string.charCodeAt(i) == 10)
801                 quantityOfLines++;
802         }
803 
804         var totalHeight = this._configuration.commonHeight * quantityOfLines;
805         var nextFontPositionY = -(this._configuration.commonHeight - this._configuration.commonHeight * quantityOfLines);
806 
807         for (i = 0; i < stringLen; i++) {
808             var key = this._string.charCodeAt(i);
809 
810             if (key === 10) {
811                 //new line
812                 nextFontPositionX = 0;
813                 nextFontPositionY -= this._configuration.commonHeight;
814                 continue;
815             }
816 
817             if (charSet[key] === null) {
818                 cc.log("cc.LabelBMFont: Attempted to use character not defined in this bitmap: " + this._string[i]);
819                 continue;
820             }
821 
822             kerningAmount = this._kerningAmountForFirst(prev,key);
823             var element = this._configuration.fontDefDictionary[key];
824             if (!element) {
825                 if(key !== 0 && key !== 10)
826                     cc.log("cocos2d: LabelBMFont: character not found " + this._string[i]);
827                 continue;
828             }
829 
830             var fontDef = element.fontDef;
831 
832             var rect = cc.rect(fontDef.rect.x, fontDef.rect.y, fontDef.rect.width, fontDef.rect.height);
833             rect = cc.RECT_PIXELS_TO_POINTS(rect);
834             rect.x += this._imageOffset.x;
835             rect.y += this._imageOffset.y;
836 
837             var fontChar = this.getChildByTag(i);
838             //var hasSprite = true;
839             if (!fontChar) {
840                 fontChar = new cc.Sprite();
841                 if ((key === 32) && (locContextType === cc.CANVAS)) {
842                     fontChar.initWithTexture(locTexture, cc.RectZero(), false);
843                 } else
844                     fontChar.initWithTexture(locTexture, rect, false);
845                 fontChar._newTextureWhenChangeColor = true;
846                 this.addChild(fontChar, 0, i);
847             } else {
848                 if ((key === 32) && (locContextType === cc.CANVAS)) {
849                     fontChar.setTextureRect(rect, false, cc.SizeZero());
850                 } else {
851                     // updating previous sprite
852                     fontChar.setTextureRect(rect, false, rect._size);
853                     // restore to default in case they were modified
854                     fontChar.setVisible(true);
855                 }
856             }
857             // Apply label properties
858             fontChar.setOpacityModifyRGB(this._opacityModifyRGB);
859             // Color MUST be set before opacity, since opacity might change color if OpacityModifyRGB is on
860             if (cc.Browser.supportWebGL) {
861                 fontChar.updateDisplayedColor(this._displayedColor);
862                 fontChar.updateDisplayedOpacity(this._displayedOpacity);
863             } else {
864                 cc.NodeRGBA.prototype.updateDisplayedColor.call(fontChar, this._displayedColor);
865                 cc.NodeRGBA.prototype.updateDisplayedOpacity.call(fontChar, this._displayedOpacity);
866                 fontChar.setNodeDirty();
867             }
868 
869             var yOffset = this._configuration.commonHeight - fontDef.yOffset;
870             var fontPos = cc.p(nextFontPositionX + fontDef.xOffset + fontDef.rect.width * 0.5 + kerningAmount,
871                 nextFontPositionY + yOffset - rect.height * 0.5 * cc.CONTENT_SCALE_FACTOR());
872             fontChar.setPosition(cc.POINT_PIXELS_TO_POINTS(fontPos));
873 
874             // update kerning
875             nextFontPositionX += fontDef.xAdvance + kerningAmount;
876             prev = key;
877 
878             if (longestLine < nextFontPositionX)
879                 longestLine = nextFontPositionX;
880         }
881 
882         tmpSize.width = longestLine;
883         tmpSize.height = totalHeight;
884         this.setContentSize(cc.SIZE_PIXELS_TO_POINTS(tmpSize));
885     },
886 
887     /**
888      * update String
889      * @param {Boolean} fromUpdate
890      */
891     updateString:function (fromUpdate) {
892         var locChildren = this._children;
893         if (locChildren) {
894             for (var i = 0; i < locChildren.length; i++) {
895                 var node = locChildren[i];
896                 if (node)
897                     node.setVisible(false);
898             }
899         }
900         if (this._configuration)
901             this.createFontChars();
902 
903         if (!fromUpdate)
904             this.updateLabel();
905     },
906 
907     /**
908      * get the text of this label
909      * @return {String}
910      */
911     getString:function () {
912         return this._initialString;
913     },
914 
915     /**
916      * set the text
917      * @param {String} newString
918      * @param {Boolean|null} needUpdateLabel
919      */
920     setString: function (newString, needUpdateLabel) {
921         newString = String(newString);
922         if(needUpdateLabel == null)
923             needUpdateLabel = true;
924         if (newString == null || typeof(newString) != "string")
925             newString = newString + "";
926 
927         this._initialString = newString;
928         this._setString(newString, needUpdateLabel);
929     },
930 
931     /**
932      * @deprecated
933      * @param label
934      */
935     setCString:function (label) {
936         this.setString(label,true);
937     },
938 
939     /**
940      *  update Label
941      */
942     updateLabel:function () {
943         this.setString(this._initialString, false);
944 
945         // Step 1: Make multiline
946         if (this._width > 0) {
947             var stringLength = this._string.length;
948             var multiline_string = [];
949             var last_word = [];
950 
951             var line = 1, i = 0, start_line = false, start_word = false, startOfLine = -1, startOfWord = -1, skip = 0, j;
952 
953             var characterSprite;
954             for (j = 0; j < this._children.length; j++) {
955                 var justSkipped = 0;
956                 while (!(characterSprite = this.getChildByTag(j + skip + justSkipped)))
957                     justSkipped++;
958                 skip += justSkipped;
959 
960                 if (i >= stringLength)
961                     break;
962 
963                 var character = this._string[i];
964                 if (!start_word) {
965                     startOfWord = this._getLetterPosXLeft(characterSprite);
966                     start_word = true;
967                 }
968                 if (!start_line) {
969                     startOfLine = startOfWord;
970                     start_line = true;
971                 }
972 
973                 // Newline.
974                 if (character.charCodeAt(0) == 10) {
975                     last_word.push('\n');
976                     multiline_string = multiline_string.concat(last_word);
977                     last_word.length = 0;
978                     start_word = false;
979                     start_line = false;
980                     startOfWord = -1;
981                     startOfLine = -1;
982                     j--;
983                     skip -= justSkipped;
984                     line++;
985 
986                     if (i >= stringLength)
987                         break;
988 
989                     character = this._string[i];
990                     if (!startOfWord) {
991                         startOfWord = this._getLetterPosXLeft(characterSprite);
992                         start_word = true;
993                     }
994                     if (!startOfLine) {
995                         startOfLine = startOfWord;
996                         start_line = true;
997                     }
998                     i++;
999                     continue;
1000                 }
1001 
1002                 // Whitespace.
1003                 if (cc.isspace_unicode(character)) {
1004                     last_word.push(character);
1005                     multiline_string = multiline_string.concat(last_word);
1006                     last_word.length = 0;
1007                     start_word = false;
1008                     startOfWord = -1;
1009                     i++;
1010                     continue;
1011                 }
1012 
1013                 // Out of bounds.
1014                 if (this._getLetterPosXRight(characterSprite) - startOfLine > this._width) {
1015                     if (!this._lineBreakWithoutSpaces) {
1016                         last_word.push(character);
1017 
1018                         var found = multiline_string.lastIndexOf(" ");
1019                         if (found != -1)
1020                             cc.utf8_trim_ws(multiline_string);
1021                         else
1022                             multiline_string = [];
1023 
1024                         if (multiline_string.length > 0)
1025                             multiline_string.push('\n');
1026 
1027                         line++;
1028                         start_line = false;
1029                         startOfLine = -1;
1030                         i++;
1031                     } else {
1032                         cc.utf8_trim_ws(last_word);
1033 
1034                         last_word.push('\n');
1035                         multiline_string = multiline_string.concat(last_word);
1036                         last_word.length = 0;
1037                         start_word = false;
1038                         start_line = false;
1039                         startOfWord = -1;
1040                         startOfLine = -1;
1041                         line++;
1042 
1043                         if (i >= stringLength)
1044                             break;
1045 
1046                         if (!startOfWord) {
1047                             startOfWord = this._getLetterPosXLeft(characterSprite);
1048                             start_word = true;
1049                         }
1050                         if (!startOfLine) {
1051                             startOfLine = startOfWord;
1052                             start_line = true;
1053                         }
1054                         j--;
1055                     }
1056                 } else {
1057                     // Character is normal.
1058                     last_word.push(character);
1059                     i++;
1060                 }
1061             }
1062 
1063             multiline_string = multiline_string.concat(last_word);
1064             var len = multiline_string.length;
1065             var str_new = "";
1066 
1067             for (i = 0; i < len; ++i)
1068                 str_new += multiline_string[i];
1069 
1070             str_new = str_new + String.fromCharCode(0);
1071             //this.updateString(true);
1072             this._setString(str_new, false)
1073         }
1074 
1075         // Step 2: Make alignment
1076         if (this._alignment != cc.TEXT_ALIGNMENT_LEFT) {
1077             i = 0;
1078 
1079             var lineNumber = 0;
1080             var strlen = this._string.length;
1081             var last_line = [];
1082 
1083             for (var ctr = 0; ctr < strlen; ctr++) {
1084                 if (this._string[ctr].charCodeAt(0) == 10 || this._string[ctr].charCodeAt(0) == 0) {
1085                     var lineWidth = 0;
1086                     var line_length = last_line.length;
1087                     // if last line is empty we must just increase lineNumber and work with next line
1088                     if (line_length == 0) {
1089                         lineNumber++;
1090                         continue;
1091                     }
1092                     var index = i + line_length - 1 + lineNumber;
1093                     if (index < 0) continue;
1094 
1095                     var lastChar = this.getChildByTag(index);
1096                     if (lastChar == null)
1097                         continue;
1098                     lineWidth = lastChar.getPositionX() + lastChar.getContentSize().width / 2;
1099 
1100                     var shift = 0;
1101                     switch (this._alignment) {
1102                         case cc.TEXT_ALIGNMENT_CENTER:
1103                             shift = this.getContentSize().width / 2 - lineWidth / 2;
1104                             break;
1105                         case cc.TEXT_ALIGNMENT_RIGHT:
1106                             shift = this.getContentSize().width - lineWidth;
1107                             break;
1108                         default:
1109                             break;
1110                     }
1111 
1112                     if (shift != 0) {
1113                         for (j = 0; j < line_length; j++) {
1114                             index = i + j + lineNumber;
1115                             if (index < 0) continue;
1116                             characterSprite = this.getChildByTag(index);
1117                             if (characterSprite)
1118                                 characterSprite.setPosition(cc.pAdd(characterSprite.getPosition(), cc.p(shift, 0)));
1119                         }
1120                     }
1121 
1122                     i += line_length;
1123                     lineNumber++;
1124 
1125                     last_line.length = 0;
1126                     continue;
1127                 }
1128                 last_line.push(this._string[i]);
1129             }
1130         }
1131     },
1132 
1133     /**
1134      * Set text vertical alignment
1135      * @param {Number} alignment
1136      */
1137     setAlignment:function (alignment) {
1138         this._alignment = alignment;
1139         this.updateLabel();
1140     },
1141 
1142     /**
1143      * @param {Number} width
1144      */
1145     setWidth:function (width) {
1146         this._width = width;
1147         this.updateLabel();
1148     },
1149 
1150     /**
1151      * @param {Boolean}  breakWithoutSpace
1152      */
1153     setLineBreakWithoutSpace:function (breakWithoutSpace) {
1154         this._lineBreakWithoutSpaces = breakWithoutSpace;
1155         this.updateLabel();
1156     },
1157 
1158     /**
1159      * @param {Number} scale
1160      * @param {Number} [scaleY=null]
1161      */
1162     setScale:function (scale, scaleY) {
1163         cc.Node.prototype.setScale.call(this, scale, scaleY);
1164         this.updateLabel();
1165     },
1166 
1167     /**
1168      * @param {Number} scaleX
1169      */
1170     setScaleX:function (scaleX) {
1171         cc.Node.prototype.setScaleX.call(this,scaleX);
1172         this.updateLabel();
1173     },
1174 
1175     /**
1176      * @param {Number} scaleY
1177      */
1178     setScaleY:function (scaleY) {
1179         cc.Node.prototype.setScaleY.call(this,scaleY);
1180         this.updateLabel();
1181     },
1182 
1183     //TODO
1184     /**
1185      * set fnt file path
1186      * @param {String} fntFile
1187      */
1188     setFntFile:function (fntFile) {
1189         if (fntFile != null && fntFile != this._fntFile) {
1190             var newConf = cc.FNTConfigLoadFile(fntFile);
1191 
1192             if(!newConf){
1193                 cc.log("cc.LabelBMFont.setFntFile() : Impossible to create font. Please check file");
1194                 return;
1195             }
1196 
1197             this._fntFile = fntFile;
1198             this._configuration = newConf;
1199 
1200             var texture = cc.TextureCache.getInstance().addImage(this._configuration.getAtlasName());
1201             var locIsLoaded = texture.isLoaded();
1202             this._textureLoaded = locIsLoaded;
1203             this.setTexture(texture);
1204             if (cc.renderContextType === cc.CANVAS)
1205                 this._originalTexture = this.getTexture();
1206             if(!locIsLoaded){
1207                 texture.addLoadedEventListener(function(sender){
1208                     this._textureLoaded = true;
1209                     this.setTexture(sender);
1210                     this.createFontChars();
1211                     this._changeTextureColor();
1212                     this.updateLabel();
1213                     this._callLoadedEventCallbacks();
1214                 }, this);
1215             } else {
1216                 this.createFontChars();
1217             }
1218         }
1219     },
1220 
1221     /**
1222      * @return {String}
1223      */
1224     getFntFile:function () {
1225         return this._fntFile;
1226     },
1227 
1228     /**
1229      * set the AnchorPoint of the labelBMFont
1230      * @override
1231      * @param {cc.Point|Number} point The anchor point of labelBMFont or The anchor point.x of labelBMFont.
1232      * @param {Number} [y] The anchor point.y of labelBMFont.
1233      */
1234     setAnchorPoint:function (point, y) {
1235 	    cc.Node.prototype.setAnchorPoint.call(this, point, y);
1236         this.updateLabel();
1237     },
1238 
1239     _atlasNameFromFntFile:function (fntFile) {
1240     },
1241 
1242     _kerningAmountForFirst:function (first, second) {
1243         var ret = 0;
1244         var key = (first << 16) | (second & 0xffff);
1245         if (this._configuration.kerningDictionary) {
1246             var element = this._configuration.kerningDictionary[key.toString()];
1247             if (element)
1248                 ret = element.amount;
1249         }
1250         return ret;
1251     },
1252 
1253     _getLetterPosXLeft:function (sp) {
1254         return sp.getPositionX() * this._scaleX - (sp.getContentSize().width * this._scaleX * sp.getAnchorPoint().x);
1255     },
1256 
1257     _getLetterPosXRight:function (sp) {
1258         return sp.getPositionX() * this._scaleX + (sp.getContentSize().width * this._scaleX * (1 - sp.getAnchorPoint().x));
1259     }
1260 });
1261 
1262 /**
1263  * creates a bitmap font atlas with an initial string and the FNT file
1264  * @param {String} str
1265  * @param {String} fntFile
1266  * @param {Number} width
1267  * @param {Number} alignment
1268  * @param {cc.Point} imageOffset
1269  * @return {cc.LabelBMFont|Null}
1270  * @example
1271  * // Example 01
1272  * var label1 = cc.LabelBMFont.create("Test case", "test.fnt");
1273  *
1274  * // Example 02
1275  * var label2 = cc.LabelBMFont.create("test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT);
1276  *
1277  * // Example 03
1278  * var label3 = cc.LabelBMFont.create("This is a \n test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT, cc.PointZero());
1279  */
1280 cc.LabelBMFont.create = function (str, fntFile, width, alignment, imageOffset) {
1281     var ret = new cc.LabelBMFont();
1282     if (str === undefined) {
1283         if (ret && ret.init())
1284             return ret;
1285         return null;
1286     }
1287 
1288     if (ret && ret.initWithString(str, fntFile, width, alignment, imageOffset)) {
1289         return ret;
1290     }
1291     return null;
1292 };
1293 
1294 /**
1295  * shared instance of configuration
1296  * @type cc.BMFontConfiguration
1297  */
1298 cc.LabelBMFont._configurations = null;
1299 
1300 /**
1301  * Load the .fnt file
1302  * @param {String} fntFile
1303  * @return {cc.BMFontConfiguration}
1304  * Constructor
1305  */
1306 cc.FNTConfigLoadFile = function (fntFile) {
1307     if (!cc.LabelBMFont._configurations) {
1308         cc.LabelBMFont._configurations = {};
1309     }
1310     var ret = cc.LabelBMFont._configurations[fntFile];
1311     if (!ret) {
1312         ret = cc.BMFontConfiguration.create(fntFile);
1313         cc.LabelBMFont._configurations[fntFile] = ret;
1314     }
1315     return ret;
1316 };
1317 
1318 /**
1319  * Purges the cached .fnt data
1320  */
1321 cc.LabelBMFont.purgeCachedData = function () {
1322     cc.FNTConfigRemoveCache();
1323 };
1324 
1325 /**
1326  * Purges the FNT config cache
1327  */
1328 cc.FNTConfigRemoveCache = function () {
1329     if (cc.LabelBMFont._configurations) {
1330         cc.LabelBMFont._configurations = null;
1331     }
1332 };
1333 
1334 /**
1335  * @param {String} ch
1336  * @return {Boolean}  weather the character is a whitespace character.
1337  */
1338 cc.isspace_unicode = function (ch) {
1339     ch = ch.charCodeAt(0);
1340     return  ((ch >= 9 && ch <= 13) || ch == 32 || ch == 133 || ch == 160 || ch == 5760
1341         || (ch >= 8192 && ch <= 8202) || ch == 8232 || ch == 8233 || ch == 8239
1342         || ch == 8287 || ch == 12288)
1343 };
1344 
1345 /**
1346  * @param {Array} str
1347  */
1348 cc.utf8_trim_ws = function (str) {
1349     var len = str.length;
1350 
1351     if (len <= 0)
1352         return;
1353 
1354     var last_index = len - 1;
1355 
1356     // Only start trimming if the last character is whitespace..
1357     if (cc.isspace_unicode(str[last_index])) {
1358         for (var i = last_index - 1; i >= 0; --i) {
1359             if (cc.isspace_unicode(str[i])) {
1360                 last_index = i;
1361             }
1362             else {
1363                 break;
1364             }
1365         }
1366         cc.utf8_trim_from(str, last_index);
1367     }
1368 };
1369 
1370 /**
1371  * Trims str st str=[0, index) after the operation.
1372  * Return value: the trimmed string.
1373  * @param {Array} str  he string to trim
1374  * @param {Number} index  the index to start trimming from.
1375  */
1376 cc.utf8_trim_from = function (str, index) {
1377     var len = str.length;
1378     if (index >= len || index < 0)
1379         return;
1380     str.splice(index, len);
1381 };
1382