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