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