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