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