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