1 /**************************************************************************** 2 Copyright (c) 2008-2010 Ricardo Quesada 3 Copyright (c) 2011-2012 cocos2d-x.org 4 Copyright (c) 2013-2014 Chukong Technologies 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 /** 39 * <p>cc.LabelBMFont is a subclass of cc.SpriteBatchNode.</p> 40 * 41 * <p>Features:<br/> 42 * <ul><li>- Treats each character like a cc.Sprite. This means that each individual character can be:</li> 43 * <li>- rotated</li> 44 * <li>- scaled</li> 45 * <li>- translated</li> 46 * <li>- tinted</li> 47 * <li>- chage the opacity</li> 48 * <li>- It can be used as part of a menu item.</li> 49 * <li>- anchorPoint can be used to align the "label"</li> 50 * <li>- Supports AngelCode text format</li></ul></p> 51 * 52 * <p>Limitations:<br/> 53 * - All inner characters are using an anchorPoint of (0.5, 0.5) and it is not recommend to change it 54 * because it might affect the rendering</p> 55 * 56 * <p>cc.LabelBMFont implements the protocol cc.LabelProtocol, like cc.Label and cc.LabelAtlas.<br/> 57 * cc.LabelBMFont has the flexibility of cc.Label, the speed of cc.LabelAtlas and all the features of cc.Sprite.<br/> 58 * If in doubt, use cc.LabelBMFont instead of cc.LabelAtlas / cc.Label.</p> 59 * 60 * <p>Supported editors:<br/> 61 * http://glyphdesigner.71squared.com/ (Commercial, Mac OS X)<br/> 62 * http://www.n4te.com/hiero/hiero.jnlp (Free, Java)<br/> 63 * http://slick.cokeandcode.com/demos/hiero.jnlp (Free, Java)<br/> 64 * http://www.angelcode.com/products/bmfont/ (Free, Windows only)</p> 65 * @class 66 * @extends cc.SpriteBatchNode 67 * 68 * @property {String} string - Content string of label 69 * @property {Number} textAlign - Horizontal Alignment of label, cc.TEXT_ALIGNMENT_LEFT|cc.TEXT_ALIGNMENT_CENTER|cc.TEXT_ALIGNMENT_RIGHT 70 * @property {Number} boundingWidth - Width of the bounding box of label, the real content width is limited by boundingWidth 71 * 72 * @param {String} str 73 * @param {String} fntFile 74 * @param {Number} [width=-1] 75 * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT] 76 * @param {cc.Point} [imageOffset=cc.p(0,0)] 77 * 78 * @example 79 * // Example 01 80 * var label1 = new cc.LabelBMFont("Test case", "test.fnt"); 81 * 82 * // Example 02 83 * var label2 = new cc.LabelBMFont("test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT); 84 * 85 * // Example 03 86 * var label3 = new cc.LabelBMFont("This is a \n test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT, cc.p(0,0)); 87 */ 88 cc.LabelBMFont = cc.SpriteBatchNode.extend(/** @lends cc.LabelBMFont# */{ 89 90 //property string is Getter and Setter. 91 //property textAlign is Getter and Setter. 92 //property boundingWidth is Getter and Setter. 93 94 _opacityModifyRGB: false, 95 96 _string: "", 97 _config: null, 98 99 // name of fntFile 100 _fntFile: "", 101 102 // initial string without line breaks 103 _initialString: "", 104 105 // alignment of all lines 106 _alignment: cc.TEXT_ALIGNMENT_CENTER, 107 108 // max width until a line break is added 109 _width: -1, 110 _lineBreakWithoutSpaces: false, 111 _imageOffset: null, 112 113 _reusedChar: null, 114 115 //texture RGBA 116 _displayedOpacity: 255, 117 _realOpacity: 255, 118 _displayedColor: null, 119 _realColor: null, 120 _cascadeColorEnabled: true, 121 _cascadeOpacityEnabled: true, 122 123 _textureLoaded: false, 124 _loadedEventListeners: null, 125 _className: "LabelBMFont", 126 127 _setString: function (newString, needUpdateLabel) { 128 if (!needUpdateLabel) { 129 this._string = newString; 130 } else { 131 this._initialString = newString; 132 } 133 var locChildren = this._children; 134 if (locChildren) { 135 for (var i = 0; i < locChildren.length; i++) { 136 var selNode = locChildren[i]; 137 if (selNode) 138 selNode.setVisible(false); 139 } 140 } 141 if (this._textureLoaded) { 142 this.createFontChars(); 143 144 if (needUpdateLabel) 145 this.updateLabel(); 146 } 147 }, 148 149 /** 150 * Constructor function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. <br /> 151 * creates a bitmap font atlas with an initial string and the FNT file. 152 * @param {String} str 153 * @param {String} fntFile 154 * @param {Number} [width=-1] 155 * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT] 156 * @param {cc.Point} [imageOffset=cc.p(0,0)] 157 */ 158 ctor: function (str, fntFile, width, alignment, imageOffset) { 159 var self = this; 160 cc.SpriteBatchNode.prototype.ctor.call(self); 161 self._imageOffset = cc.p(0, 0); 162 self._displayedColor = cc.color(255, 255, 255, 255); 163 self._realColor = cc.color(255, 255, 255, 255); 164 self._reusedChar = []; 165 166 this.initWithString(str, fntFile, width, alignment, imageOffset); 167 }, 168 169 /** 170 * return texture is loaded 171 * @returns {boolean} 172 */ 173 textureLoaded: function () { 174 return this._textureLoaded; 175 }, 176 177 /** 178 * add texture loaded event listener. <br /> 179 * Will execute the callback in the loaded. 180 * @param {Function} callback 181 * @param {Object} target 182 */ 183 addLoadedEventListener: function (callback, target) { 184 if (!this._loadedEventListeners) 185 this._loadedEventListeners = []; 186 this._loadedEventListeners.push({eventCallback: callback, eventTarget: target}); 187 }, 188 189 _callLoadedEventCallbacks: function () { 190 if (!this._loadedEventListeners) 191 return; 192 var locListeners = this._loadedEventListeners; 193 for (var i = 0, len = locListeners.length; i < len; i++) { 194 var selCallback = locListeners[i]; 195 selCallback.eventCallback.call(selCallback.eventTarget, this); 196 } 197 locListeners.length = 0; 198 }, 199 200 /** 201 * Draw this font. 202 * @param {CanvasRenderingContext2D} ctx 203 */ 204 draw: function (ctx) { 205 cc.SpriteBatchNode.prototype.draw.call(this, ctx); 206 207 //LabelBMFont - Debug draw 208 if (cc.LABELBMFONT_DEBUG_DRAW) { 209 var size = this.getContentSize(); 210 var pos = cc.p(0 | ( -this._anchorPointInPoints.x), 0 | ( -this._anchorPointInPoints.y)); 211 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)]; 212 cc._drawingUtil.setDrawColor(0, 255, 0, 255); 213 cc._drawingUtil.drawPoly(vertices, 4, true); 214 } 215 }, 216 217 //TODO 218 /** 219 * tint this label 220 * @param {cc.Color} color 221 */ 222 setColor: function (color) { 223 var locDisplayed = this._displayedColor, locRealColor = this._realColor; 224 if ((locRealColor.r == color.r) && (locRealColor.g == color.g) && (locRealColor.b == color.b) && (locRealColor.a == color.a)) 225 return; 226 locDisplayed.r = locRealColor.r = color.r; 227 locDisplayed.g = locRealColor.g = color.g; 228 locDisplayed.b = locRealColor.b = color.b; 229 230 if (this._textureLoaded) { 231 if (this._cascadeColorEnabled) { 232 var parentColor = cc.color.WHITE; 233 var locParent = this._parent; 234 if (locParent && locParent.cascadeColor) 235 parentColor = locParent.getDisplayedColor(); 236 this.updateDisplayedColor(parentColor); 237 } 238 } 239 }, 240 241 /** 242 * Conforms to cc.RGBAProtocol protocol. 243 * @return {Boolean} 244 */ 245 isOpacityModifyRGB: function () { 246 return this._opacityModifyRGB; 247 }, 248 249 /** 250 * Set whether to support cc.RGBAProtocol protocol 251 * @param {Boolean} opacityModifyRGB 252 */ 253 setOpacityModifyRGB: function (opacityModifyRGB) { 254 this._opacityModifyRGB = opacityModifyRGB; 255 var locChildren = this._children; 256 if (locChildren) { 257 for (var i = 0; i < locChildren.length; i++) { 258 var node = locChildren[i]; 259 if (node) 260 node.opacityModifyRGB = this._opacityModifyRGB; 261 } 262 } 263 }, 264 265 /** 266 * Gets the real opacity. 267 * @returns {number} 268 */ 269 getOpacity: function () { 270 return this._realOpacity; 271 }, 272 273 /** 274 * Gets the display opacity. 275 * @returns {number} 276 */ 277 getDisplayedOpacity: function () { 278 return this._displayedOpacity; 279 }, 280 281 /** 282 * Override synthesized setOpacity to recurse items 283 * @param {Number} opacity 284 */ 285 setOpacity: function (opacity) { 286 this._displayedOpacity = this._realOpacity = opacity; 287 if (this._cascadeOpacityEnabled) { 288 var parentOpacity = 255; 289 var locParent = this._parent; 290 if (locParent && locParent.cascadeOpacity) 291 parentOpacity = locParent.getDisplayedOpacity(); 292 this.updateDisplayedOpacity(parentOpacity); 293 } 294 295 this._displayedColor.a = this._realColor.a = opacity; 296 }, 297 298 /** 299 * Override synthesized update pacity to recurse items 300 * @param parentOpacity 301 */ 302 updateDisplayedOpacity: function (parentOpacity) { 303 this._displayedOpacity = this._realOpacity * parentOpacity / 255.0; 304 var locChildren = this._children; 305 for (var i = 0; i < locChildren.length; i++) { 306 var locChild = locChildren[i]; 307 if (cc._renderType == cc._RENDER_TYPE_WEBGL) { 308 locChild.updateDisplayedOpacity(this._displayedOpacity); 309 } else { 310 cc.Node.prototype.updateDisplayedOpacity.call(locChild, this._displayedOpacity); 311 locChild.setNodeDirty(); 312 } 313 } 314 this._changeTextureColor(); 315 }, 316 317 /** 318 * Checking cascade opacity enabled 319 * @returns {boolean} 320 */ 321 isCascadeOpacityEnabled: function () { 322 return false; 323 }, 324 325 /** 326 * Set cascade opacity enabled 327 * @param {Boolean} cascadeOpacityEnabled 328 */ 329 setCascadeOpacityEnabled: function (cascadeOpacityEnabled) { 330 this._cascadeOpacityEnabled = cascadeOpacityEnabled; 331 }, 332 333 /** 334 * Gets the real color. <br /> 335 * Create a new cc.Color clone in this real color. 336 * @returns {cc.Color} 337 */ 338 getColor: function () { 339 var locRealColor = this._realColor; 340 return cc.color(locRealColor.r, locRealColor.g, locRealColor.b, locRealColor.a); 341 }, 342 343 /** 344 * Gets the display color. <br /> 345 * Create a new cc.Color clone in this display color. 346 * @returns {cc.Color} 347 */ 348 getDisplayedColor: function () { 349 var dc = this._displayedColor; 350 return cc.color(dc.r, dc.g, dc.b, dc.a); 351 }, 352 353 /** 354 * Update the display color. <br /> 355 * Only update this label display color. 356 * @returns {cc.Color} 357 */ 358 updateDisplayedColor: function (parentColor) { 359 var locDispColor = this._displayedColor; 360 var locRealColor = this._realColor; 361 locDispColor.r = locRealColor.r * parentColor.r / 255.0; 362 locDispColor.g = locRealColor.g * parentColor.g / 255.0; 363 locDispColor.b = locRealColor.b * parentColor.b / 255.0; 364 365 var locChildren = this._children; 366 for (var i = 0; i < locChildren.length; i++) { 367 var locChild = locChildren[i]; 368 if (cc._renderType == cc._RENDER_TYPE_WEBGL) { 369 locChild.updateDisplayedColor(this._displayedColor); 370 } else { 371 if(cc.sys._supportCanvasNewBlendModes) 372 cc.Node.prototype.updateDisplayedColor.call(locChild, this._displayedColor); 373 else 374 locChild.updateDisplayedColor(this._displayedColor); 375 locChild.setNodeDirty(); 376 } 377 } 378 this._changeTextureColor(); 379 }, 380 381 _changeTextureColor: function () { 382 if (cc._renderType == cc._RENDER_TYPE_WEBGL) 383 return; 384 385 var locTexture = this.getTexture(); 386 if (locTexture && locTexture.getContentSize().width>0) { 387 var element = this._originalTexture.getHtmlElementObj(); 388 if(!element) 389 return; 390 var locElement = locTexture.getHtmlElementObj(); 391 var textureRect = cc.rect(0, 0, element.width, element.height); 392 if (locElement instanceof HTMLCanvasElement && !this._rectRotated){ 393 cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect, locElement); 394 this.setTexture(locTexture); 395 } else { 396 locElement = cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect); 397 locTexture = new cc.Texture2D(); 398 locTexture.initWithElement(locElement); 399 locTexture.handleLoadedTexture(); 400 this.setTexture(locTexture); 401 } 402 } 403 }, 404 405 /** 406 * Checking cascade color enabled 407 * @returns {boolean} 408 */ 409 isCascadeColorEnabled: function () { 410 return false; 411 }, 412 413 /** 414 * Override synthesized setOpacity to recurse items 415 * @param {Boolean} cascadeColorEnabled 416 */ 417 setCascadeColorEnabled: function (cascadeColorEnabled) { 418 this._cascadeColorEnabled = cascadeColorEnabled; 419 }, 420 421 /** 422 * Initialization of the node, please do not call this function by yourself, you should pass the parameters to constructor to initialize it
. 423 */ 424 init: function () { 425 return this.initWithString(null, null, null, null, null); 426 }, 427 428 /** 429 * init a bitmap font atlas with an initial string and the FNT file 430 * @param {String} str 431 * @param {String} fntFile 432 * @param {Number} [width=-1] 433 * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT] 434 * @param {cc.Point} [imageOffset=cc.p(0,0)] 435 * @return {Boolean} 436 */ 437 initWithString: function (str, fntFile, width, alignment, imageOffset) { 438 var self = this, theString = str || ""; 439 440 if (self._config) 441 cc.log("cc.LabelBMFont.initWithString(): re-init is no longer supported"); 442 443 444 var texture; 445 if (fntFile) { 446 var newConf = cc.loader.getRes(fntFile); 447 if (!newConf) { 448 cc.log("cc.LabelBMFont.initWithString(): Impossible to create font. Please check file"); 449 return false; 450 } 451 452 self._config = newConf; 453 self._fntFile = fntFile; 454 texture = cc.textureCache.addImage(newConf.atlasName); 455 var locIsLoaded = texture.isLoaded(); 456 self._textureLoaded = locIsLoaded; 457 if (!locIsLoaded) { 458 texture.addLoadedEventListener(function (sender) { 459 var self1 = this; 460 self1._textureLoaded = true; 461 //reset the LabelBMFont 462 self1.initWithTexture(sender, self1._initialString.length); 463 self1.setString(self1._initialString, true); 464 self1._callLoadedEventCallbacks(); 465 }, self); 466 } 467 } else { 468 texture = new cc.Texture2D(); 469 var image = new Image(); 470 texture.initWithElement(image); 471 self._textureLoaded = false; 472 } 473 474 if (self.initWithTexture(texture, theString.length)) { 475 self._alignment = alignment || cc.TEXT_ALIGNMENT_LEFT; 476 self._imageOffset = imageOffset || cc.p(0, 0); 477 self._width = (width == null) ? -1 : width; 478 479 self._displayedOpacity = self._realOpacity = 255; 480 self._displayedColor = cc.color(255, 255, 255, 255); 481 self._realColor = cc.color(255, 255, 255, 255); 482 self._cascadeOpacityEnabled = true; 483 self._cascadeColorEnabled = true; 484 485 self._contentSize.width = 0; 486 self._contentSize.height = 0; 487 488 self.setAnchorPoint(0.5, 0.5); 489 490 if (cc._renderType === cc._RENDER_TYPE_WEBGL) { 491 var locTexture = self.textureAtlas.texture; 492 self._opacityModifyRGB = locTexture.hasPremultipliedAlpha(); 493 494 var reusedChar = self._reusedChar = new cc.Sprite(); 495 reusedChar.initWithTexture(locTexture, cc.rect(0, 0, 0, 0), false); 496 reusedChar.batchNode = self; 497 } 498 self.setString(theString, true); 499 return true; 500 } 501 return false; 502 }, 503 504 /** 505 * updates the font chars based on the string to render 506 */ 507 createFontChars: function () { 508 var self = this; 509 var locContextType = cc._renderType; 510 var locTexture = (locContextType === cc._RENDER_TYPE_CANVAS) ? self.texture : self.textureAtlas.texture; 511 512 var nextFontPositionX = 0; 513 514 var tmpSize = cc.size(0, 0); 515 516 var longestLine = 0; 517 518 var quantityOfLines = 1; 519 520 var locStr = self._string; 521 var stringLen = locStr ? locStr.length : 0; 522 523 if (stringLen === 0) 524 return; 525 526 var i, locCfg = self._config, locKerningDict = locCfg.kerningDict, 527 locCommonH = locCfg.commonHeight, locFontDict = locCfg.fontDefDictionary; 528 for (i = 0; i < stringLen - 1; i++) { 529 if (locStr.charCodeAt(i) == 10) quantityOfLines++; 530 } 531 532 var totalHeight = locCommonH * quantityOfLines; 533 var nextFontPositionY = -(locCommonH - locCommonH * quantityOfLines); 534 535 var prev = -1; 536 for (i = 0; i < stringLen; i++) { 537 var key = locStr.charCodeAt(i); 538 if (key == 0) continue; 539 540 if (key === 10) { 541 //new line 542 nextFontPositionX = 0; 543 nextFontPositionY -= locCfg.commonHeight; 544 continue; 545 } 546 547 var kerningAmount = locKerningDict[(prev << 16) | (key & 0xffff)] || 0; 548 var fontDef = locFontDict[key]; 549 if (!fontDef) { 550 cc.log("cocos2d: LabelBMFont: character not found " + locStr[i]); 551 continue; 552 } 553 554 var rect = cc.rect(fontDef.rect.x, fontDef.rect.y, fontDef.rect.width, fontDef.rect.height); 555 rect = cc.rectPixelsToPoints(rect); 556 rect.x += self._imageOffset.x; 557 rect.y += self._imageOffset.y; 558 559 var fontChar = self.getChildByTag(i); 560 //var hasSprite = true; 561 if (!fontChar) { 562 fontChar = new cc.Sprite(); 563 if ((key === 32) && (locContextType === cc._RENDER_TYPE_CANVAS)) rect = cc.rect(0, 0, 0, 0); 564 fontChar.initWithTexture(locTexture, rect, false); 565 fontChar._newTextureWhenChangeColor = true; 566 self.addChild(fontChar, 0, i); 567 } else { 568 if ((key === 32) && (locContextType === cc._RENDER_TYPE_CANVAS)) { 569 fontChar.setTextureRect(rect, false, cc.size(0, 0)); 570 } else { 571 // updating previous sprite 572 fontChar.setTextureRect(rect, false); 573 // restore to default in case they were modified 574 fontChar.visible = true; 575 } 576 } 577 // Apply label properties 578 fontChar.opacityModifyRGB = self._opacityModifyRGB; 579 // Color MUST be set before opacity, since opacity might change color if OpacityModifyRGB is on 580 if (cc._renderType == cc._RENDER_TYPE_WEBGL) { 581 fontChar.updateDisplayedColor(self._displayedColor); 582 fontChar.updateDisplayedOpacity(self._displayedOpacity); 583 } else { 584 cc.Node.prototype.updateDisplayedColor.call(fontChar, self._displayedColor); 585 cc.Node.prototype.updateDisplayedOpacity.call(fontChar, self._displayedOpacity); 586 fontChar.setNodeDirty(); 587 } 588 589 var yOffset = locCfg.commonHeight - fontDef.yOffset; 590 var fontPos = cc.p(nextFontPositionX + fontDef.xOffset + fontDef.rect.width * 0.5 + kerningAmount, 591 nextFontPositionY + yOffset - rect.height * 0.5 * cc.contentScaleFactor()); 592 fontChar.setPosition(cc.pointPixelsToPoints(fontPos)); 593 594 // update kerning 595 nextFontPositionX += fontDef.xAdvance + kerningAmount; 596 prev = key; 597 598 if (longestLine < nextFontPositionX) 599 longestLine = nextFontPositionX; 600 } 601 602 //If the last character processed has an xAdvance which is less that the width of the characters image, then we need 603 // to adjust the width of the string to take this into account, or the character will overlap the end of the bounding box 604 //TODO sync to -x 605 if(fontDef && fontDef.xAdvance < fontDef.rect.width) 606 tmpSize.width = longestLine - fontDef.xAdvance + fontDef.rect.width; 607 else 608 tmpSize.width = longestLine; 609 tmpSize.height = totalHeight; 610 self.setContentSize(cc.sizePixelsToPoints(tmpSize)); 611 }, 612 613 /** 614 * Update String. <br /> 615 * Only update this label display string. 616 * @param {Boolean} fromUpdate 617 */ 618 updateString: function (fromUpdate) { 619 var self = this; 620 var locChildren = self._children; 621 if (locChildren) { 622 for (var i = 0, li = locChildren.length; i < li; i++) { 623 var node = locChildren[i]; 624 if (node) node.visible = false; 625 } 626 } 627 if (self._config) 628 self.createFontChars(); 629 630 if (!fromUpdate) 631 self.updateLabel(); 632 }, 633 634 /** 635 * Gets the text of this label 636 * @return {String} 637 */ 638 getString: function () { 639 return this._initialString; 640 }, 641 642 /** 643 * Set the text 644 * @param {String} newString 645 * @param {Boolean|null} needUpdateLabel 646 */ 647 setString: function (newString, needUpdateLabel) { 648 newString = String(newString); 649 if (needUpdateLabel == null) 650 needUpdateLabel = true; 651 if (newString == null || !cc.isString(newString)) 652 newString = newString + ""; 653 654 this._initialString = newString; 655 this._setString(newString, needUpdateLabel); 656 }, 657 658 _setStringForSetter: function (newString) { 659 this.setString(newString, false); 660 }, 661 662 /** 663 * Set the text. <br /> 664 * Change this Label display string. 665 * @deprecated since v3.0 please use .setString 666 * @param label 667 */ 668 setCString: function (label) { 669 this.setString(label, true); 670 }, 671 672 /** 673 * Update Label. <br /> 674 * Update this Label display string and more... 675 */ 676 updateLabel: function () { 677 var self = this; 678 self.string = self._initialString; 679 680 // Step 1: Make multiline 681 if (self._width > 0) { 682 var stringLength = self._string.length; 683 var multiline_string = []; 684 var last_word = []; 685 686 var line = 1, i = 0, start_line = false, start_word = false, startOfLine = -1, startOfWord = -1, skip = 0; 687 688 var characterSprite; 689 for (var j = 0, lj = self._children.length; j < lj; j++) { 690 var justSkipped = 0; 691 while (!(characterSprite = self.getChildByTag(j + skip + justSkipped))) 692 justSkipped++; 693 skip += justSkipped; 694 695 if (i >= stringLength) 696 break; 697 698 var character = self._string[i]; 699 if (!start_word) { 700 startOfWord = self._getLetterPosXLeft(characterSprite); 701 start_word = true; 702 } 703 if (!start_line) { 704 startOfLine = startOfWord; 705 start_line = true; 706 } 707 708 // Newline. 709 if (character.charCodeAt(0) == 10) { 710 last_word.push('\n'); 711 multiline_string = multiline_string.concat(last_word); 712 last_word.length = 0; 713 start_word = false; 714 start_line = false; 715 startOfWord = -1; 716 startOfLine = -1; 717 //i+= justSkipped; 718 j--; 719 skip -= justSkipped; 720 line++; 721 722 if (i >= stringLength) 723 break; 724 725 character = self._string[i]; 726 if (!startOfWord) { 727 startOfWord = self._getLetterPosXLeft(characterSprite); 728 start_word = true; 729 } 730 if (!startOfLine) { 731 startOfLine = startOfWord; 732 start_line = true; 733 } 734 i++; 735 continue; 736 } 737 738 // Whitespace. 739 if (this._isspace_unicode(character)) { 740 last_word.push(character); 741 multiline_string = multiline_string.concat(last_word); 742 last_word.length = 0; 743 start_word = false; 744 startOfWord = -1; 745 i++; 746 continue; 747 } 748 749 // Out of bounds. 750 if (self._getLetterPosXRight(characterSprite) - startOfLine > self._width) { 751 if (!self._lineBreakWithoutSpaces) { 752 last_word.push(character); 753 754 var found = multiline_string.lastIndexOf(" "); 755 if (found != -1) 756 this._utf8_trim_ws(multiline_string); 757 else 758 multiline_string = []; 759 760 if (multiline_string.length > 0) 761 multiline_string.push('\n'); 762 763 line++; 764 start_line = false; 765 startOfLine = -1; 766 i++; 767 } else { 768 this._utf8_trim_ws(last_word); 769 770 last_word.push('\n'); 771 multiline_string = multiline_string.concat(last_word); 772 last_word.length = 0; 773 start_word = false; 774 start_line = false; 775 startOfWord = -1; 776 startOfLine = -1; 777 line++; 778 779 if (i >= stringLength) 780 break; 781 782 if (!startOfWord) { 783 startOfWord = self._getLetterPosXLeft(characterSprite); 784 start_word = true; 785 } 786 if (!startOfLine) { 787 startOfLine = startOfWord; 788 start_line = true; 789 } 790 j--; 791 } 792 } else { 793 // Character is normal. 794 last_word.push(character); 795 i++; 796 } 797 } 798 799 multiline_string = multiline_string.concat(last_word); 800 var len = multiline_string.length; 801 var str_new = ""; 802 803 for (i = 0; i < len; ++i) 804 str_new += multiline_string[i]; 805 806 str_new = str_new + String.fromCharCode(0); 807 //this.updateString(true); 808 self._setString(str_new, false) 809 } 810 811 // Step 2: Make alignment 812 if (self._alignment != cc.TEXT_ALIGNMENT_LEFT) { 813 i = 0; 814 815 var lineNumber = 0; 816 var strlen = self._string.length; 817 var last_line = []; 818 819 for (var ctr = 0; ctr < strlen; ctr++) { 820 if (self._string[ctr].charCodeAt(0) == 10 || self._string[ctr].charCodeAt(0) == 0) { 821 var lineWidth = 0; 822 var line_length = last_line.length; 823 // if last line is empty we must just increase lineNumber and work with next line 824 if (line_length == 0) { 825 lineNumber++; 826 continue; 827 } 828 var index = i + line_length - 1 + lineNumber; 829 if (index < 0) continue; 830 831 var lastChar = self.getChildByTag(index); 832 if (lastChar == null) 833 continue; 834 lineWidth = lastChar.getPositionX() + lastChar._getWidth() / 2; 835 836 var shift = 0; 837 switch (self._alignment) { 838 case cc.TEXT_ALIGNMENT_CENTER: 839 shift = self.width / 2 - lineWidth / 2; 840 break; 841 case cc.TEXT_ALIGNMENT_RIGHT: 842 shift = self.width - lineWidth; 843 break; 844 default: 845 break; 846 } 847 848 if (shift != 0) { 849 for (j = 0; j < line_length; j++) { 850 index = i + j + lineNumber; 851 if (index < 0) continue; 852 characterSprite = self.getChildByTag(index); 853 if (characterSprite) 854 characterSprite.x += shift; 855 } 856 } 857 858 i += line_length; 859 lineNumber++; 860 861 last_line.length = 0; 862 continue; 863 } 864 last_line.push(self._string[i]); 865 } 866 } 867 }, 868 869 /** 870 * Set text alignment. 871 * @param {Number} alignment 872 */ 873 setAlignment: function (alignment) { 874 this._alignment = alignment; 875 this.updateLabel(); 876 }, 877 878 _getAlignment: function () { 879 return this._alignment; 880 }, 881 882 /** 883 * Set the bounding width. <br /> 884 * max with display width. The exceeding string will be wrapping. 885 * @param {Number} width 886 */ 887 setBoundingWidth: function (width) { 888 this._width = width; 889 this.updateLabel(); 890 }, 891 892 _getBoundingWidth: function () { 893 return this._width; 894 }, 895 896 /** 897 * Set the param to change English word warp according to whether the space. <br /> 898 * default is false. 899 * @param {Boolean} breakWithoutSpace 900 */ 901 setLineBreakWithoutSpace: function (breakWithoutSpace) { 902 this._lineBreakWithoutSpaces = breakWithoutSpace; 903 this.updateLabel(); 904 }, 905 906 /** 907 * Set scale. <br /> 908 * Input a number, will be decrease or increase the font size. <br /> 909 * @param {Number} scale 910 * @param {Number} [scaleY=null] default is scale 911 */ 912 setScale: function (scale, scaleY) { 913 cc.Node.prototype.setScale.call(this, scale, scaleY); 914 this.updateLabel(); 915 }, 916 917 /** 918 * Set scale of x. <br /> 919 * Input a number, will be decrease or increase the font size. <br /> 920 * Horizontal scale. 921 * @param {Number} scaleX 922 */ 923 setScaleX: function (scaleX) { 924 cc.Node.prototype.setScaleX.call(this, scaleX); 925 this.updateLabel(); 926 }, 927 928 /** 929 * Set scale of x. <br /> 930 * Input a number, will be decrease or increase the font size. <br /> 931 * Longitudinal scale. 932 * @param {Number} scaleY 933 */ 934 setScaleY: function (scaleY) { 935 cc.Node.prototype.setScaleY.call(this, scaleY); 936 this.updateLabel(); 937 }, 938 939 //TODO 940 /** 941 * set fnt file path. <br /> 942 * Change the fnt file path. 943 * @param {String} fntFile 944 */ 945 setFntFile: function (fntFile) { 946 var self = this; 947 if (fntFile != null && fntFile != self._fntFile) { 948 var newConf = cc.loader.getRes(fntFile); 949 950 if (!newConf) { 951 cc.log("cc.LabelBMFont.setFntFile() : Impossible to create font. Please check file"); 952 return; 953 } 954 955 self._fntFile = fntFile; 956 self._config = newConf; 957 958 var texture = cc.textureCache.addImage(newConf.atlasName); 959 var locIsLoaded = texture.isLoaded(); 960 self._textureLoaded = locIsLoaded; 961 self.texture = texture; 962 if (cc._renderType === cc._RENDER_TYPE_CANVAS) 963 self._originalTexture = self.texture; 964 if (!locIsLoaded) { 965 texture.addLoadedEventListener(function (sender) { 966 var self1 = this; 967 self1._textureLoaded = true; 968 self1.texture = sender; 969 self1.createFontChars(); 970 self1._changeTextureColor(); 971 self1.updateLabel(); 972 self1._callLoadedEventCallbacks(); 973 }, self); 974 } else { 975 self.createFontChars(); 976 } 977 } 978 }, 979 980 /** 981 * Return the fnt file path. 982 * @return {String} 983 */ 984 getFntFile: function () { 985 return this._fntFile; 986 }, 987 988 /** 989 * Set the AnchorPoint of the labelBMFont. <br /> 990 * In order to change the location of label. 991 * @override 992 * @param {cc.Point|Number} point The anchor point of labelBMFont or The anchor point.x of labelBMFont. 993 * @param {Number} [y] The anchor point.y of labelBMFont. 994 */ 995 setAnchorPoint: function (point, y) { 996 cc.Node.prototype.setAnchorPoint.call(this, point, y); 997 this.updateLabel(); 998 }, 999 1000 _setAnchor: function (p) { 1001 cc.Node.prototype._setAnchor.call(this, p); 1002 this.updateLabel(); 1003 }, 1004 1005 _setAnchorX: function (x) { 1006 cc.Node.prototype._setAnchorX.call(this, x); 1007 this.updateLabel(); 1008 }, 1009 1010 _setAnchorY: function (y) { 1011 cc.Node.prototype._setAnchorY.call(this, y); 1012 this.updateLabel(); 1013 }, 1014 1015 _atlasNameFromFntFile: function (fntFile) {}, 1016 1017 _kerningAmountForFirst: function (first, second) { 1018 var ret = 0; 1019 var key = (first << 16) | (second & 0xffff); 1020 if (this._configuration.kerningDictionary) { 1021 var element = this._configuration.kerningDictionary[key.toString()]; 1022 if (element) 1023 ret = element.amount; 1024 } 1025 return ret; 1026 }, 1027 1028 _getLetterPosXLeft: function (sp) { 1029 return sp.getPositionX() * this._scaleX - (sp._getWidth() * this._scaleX * sp._getAnchorX()); 1030 }, 1031 1032 _getLetterPosXRight: function (sp) { 1033 return sp.getPositionX() * this._scaleX + (sp._getWidth() * this._scaleX * sp._getAnchorX()); 1034 }, 1035 1036 //Checking whether the character is a whitespace 1037 _isspace_unicode: function(ch){ 1038 ch = ch.charCodeAt(0); 1039 return ((ch >= 9 && ch <= 13) || ch == 32 || ch == 133 || ch == 160 || ch == 5760 1040 || (ch >= 8192 && ch <= 8202) || ch == 8232 || ch == 8233 || ch == 8239 1041 || ch == 8287 || ch == 12288) 1042 }, 1043 1044 _utf8_trim_ws: function(str){ 1045 var len = str.length; 1046 1047 if (len <= 0) 1048 return; 1049 1050 var last_index = len - 1; 1051 1052 // Only start trimming if the last character is whitespace.. 1053 if (this._isspace_unicode(str[last_index])) { 1054 for (var i = last_index - 1; i >= 0; --i) { 1055 if (this._isspace_unicode(str[i])) { 1056 last_index = i; 1057 } 1058 else { 1059 break; 1060 } 1061 } 1062 this._utf8_trim_from(str, last_index); 1063 } 1064 }, 1065 1066 //Trims str st str=[0, index) after the operation. 1067 //Return value: the trimmed string. 1068 _utf8_trim_from: function(str, index){ 1069 var len = str.length; 1070 if (index >= len || index < 0) 1071 return; 1072 str.splice(index, len); 1073 } 1074 }); 1075 1076 var _p = cc.LabelBMFont.prototype; 1077 1078 if(cc._renderType === cc._RENDER_TYPE_CANVAS && !cc.sys._supportCanvasNewBlendModes) 1079 _p._changeTextureColor = function(){ 1080 if(cc._renderType == cc._RENDER_TYPE_WEBGL) 1081 return; 1082 var locElement, locTexture = this.getTexture(); 1083 if (locTexture && locTexture.getContentSize().width>0) { 1084 locElement = locTexture.getHtmlElementObj(); 1085 if (!locElement) 1086 return; 1087 var cacheTextureForColor = cc.textureCache.getTextureColors(this._originalTexture.getHtmlElementObj()); 1088 if (cacheTextureForColor) { 1089 if (locElement instanceof HTMLCanvasElement && !this._rectRotated) 1090 cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor, null, locElement); 1091 else{ 1092 locElement = cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor); 1093 locTexture = new cc.Texture2D(); 1094 locTexture.initWithElement(locElement); 1095 locTexture.handleLoadedTexture(); 1096 this.setTexture(locTexture); 1097 } 1098 } 1099 } 1100 }; 1101 1102 /** @expose */ 1103 _p.string; 1104 cc.defineGetterSetter(_p, "string", _p.getString, _p._setStringForSetter); 1105 /** @expose */ 1106 _p.boundingWidth; 1107 cc.defineGetterSetter(_p, "boundingWidth", _p._getBoundingWidth, _p.setBoundingWidth); 1108 /** @expose */ 1109 _p.textAlign; 1110 cc.defineGetterSetter(_p, "textAlign", _p._getAlignment, _p.setAlignment); 1111 1112 /** 1113 * creates a bitmap font atlas with an initial string and the FNT file 1114 * @deprecated since v3.0 please use new cc.LabelBMFont 1115 * @param {String} str 1116 * @param {String} fntFile 1117 * @param {Number} [width=-1] 1118 * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT] 1119 * @param {cc.Point} [imageOffset=cc.p(0,0)] 1120 * @return {cc.LabelBMFont|Null} 1121 * @example 1122 * // Example 01 1123 * var label1 = cc.LabelBMFont.create("Test case", "test.fnt"); 1124 * 1125 * // Example 02 1126 * var label2 = cc.LabelBMFont.create("test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT); 1127 * 1128 * // Example 03 1129 * var label3 = cc.LabelBMFont.create("This is a \n test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT, cc.p(0,0)); 1130 */ 1131 cc.LabelBMFont.create = function (str, fntFile, width, alignment, imageOffset) { 1132 return new cc.LabelBMFont(str, fntFile, width, alignment, imageOffset); 1133 }; 1134 1135 cc._fntLoader = { 1136 INFO_EXP: /info [^\n]*(\n|$)/gi, 1137 COMMON_EXP: /common [^\n]*(\n|$)/gi, 1138 PAGE_EXP: /page [^\n]*(\n|$)/gi, 1139 CHAR_EXP: /char [^\n]*(\n|$)/gi, 1140 KERNING_EXP: /kerning [^\n]*(\n|$)/gi, 1141 ITEM_EXP: /\w+=[^ \r\n]+/gi, 1142 INT_EXP: /^[\-]?\d+$/, 1143 1144 _parseStrToObj: function (str) { 1145 var arr = str.match(this.ITEM_EXP); 1146 var obj = {}; 1147 if (arr) { 1148 for (var i = 0, li = arr.length; i < li; i++) { 1149 var tempStr = arr[i]; 1150 var index = tempStr.indexOf("="); 1151 var key = tempStr.substring(0, index); 1152 var value = tempStr.substring(index + 1); 1153 if (value.match(this.INT_EXP)) value = parseInt(value); 1154 else if (value[0] == '"') value = value.substring(1, value.length - 1); 1155 obj[key] = value; 1156 } 1157 } 1158 return obj; 1159 }, 1160 1161 /** 1162 * Parse Fnt string. 1163 * @param fntStr 1164 * @param url 1165 * @returns {{}} 1166 */ 1167 parseFnt: function (fntStr, url) { 1168 var self = this, fnt = {}; 1169 //padding 1170 var infoObj = self._parseStrToObj(fntStr.match(self.INFO_EXP)[0]); 1171 var paddingArr = infoObj["padding"].split(","); 1172 var padding = { 1173 left: parseInt(paddingArr[0]), 1174 top: parseInt(paddingArr[1]), 1175 right: parseInt(paddingArr[2]), 1176 bottom: parseInt(paddingArr[3]) 1177 }; 1178 1179 //common 1180 var commonObj = self._parseStrToObj(fntStr.match(self.COMMON_EXP)[0]); 1181 fnt.commonHeight = commonObj["lineHeight"]; 1182 if (cc._renderType === cc._RENDER_TYPE_WEBGL) { 1183 var texSize = cc.configuration.getMaxTextureSize(); 1184 if (commonObj["scaleW"] > texSize.width || commonObj["scaleH"] > texSize.height) 1185 cc.log("cc.LabelBMFont._parseCommonArguments(): page can't be larger than supported"); 1186 } 1187 if (commonObj["pages"] !== 1) cc.log("cc.LabelBMFont._parseCommonArguments(): only supports 1 page"); 1188 1189 //page 1190 var pageObj = self._parseStrToObj(fntStr.match(self.PAGE_EXP)[0]); 1191 if (pageObj["id"] !== 0) cc.log("cc.LabelBMFont._parseImageFileName() : file could not be found"); 1192 fnt.atlasName = cc.path.changeBasename(url, pageObj["file"]); 1193 1194 //char 1195 var charLines = fntStr.match(self.CHAR_EXP); 1196 var fontDefDictionary = fnt.fontDefDictionary = {}; 1197 for (var i = 0, li = charLines.length; i < li; i++) { 1198 var charObj = self._parseStrToObj(charLines[i]); 1199 var charId = charObj["id"]; 1200 fontDefDictionary[charId] = { 1201 rect: {x: charObj["x"], y: charObj["y"], width: charObj["width"], height: charObj["height"]}, 1202 xOffset: charObj["xoffset"], 1203 yOffset: charObj["yoffset"], 1204 xAdvance: charObj["xadvance"] 1205 }; 1206 } 1207 1208 //kerning 1209 var kerningDict = fnt.kerningDict = {}; 1210 var kerningLines = fntStr.match(self.KERNING_EXP); 1211 if (kerningLines) { 1212 for (var i = 0, li = kerningLines.length; i < li; i++) { 1213 var kerningObj = self._parseStrToObj(kerningLines[i]); 1214 kerningDict[(kerningObj["first"] << 16) | (kerningObj["second"] & 0xffff)] = kerningObj["amount"]; 1215 } 1216 } 1217 return fnt; 1218 }, 1219 1220 /** 1221 * load the fnt 1222 * @param realUrl 1223 * @param url 1224 * @param res 1225 * @param cb 1226 */ 1227 load: function (realUrl, url, res, cb) { 1228 var self = this; 1229 cc.loader.loadTxt(realUrl, function (err, txt) { 1230 if (err) return cb(err); 1231 cb(null, self.parseFnt(txt, url)); 1232 }); 1233 } 1234 }; 1235 cc.loader.register(["fnt"], cc._fntLoader); 1236