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