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