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