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 cc.Node.prototype.updateDisplayedColor.call(locChild, this._displayedColor); 372 locChild.setNodeDirty(); 373 } 374 } 375 this._changeTextureColor(); 376 }, 377 378 _changeTextureColor: function () { 379 if (cc._renderType == cc._RENDER_TYPE_WEBGL) 380 return; 381 382 var locTexture = this.getTexture(); 383 if (locTexture && locTexture.getContentSize().width>0) { 384 var element = this._originalTexture.getHtmlElementObj(); 385 if(!element) 386 return; 387 var locElement = locTexture.getHtmlElementObj(); 388 var textureRect = cc.rect(0, 0, element.width, element.height); 389 if (locElement instanceof HTMLCanvasElement && !this._rectRotated){ 390 cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect, locElement); 391 this.setTexture(locTexture); 392 } else { 393 locElement = cc.generateTintImageWithMultiply(element, this._displayedColor, textureRect); 394 locTexture = new cc.Texture2D(); 395 locTexture.initWithElement(locElement); 396 locTexture.handleLoadedTexture(); 397 this.setTexture(locTexture); 398 } 399 } 400 }, 401 402 /** 403 * Checking cascade color enabled 404 * @returns {boolean} 405 */ 406 isCascadeColorEnabled: function () { 407 return false; 408 }, 409 410 /** 411 * Override synthesized setOpacity to recurse items 412 * @param {Boolean} cascadeColorEnabled 413 */ 414 setCascadeColorEnabled: function (cascadeColorEnabled) { 415 this._cascadeColorEnabled = cascadeColorEnabled; 416 }, 417 418 /** 419 * Initialization of the node, please do not call this function by yourself, you should pass the parameters to constructor to initialize it
. 420 */ 421 init: function () { 422 return this.initWithString(null, null, null, null, null); 423 }, 424 425 /** 426 * init a bitmap font atlas with an initial string and the FNT file 427 * @param {String} str 428 * @param {String} fntFile 429 * @param {Number} [width=-1] 430 * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT] 431 * @param {cc.Point} [imageOffset=cc.p(0,0)] 432 * @return {Boolean} 433 */ 434 initWithString: function (str, fntFile, width, alignment, imageOffset) { 435 var self = this, theString = str || ""; 436 437 if (self._config) 438 cc.log("cc.LabelBMFont.initWithString(): re-init is no longer supported"); 439 440 441 var texture; 442 if (fntFile) { 443 var newConf = cc.loader.getRes(fntFile); 444 if (!newConf) { 445 cc.log("cc.LabelBMFont.initWithString(): Impossible to create font. Please check file"); 446 return false; 447 } 448 449 self._config = newConf; 450 self._fntFile = fntFile; 451 texture = cc.textureCache.addImage(newConf.atlasName); 452 var locIsLoaded = texture.isLoaded(); 453 self._textureLoaded = locIsLoaded; 454 if (!locIsLoaded) { 455 texture.addLoadedEventListener(function (sender) { 456 var self1 = this; 457 self1._textureLoaded = true; 458 //reset the LabelBMFont 459 self1.initWithTexture(sender, self1._initialString.length); 460 self1.setString(self1._initialString, true); 461 self1._callLoadedEventCallbacks(); 462 }, self); 463 } 464 } else { 465 texture = new cc.Texture2D(); 466 var image = new Image(); 467 texture.initWithElement(image); 468 self._textureLoaded = false; 469 } 470 471 if (self.initWithTexture(texture, theString.length)) { 472 self._alignment = alignment || cc.TEXT_ALIGNMENT_LEFT; 473 self._imageOffset = imageOffset || cc.p(0, 0); 474 self._width = (width == null) ? -1 : width; 475 476 self._displayedOpacity = self._realOpacity = 255; 477 self._displayedColor = cc.color(255, 255, 255, 255); 478 self._realColor = cc.color(255, 255, 255, 255); 479 self._cascadeOpacityEnabled = true; 480 self._cascadeColorEnabled = true; 481 482 self._contentSize.width = 0; 483 self._contentSize.height = 0; 484 485 self.setAnchorPoint(0.5, 0.5); 486 487 if (cc._renderType === cc._RENDER_TYPE_WEBGL) { 488 var locTexture = self.textureAtlas.texture; 489 self._opacityModifyRGB = locTexture.hasPremultipliedAlpha(); 490 491 var reusedChar = self._reusedChar = new cc.Sprite(); 492 reusedChar.initWithTexture(locTexture, cc.rect(0, 0, 0, 0), false); 493 reusedChar.batchNode = self; 494 } 495 self.setString(theString, true); 496 return true; 497 } 498 return false; 499 }, 500 501 /** 502 * updates the font chars based on the string to render 503 */ 504 createFontChars: function () { 505 var self = this; 506 var locContextType = cc._renderType; 507 var locTexture = (locContextType === cc._RENDER_TYPE_CANVAS) ? self.texture : self.textureAtlas.texture; 508 509 var nextFontPositionX = 0; 510 511 var tmpSize = cc.size(0, 0); 512 513 var longestLine = 0; 514 515 var quantityOfLines = 1; 516 517 var locStr = self._string; 518 var stringLen = locStr ? locStr.length : 0; 519 520 if (stringLen === 0) 521 return; 522 523 var i, locCfg = self._config, locKerningDict = locCfg.kerningDict, 524 locCommonH = locCfg.commonHeight, locFontDict = locCfg.fontDefDictionary; 525 for (i = 0; i < stringLen - 1; i++) { 526 if (locStr.charCodeAt(i) == 10) quantityOfLines++; 527 } 528 529 var totalHeight = locCommonH * quantityOfLines; 530 var nextFontPositionY = -(locCommonH - locCommonH * quantityOfLines); 531 532 var prev = -1; 533 for (i = 0; i < stringLen; i++) { 534 var key = locStr.charCodeAt(i); 535 if (key == 0) continue; 536 537 if (key === 10) { 538 //new line 539 nextFontPositionX = 0; 540 nextFontPositionY -= locCfg.commonHeight; 541 continue; 542 } 543 544 var kerningAmount = locKerningDict[(prev << 16) | (key & 0xffff)] || 0; 545 var fontDef = locFontDict[key]; 546 if (!fontDef) { 547 cc.log("cocos2d: LabelBMFont: character not found " + locStr[i]); 548 continue; 549 } 550 551 var rect = cc.rect(fontDef.rect.x, fontDef.rect.y, fontDef.rect.width, fontDef.rect.height); 552 rect = cc.rectPixelsToPoints(rect); 553 rect.x += self._imageOffset.x; 554 rect.y += self._imageOffset.y; 555 556 var fontChar = self.getChildByTag(i); 557 //var hasSprite = true; 558 if (!fontChar) { 559 fontChar = new cc.Sprite(); 560 if ((key === 32) && (locContextType === cc._RENDER_TYPE_CANVAS)) rect = cc.rect(0, 0, 0, 0); 561 fontChar.initWithTexture(locTexture, rect, false); 562 fontChar._newTextureWhenChangeColor = true; 563 self.addChild(fontChar, 0, i); 564 } else { 565 if ((key === 32) && (locContextType === cc._RENDER_TYPE_CANVAS)) { 566 fontChar.setTextureRect(rect, false, cc.size(0, 0)); 567 } else { 568 // updating previous sprite 569 fontChar.setTextureRect(rect, false); 570 // restore to default in case they were modified 571 fontChar.visible = true; 572 } 573 } 574 // Apply label properties 575 fontChar.opacityModifyRGB = self._opacityModifyRGB; 576 // Color MUST be set before opacity, since opacity might change color if OpacityModifyRGB is on 577 if (cc._renderType == cc._RENDER_TYPE_WEBGL) { 578 fontChar.updateDisplayedColor(self._displayedColor); 579 fontChar.updateDisplayedOpacity(self._displayedOpacity); 580 } else { 581 cc.Node.prototype.updateDisplayedColor.call(fontChar, self._displayedColor); 582 cc.Node.prototype.updateDisplayedOpacity.call(fontChar, self._displayedOpacity); 583 fontChar.setNodeDirty(); 584 } 585 586 var yOffset = locCfg.commonHeight - fontDef.yOffset; 587 var fontPos = cc.p(nextFontPositionX + fontDef.xOffset + fontDef.rect.width * 0.5 + kerningAmount, 588 nextFontPositionY + yOffset - rect.height * 0.5 * cc.contentScaleFactor()); 589 fontChar.setPosition(cc.pointPixelsToPoints(fontPos)); 590 591 // update kerning 592 nextFontPositionX += fontDef.xAdvance + kerningAmount; 593 prev = key; 594 595 if (longestLine < nextFontPositionX) 596 longestLine = nextFontPositionX; 597 } 598 599 tmpSize.width = longestLine; 600 tmpSize.height = totalHeight; 601 self.setContentSize(cc.sizePixelsToPoints(tmpSize)); 602 }, 603 604 /** 605 * Update String. <br /> 606 * Only update this label displa string. 607 * @param {Boolean} fromUpdate 608 */ 609 updateString: function (fromUpdate) { 610 var self = this; 611 var locChildren = self._children; 612 if (locChildren) { 613 for (var i = 0, li = locChildren.length; i < li; i++) { 614 var node = locChildren[i]; 615 if (node) node.visible = false; 616 } 617 } 618 if (self._config) 619 self.createFontChars(); 620 621 if (!fromUpdate) 622 self.updateLabel(); 623 }, 624 625 /** 626 * Gets the text of this label 627 * @return {String} 628 */ 629 getString: function () { 630 return this._initialString; 631 }, 632 633 /** 634 * Set the text 635 * @param {String} newString 636 * @param {Boolean|null} needUpdateLabel 637 */ 638 setString: function (newString, needUpdateLabel) { 639 newString = String(newString); 640 if (needUpdateLabel == null) 641 needUpdateLabel = true; 642 if (newString == null || typeof(newString) != "string") 643 newString = newString + ""; 644 645 this._initialString = newString; 646 this._setString(newString, needUpdateLabel); 647 }, 648 649 _setStringForSetter: function (newString) { 650 this.setString(newString, false); 651 }, 652 653 /** 654 * Set the text. <br /> 655 * Change this Label display string. 656 * @deprecated since v3.0 please use .setString 657 * @param label 658 */ 659 setCString: function (label) { 660 this.setString(label, true); 661 }, 662 663 /** 664 * Update Label. <br /> 665 * Update this Label display string and more... 666 */ 667 updateLabel: function () { 668 var self = this; 669 self.string = self._initialString; 670 671 // Step 1: Make multiline 672 if (self._width > 0) { 673 var stringLength = self._string.length; 674 var multiline_string = []; 675 var last_word = []; 676 677 var line = 1, i = 0, start_line = false, start_word = false, startOfLine = -1, startOfWord = -1, skip = 0; 678 679 var characterSprite; 680 for (var j = 0, lj = self._children.length; j < lj; j++) { 681 var justSkipped = 0; 682 while (!(characterSprite = self.getChildByTag(j + skip + justSkipped))) 683 justSkipped++; 684 skip += justSkipped; 685 686 if (i >= stringLength) 687 break; 688 689 var character = self._string[i]; 690 if (!start_word) { 691 startOfWord = self._getLetterPosXLeft(characterSprite); 692 start_word = true; 693 } 694 if (!start_line) { 695 startOfLine = startOfWord; 696 start_line = true; 697 } 698 699 // Newline. 700 if (character.charCodeAt(0) == 10) { 701 last_word.push('\n'); 702 multiline_string = multiline_string.concat(last_word); 703 last_word.length = 0; 704 start_word = false; 705 start_line = false; 706 startOfWord = -1; 707 startOfLine = -1; 708 //i+= justSkipped; 709 j--; 710 skip -= justSkipped; 711 line++; 712 713 if (i >= stringLength) 714 break; 715 716 character = self._string[i]; 717 if (!startOfWord) { 718 startOfWord = self._getLetterPosXLeft(characterSprite); 719 start_word = true; 720 } 721 if (!startOfLine) { 722 startOfLine = startOfWord; 723 start_line = true; 724 } 725 i++; 726 continue; 727 } 728 729 // Whitespace. 730 if (this._isspace_unicode(character)) { 731 last_word.push(character); 732 multiline_string = multiline_string.concat(last_word); 733 last_word.length = 0; 734 start_word = false; 735 startOfWord = -1; 736 i++; 737 continue; 738 } 739 740 // Out of bounds. 741 if (self._getLetterPosXRight(characterSprite) - startOfLine > self._width) { 742 if (!self._lineBreakWithoutSpaces) { 743 last_word.push(character); 744 745 var found = multiline_string.lastIndexOf(" "); 746 if (found != -1) 747 this._utf8_trim_ws(multiline_string); 748 else 749 multiline_string = []; 750 751 if (multiline_string.length > 0) 752 multiline_string.push('\n'); 753 754 line++; 755 start_line = false; 756 startOfLine = -1; 757 i++; 758 } else { 759 this._utf8_trim_ws(last_word); 760 761 last_word.push('\n'); 762 multiline_string = multiline_string.concat(last_word); 763 last_word.length = 0; 764 start_word = false; 765 start_line = false; 766 startOfWord = -1; 767 startOfLine = -1; 768 line++; 769 770 if (i >= stringLength) 771 break; 772 773 if (!startOfWord) { 774 startOfWord = self._getLetterPosXLeft(characterSprite); 775 start_word = true; 776 } 777 if (!startOfLine) { 778 startOfLine = startOfWord; 779 start_line = true; 780 } 781 j--; 782 } 783 } else { 784 // Character is normal. 785 last_word.push(character); 786 i++; 787 } 788 } 789 790 multiline_string = multiline_string.concat(last_word); 791 var len = multiline_string.length; 792 var str_new = ""; 793 794 for (i = 0; i < len; ++i) 795 str_new += multiline_string[i]; 796 797 str_new = str_new + String.fromCharCode(0); 798 //this.updateString(true); 799 self._setString(str_new, false) 800 } 801 802 // Step 2: Make alignment 803 if (self._alignment != cc.TEXT_ALIGNMENT_LEFT) { 804 i = 0; 805 806 var lineNumber = 0; 807 var strlen = self._string.length; 808 var last_line = []; 809 810 for (var ctr = 0; ctr < strlen; ctr++) { 811 if (self._string[ctr].charCodeAt(0) == 10 || self._string[ctr].charCodeAt(0) == 0) { 812 var lineWidth = 0; 813 var line_length = last_line.length; 814 // if last line is empty we must just increase lineNumber and work with next line 815 if (line_length == 0) { 816 lineNumber++; 817 continue; 818 } 819 var index = i + line_length - 1 + lineNumber; 820 if (index < 0) continue; 821 822 var lastChar = self.getChildByTag(index); 823 if (lastChar == null) 824 continue; 825 lineWidth = lastChar.getPositionX() + lastChar._getWidth() / 2; 826 827 var shift = 0; 828 switch (self._alignment) { 829 case cc.TEXT_ALIGNMENT_CENTER: 830 shift = self.width / 2 - lineWidth / 2; 831 break; 832 case cc.TEXT_ALIGNMENT_RIGHT: 833 shift = self.width - lineWidth; 834 break; 835 default: 836 break; 837 } 838 839 if (shift != 0) { 840 for (j = 0; j < line_length; j++) { 841 index = i + j + lineNumber; 842 if (index < 0) continue; 843 characterSprite = self.getChildByTag(index); 844 if (characterSprite) 845 characterSprite.x += shift; 846 } 847 } 848 849 i += line_length; 850 lineNumber++; 851 852 last_line.length = 0; 853 continue; 854 } 855 last_line.push(self._string[i]); 856 } 857 } 858 }, 859 860 /** 861 * Set text alignment. 862 * @param {Number} alignment 863 */ 864 setAlignment: function (alignment) { 865 this._alignment = alignment; 866 this.updateLabel(); 867 }, 868 869 _getAlignment: function () { 870 return this._alignment; 871 }, 872 873 /** 874 * Set the bounding width. <br /> 875 * max with display width. The exceeding string will be wrapping. 876 * @param {Number} width 877 */ 878 setBoundingWidth: function (width) { 879 this._width = width; 880 this.updateLabel(); 881 }, 882 883 _getBoundingWidth: function () { 884 return this._width; 885 }, 886 887 /** 888 * Set the param to change English word warp according to whether the space. <br /> 889 * default is false. 890 * @param {Boolean} breakWithoutSpace 891 */ 892 setLineBreakWithoutSpace: function (breakWithoutSpace) { 893 this._lineBreakWithoutSpaces = breakWithoutSpace; 894 this.updateLabel(); 895 }, 896 897 /** 898 * Set scale. <br /> 899 * Input a number, will be decrease or increase the font size. <br /> 900 * @param {Number} scale 901 * @param {Number} [scaleY=null] default is scale 902 */ 903 setScale: function (scale, scaleY) { 904 cc.Node.prototype.setScale.call(this, scale, scaleY); 905 this.updateLabel(); 906 }, 907 908 /** 909 * Set scale of x. <br /> 910 * Input a number, will be decrease or increase the font size. <br /> 911 * Horizontal scale. 912 * @param {Number} scaleX 913 */ 914 setScaleX: function (scaleX) { 915 cc.Node.prototype.setScaleX.call(this, scaleX); 916 this.updateLabel(); 917 }, 918 919 /** 920 * Set scale of x. <br /> 921 * Input a number, will be decrease or increase the font size. <br /> 922 * Longitudinal scale. 923 * @param {Number} scaleY 924 */ 925 setScaleY: function (scaleY) { 926 cc.Node.prototype.setScaleY.call(this, scaleY); 927 this.updateLabel(); 928 }, 929 930 //TODO 931 /** 932 * set fnt file path. <br /> 933 * Change the fnt file path. 934 * @param {String} fntFile 935 */ 936 setFntFile: function (fntFile) { 937 var self = this; 938 if (fntFile != null && fntFile != self._fntFile) { 939 var newConf = cc.loader.getRes(fntFile); 940 941 if (!newConf) { 942 cc.log("cc.LabelBMFont.setFntFile() : Impossible to create font. Please check file"); 943 return; 944 } 945 946 self._fntFile = fntFile; 947 self._config = newConf; 948 949 var texture = cc.textureCache.addImage(newConf.atlasName); 950 var locIsLoaded = texture.isLoaded(); 951 self._textureLoaded = locIsLoaded; 952 self.texture = texture; 953 if (cc._renderType === cc._RENDER_TYPE_CANVAS) 954 self._originalTexture = self.texture; 955 if (!locIsLoaded) { 956 texture.addLoadedEventListener(function (sender) { 957 var self1 = this; 958 self1._textureLoaded = true; 959 self1.texture = sender; 960 self1.createFontChars(); 961 self1._changeTextureColor(); 962 self1.updateLabel(); 963 self1._callLoadedEventCallbacks(); 964 }, self); 965 } else { 966 self.createFontChars(); 967 } 968 } 969 }, 970 971 /** 972 * Return the fnt file path. 973 * @return {String} 974 */ 975 getFntFile: function () { 976 return this._fntFile; 977 }, 978 979 /** 980 * Set the AnchorPoint of the labelBMFont. <br /> 981 * In order to change the location of label. 982 * @override 983 * @param {cc.Point|Number} point The anchor point of labelBMFont or The anchor point.x of labelBMFont. 984 * @param {Number} [y] The anchor point.y of labelBMFont. 985 */ 986 setAnchorPoint: function (point, y) { 987 cc.Node.prototype.setAnchorPoint.call(this, point, y); 988 this.updateLabel(); 989 }, 990 991 _setAnchor: function (p) { 992 cc.Node.prototype._setAnchor.call(this, p); 993 this.updateLabel(); 994 }, 995 996 _setAnchorX: function (x) { 997 cc.Node.prototype._setAnchorX.call(this, x); 998 this.updateLabel(); 999 }, 1000 1001 _setAnchorY: function (y) { 1002 cc.Node.prototype._setAnchorY.call(this, y); 1003 this.updateLabel(); 1004 }, 1005 1006 _atlasNameFromFntFile: function (fntFile) {}, 1007 1008 _kerningAmountForFirst: function (first, second) { 1009 var ret = 0; 1010 var key = (first << 16) | (second & 0xffff); 1011 if (this._configuration.kerningDictionary) { 1012 var element = this._configuration.kerningDictionary[key.toString()]; 1013 if (element) 1014 ret = element.amount; 1015 } 1016 return ret; 1017 }, 1018 1019 _getLetterPosXLeft: function (sp) { 1020 return sp.getPositionX() * this._scaleX - (sp._getWidth() * this._scaleX * sp._getAnchorX()); 1021 }, 1022 1023 _getLetterPosXRight: function (sp) { 1024 return sp.getPositionX() * this._scaleX + (sp._getWidth() * this._scaleX * sp._getAnchorX()); 1025 }, 1026 1027 //Checking whether the character is a whitespace 1028 _isspace_unicode: function(ch){ 1029 ch = ch.charCodeAt(0); 1030 return ((ch >= 9 && ch <= 13) || ch == 32 || ch == 133 || ch == 160 || ch == 5760 1031 || (ch >= 8192 && ch <= 8202) || ch == 8232 || ch == 8233 || ch == 8239 1032 || ch == 8287 || ch == 12288) 1033 }, 1034 1035 _utf8_trim_ws: function(str){ 1036 var len = str.length; 1037 1038 if (len <= 0) 1039 return; 1040 1041 var last_index = len - 1; 1042 1043 // Only start trimming if the last character is whitespace.. 1044 if (this._isspace_unicode(str[last_index])) { 1045 for (var i = last_index - 1; i >= 0; --i) { 1046 if (this._isspace_unicode(str[i])) { 1047 last_index = i; 1048 } 1049 else { 1050 break; 1051 } 1052 } 1053 this._utf8_trim_from(str, last_index); 1054 } 1055 }, 1056 1057 //Trims str st str=[0, index) after the operation. 1058 //Return value: the trimmed string. 1059 _utf8_trim_from: function(str, index){ 1060 var len = str.length; 1061 if (index >= len || index < 0) 1062 return; 1063 str.splice(index, len); 1064 } 1065 }); 1066 1067 var _p = cc.LabelBMFont.prototype; 1068 1069 if(cc._renderType === cc._RENDER_TYPE_CANVAS && !cc.sys._supportCanvasNewBlendModes) 1070 _p._changeTextureColor = function(){ 1071 if(cc._renderType == cc._RENDER_TYPE_WEBGL) 1072 return; 1073 var locElement, locTexture = this.getTexture(); 1074 if (locTexture && locTexture.getContentSize().width>0) { 1075 locElement = locTexture.getHtmlElementObj(); 1076 if (!locElement) 1077 return; 1078 var cacheTextureForColor = cc.textureCache.getTextureColors(this._originalTexture.getHtmlElementObj()); 1079 if (cacheTextureForColor) { 1080 if (locElement instanceof HTMLCanvasElement && !this._rectRotated) 1081 cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor, null, locElement); 1082 else{ 1083 locElement = cc.generateTintImage(locElement, cacheTextureForColor, this._displayedColor); 1084 locTexture = new cc.Texture2D(); 1085 locTexture.initWithElement(locElement); 1086 locTexture.handleLoadedTexture(); 1087 this.setTexture(locTexture); 1088 } 1089 } 1090 } 1091 }; 1092 1093 /** @expose */ 1094 _p.string; 1095 cc.defineGetterSetter(_p, "string", _p.getString, _p._setStringForSetter); 1096 /** @expose */ 1097 _p.boundingWidth; 1098 cc.defineGetterSetter(_p, "boundingWidth", _p._getBoundingWidth, _p.setBoundingWidth); 1099 /** @expose */ 1100 _p.textAlign; 1101 cc.defineGetterSetter(_p, "textAlign", _p._getAlignment, _p.setAlignment); 1102 1103 /** 1104 * creates a bitmap font atlas with an initial string and the FNT file 1105 * @deprecated since v3.0 please use new cc.LabelBMFont 1106 * @param {String} str 1107 * @param {String} fntFile 1108 * @param {Number} [width=-1] 1109 * @param {Number} [alignment=cc.TEXT_ALIGNMENT_LEFT] 1110 * @param {cc.Point} [imageOffset=cc.p(0,0)] 1111 * @return {cc.LabelBMFont|Null} 1112 * @example 1113 * // Example 01 1114 * var label1 = cc.LabelBMFont.create("Test case", "test.fnt"); 1115 * 1116 * // Example 02 1117 * var label2 = cc.LabelBMFont.create("test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT); 1118 * 1119 * // Example 03 1120 * var label3 = cc.LabelBMFont.create("This is a \n test case", "test.fnt", 200, cc.TEXT_ALIGNMENT_LEFT, cc.p(0,0)); 1121 */ 1122 cc.LabelBMFont.create = function (str, fntFile, width, alignment, imageOffset) { 1123 return new cc.LabelBMFont(str, fntFile, width, alignment, imageOffset); 1124 }; 1125 1126 cc._fntLoader = { 1127 INFO_EXP: /info [^\n]*(\n|$)/gi, 1128 COMMON_EXP: /common [^\n]*(\n|$)/gi, 1129 PAGE_EXP: /page [^\n]*(\n|$)/gi, 1130 CHAR_EXP: /char [^\n]*(\n|$)/gi, 1131 KERNING_EXP: /kerning [^\n]*(\n|$)/gi, 1132 ITEM_EXP: /\w+=[^ \r\n]+/gi, 1133 INT_EXP: /^[\-]?\d+$/, 1134 1135 _parseStrToObj: function (str) { 1136 var arr = str.match(this.ITEM_EXP); 1137 var obj = {}; 1138 if (arr) { 1139 for (var i = 0, li = arr.length; i < li; i++) { 1140 var tempStr = arr[i]; 1141 var index = tempStr.indexOf("="); 1142 var key = tempStr.substring(0, index); 1143 var value = tempStr.substring(index + 1); 1144 if (value.match(this.INT_EXP)) value = parseInt(value); 1145 else if (value[0] == '"') value = value.substring(1, value.length - 1); 1146 obj[key] = value; 1147 } 1148 } 1149 return obj; 1150 }, 1151 1152 /** 1153 * Parse Fnt string. 1154 * @param fntStr 1155 * @param url 1156 * @returns {{}} 1157 */ 1158 parseFnt: function (fntStr, url) { 1159 var self = this, fnt = {}; 1160 //padding 1161 var infoObj = self._parseStrToObj(fntStr.match(self.INFO_EXP)[0]); 1162 var paddingArr = infoObj["padding"].split(","); 1163 var padding = { 1164 left: parseInt(paddingArr[0]), 1165 top: parseInt(paddingArr[1]), 1166 right: parseInt(paddingArr[2]), 1167 bottom: parseInt(paddingArr[3]) 1168 }; 1169 1170 //common 1171 var commonObj = self._parseStrToObj(fntStr.match(self.COMMON_EXP)[0]); 1172 fnt.commonHeight = commonObj["lineHeight"]; 1173 if (cc._renderType === cc._RENDER_TYPE_WEBGL) { 1174 var texSize = cc.configuration.getMaxTextureSize(); 1175 if (commonObj["scaleW"] > texSize.width || commonObj["scaleH"] > texSize.height) 1176 cc.log("cc.LabelBMFont._parseCommonArguments(): page can't be larger than supported"); 1177 } 1178 if (commonObj["pages"] !== 1) cc.log("cc.LabelBMFont._parseCommonArguments(): only supports 1 page"); 1179 1180 //page 1181 var pageObj = self._parseStrToObj(fntStr.match(self.PAGE_EXP)[0]); 1182 if (pageObj["id"] !== 0) cc.log("cc.LabelBMFont._parseImageFileName() : file could not be found"); 1183 fnt.atlasName = cc.path.changeBasename(url, pageObj["file"]); 1184 1185 //char 1186 var charLines = fntStr.match(self.CHAR_EXP); 1187 var fontDefDictionary = fnt.fontDefDictionary = {}; 1188 for (var i = 0, li = charLines.length; i < li; i++) { 1189 var charObj = self._parseStrToObj(charLines[i]); 1190 var charId = charObj["id"]; 1191 fontDefDictionary[charId] = { 1192 rect: {x: charObj["x"], y: charObj["y"], width: charObj["width"], height: charObj["height"]}, 1193 xOffset: charObj["xoffset"], 1194 yOffset: charObj["yoffset"], 1195 xAdvance: charObj["xadvance"] 1196 }; 1197 } 1198 1199 //kerning 1200 var kerningDict = fnt.kerningDict = {}; 1201 var kerningLines = fntStr.match(self.KERNING_EXP); 1202 if (kerningLines) { 1203 for (var i = 0, li = kerningLines.length; i < li; i++) { 1204 var kerningObj = self._parseStrToObj(kerningLines[i]); 1205 kerningDict[(kerningObj["first"] << 16) | (kerningObj["second"] & 0xffff)] = kerningObj["amount"]; 1206 } 1207 } 1208 return fnt; 1209 }, 1210 1211 /** 1212 * load the fnt 1213 * @param realUrl 1214 * @param url 1215 * @param res 1216 * @param cb 1217 */ 1218 load: function (realUrl, url, res, cb) { 1219 var self = this; 1220 cc.loader.loadTxt(realUrl, function (err, txt) { 1221 if (err) return cb(err); 1222 cb(null, self.parseFnt(txt, url)); 1223 }); 1224 } 1225 }; 1226 cc.loader.register(["fnt"], cc._fntLoader); 1227