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