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