1 /** 2 * @fileOverview A jaws.Text object with word-wrapping functionality. 3 * @class jaws.Text 4 * @property {integer} x Horizontal position (0 = furthest left) 5 * @property {integer} y Vertical position (0 = top) 6 * @property {number} alpha Transparency: 0 (fully transparent) to 1 (no transparency) 7 * @property {number} angle Angle in degrees (0-360) 8 * @property {string} anchor String stating how to anchor the sprite to canvas; @see Sprite#anchor 9 * @property {string} text The actual text to be displayed 10 * @property {string} fontFace A valid font-family 11 * @property {number} fontSize The size of the text in pixels 12 * @property {string} textAlign "start", "end", "left", "right", or "center" 13 * @property {string} textBaseline "top", "bottom", "hanging", "middle", "alphabetic", or "ideographic" 14 * @property {number} width The width of the rect() containing the text 15 * @property {number} height The height of the rect() containing the text 16 * @property {string} style The style to draw the text: "normal", "bold" or italic" 17 * @property {boolean} wordWrap If word-wrapping should be attempted 18 * @property {string} shadowColor The color of the shadow for the text 19 * @property {number} shadowBlur The amount of shadow blur (length away from text) 20 * @property {number} shadowOffsetX The start of the shadow from initial x 21 * @property {number} shadowOffsetY The start of the shadow from initial y 22 * @example 23 * var text = new Text({text: "Hello world!", x: 10, y: 10}) 24 */ 25 26 var jaws = (function(jaws) { 27 28 /** 29 * jaws.Text constructor 30 * @constructor 31 * @param {object} options An object-literal collection of constructor values 32 */ 33 jaws.Text = function(options) { 34 if (!(this instanceof arguments.callee)) 35 return new arguments.callee(options); 36 37 this.set(options); 38 39 if (options.context) { 40 this.context = options.context; 41 } 42 43 if (!options.context) { // Defaults to jaws.context 44 if (jaws.context) 45 this.context = jaws.context; 46 } 47 }; 48 49 /** 50 * The default values of jaws.Text properties 51 */ 52 jaws.Text.prototype.default_options = { 53 x: 0, 54 y: 0, 55 alpha: 1, 56 angle: 0, 57 anchor_x: 0, 58 anchor_y: 0, 59 anchor: "top_left", 60 damping: 1, 61 style: "normal", 62 fontFace: "serif", 63 fontSize: 12, 64 color: "black", 65 textAlign: "start", 66 textBaseline: "alphabetic", 67 text: "", 68 wordWrap: false, 69 width: function(){ return jaws.width; }, 70 height: function() { return jaws.height; }, 71 shadowColor: null, 72 shadowBlur: null, 73 shadowOffsetX: null, 74 shadowOffsetY: null, 75 _constructor: null, 76 }; 77 78 /** 79 * Overrides constructor values with defaults 80 * @this {jaws.Text} 81 * @param {object} options An object-literal collection of constructor values 82 * @returns {this} 83 * @see jaws.parseOptions 84 */ 85 jaws.Text.prototype.set = function(options) { 86 87 jaws.parseOptions(this, options, this.default_options); 88 89 if (this.anchor) 90 this.setAnchor(this.anchor); 91 92 this.cacheOffsets(); 93 94 return this; 95 }; 96 97 /** 98 * Returns a new instance based on the current jaws.Text object 99 * @private 100 * @this {jaws.Text} 101 * @returns {object} The newly cloned object 102 */ 103 jaws.Text.prototype.clone = function() { 104 var constructor = this._constructor ? eval(this._constructor) : this.constructor; 105 var new_sprite = new constructor(this.attributes()); 106 new_sprite._constructor = this._constructor || this.constructor.name; 107 return new_sprite; 108 }; 109 110 /** 111 * Rotate sprite by value degrees 112 * @this {jaws.Text} 113 * @param {number} value The amount of the rotation 114 * @returns {this} Current function scope 115 */ 116 jaws.Text.prototype.rotate = function(value) { 117 this.angle += value; 118 return this; 119 }; 120 121 /** 122 * Forces a rotation-angle on sprite 123 * @this {jaws.Text} 124 * @param {number} value The amount of the rotation 125 * @returns {this} Current function instance 126 */ 127 jaws.Text.prototype.rotateTo = function(value) { 128 this.angle = value; 129 return this; 130 }; 131 132 /** 133 * Move object to position x, y 134 * @this {jaws.Text} 135 * @param {number} x The x position to move to 136 * @param {number} y The y position to move to 137 * @returns {this} Current function instance 138 */ 139 jaws.Text.prototype.moveTo = function(x, y) { 140 this.x = x; 141 this.y = y; 142 return this; 143 }; 144 145 /** 146 * Modify x and/or y by a fixed amount 147 * @this {jaws.Text} 148 * @param {type} x The additional amount to move x 149 * @param {type} y The additional amount to move y 150 * @returns {this} Current function instance 151 */ 152 jaws.Text.prototype.move = function(x, y) { 153 if (x) 154 this.x += x; 155 if (y) 156 this.y += y; 157 return this; 158 }; 159 160 /** 161 * Sets x 162 * @param {number} value The new x value 163 * @returns {this} The current function instance 164 */ 165 jaws.Text.prototype.setX = function(value) { 166 this.x = value; 167 return this; 168 }; 169 170 /** 171 * Sets y 172 * @param {number} value The new y value 173 * @returns {this} The current function instance 174 */ 175 jaws.Text.prototype.setY = function(value) { 176 this.y = value; 177 return this; 178 }; 179 180 /** 181 * Position sprites top on the y-axis 182 * @param {number} value 183 * @returns {this} The current function instance 184 */ 185 jaws.Text.prototype.setTop = function(value) { 186 this.y = value + this.top_offset; 187 return this; 188 }; 189 190 /** 191 * Position sprites bottom on the y-axis 192 * @param {number} value 193 * @returns {this} The current function instance 194 */ 195 jaws.Text.prototype.setBottom = function(value) { 196 this.y = value - this.bottom_offset; 197 return this; 198 }; 199 200 /** 201 * Position sprites left side on the x-axis 202 * @param {number} value 203 * @returns {this} The current function instance 204 */ 205 jaws.Text.prototype.setLeft = function(value) { 206 this.x = value + this.left_offset; 207 return this; 208 }; 209 210 /** 211 * Position sprites right side on the x-axis 212 * @param {number} value 213 * @returns {this} The current function instance 214 */ 215 jaws.Text.prototype.setRight = function(value) { 216 this.x = value - this.right_offset; 217 return this; 218 }; 219 220 /** 221 * Set new width. 222 * @param {number} value The new width 223 * @returns {this} 224 */ 225 jaws.Text.prototype.setWidth = function(value) { 226 this.width = value; 227 this.cacheOffsets(); 228 return this; 229 }; 230 231 /** 232 * Set new height. 233 * @param {number} value The new height 234 * @returns {this} 235 */ 236 jaws.Text.prototype.setHeight = function(value) { 237 this.height = value; 238 this.cacheOffsets(); 239 return this; 240 }; 241 242 /** 243 * Resize sprite by adding width or height 244 * @param {number} width 245 * @param {number} height 246 * @returns {this} 247 */ 248 jaws.Text.prototype.resize = function(width, height) { 249 this.width += width; 250 this.height += height; 251 this.cacheOffsets(); 252 return this; 253 }; 254 255 /** 256 * Resize sprite to exact width/height 257 * @this {jaws.Text} 258 * @param {number} width 259 * @param {number} height 260 * @returns {this} 261 */ 262 jaws.Text.prototype.resizeTo = function(width, height) { 263 this.width = width; 264 this.height = height; 265 this.cacheOffsets(); 266 return this; 267 }; 268 269 /** 270 * The anchor could be describe as "the part of the text will be placed at x/y" 271 * or "when rotating, what point of the of the text will it rotate round" 272 * @param {string} value 273 * @returns {this} The current function instance 274 * @example 275 * For example, a topdown shooter could use setAnchor("center") --> Place middle of the ship on x/y 276 * .. and a sidescroller would probably use setAnchor("center_bottom") --> Place "feet" at x/y 277 */ 278 jaws.Text.prototype.setAnchor = function(value) { 279 var anchors = { 280 top_left: [0, 0], 281 left_top: [0, 0], 282 center_left: [0, 0.5], 283 left_center: [0, 0.5], 284 bottom_left: [0, 1], 285 left_bottom: [0, 1], 286 top_center: [0.5, 0], 287 center_top: [0.5, 0], 288 center_center: [0.5, 0.5], 289 center: [0.5, 0.5], 290 bottom_center: [0.5, 1], 291 center_bottom: [0.5, 1], 292 top_right: [1, 0], 293 right_top: [1, 0], 294 center_right: [1, 0.5], 295 right_center: [1, 0.5], 296 bottom_right: [1, 1], 297 right_bottom: [1, 1] 298 }; 299 300 if (anchors.hasOwnProperty(value)) { 301 this.anchor_x = anchors[value][0]; 302 this.anchor_y = anchors[value][1]; 303 this.cacheOffsets(); 304 } 305 return this; 306 }; 307 308 /** 309 * Save the object's dimensions 310 * @private 311 * @returns {this} The current function instance 312 */ 313 jaws.Text.prototype.cacheOffsets = function() { 314 315 this.left_offset = this.width * this.anchor_x; 316 this.top_offset = this.height * this.anchor_y; 317 this.right_offset = this.width * (1.0 - this.anchor_x); 318 this.bottom_offset = this.height * (1.0 - this.anchor_y); 319 320 if (this.cached_rect) 321 this.cached_rect.resizeTo(this.width, this.height); 322 return this; 323 }; 324 325 /** 326 * Returns a jaws.Rect() perfectly surrouning text. 327 * @returns {jaws.Rect} 328 */ 329 jaws.Text.prototype.rect = function() { 330 if (!this.cached_rect && this.width) 331 this.cached_rect = new jaws.Rect(this.x, this.y, this.width, this.height); 332 if (this.cached_rect) 333 this.cached_rect.moveTo(this.x - this.left_offset, this.y - this.top_offset); 334 return this.cached_rect; 335 }; 336 337 /** 338 * Draw sprite on active canvas or update its DOM-properties 339 * @this {jaws.Text} 340 * @returns {this} The current function instance 341 */ 342 jaws.Text.prototype.draw = function() { 343 this.context.save(); 344 if (this.angle !== 0) { 345 this.context.rotate(this.angle * Math.PI / 180); 346 } 347 this.context.globalAlpha = this.alpha; 348 this.context.translate(-this.left_offset, -this.top_offset); // Needs to be separate from above translate call cause of flipped 349 this.context.fillStyle = this.color; 350 this.context.font = this.style + " " + this.fontSize + "px " + this.fontFace; 351 this.context.textBaseline = this.textBaseline; 352 this.context.textAlign = this.textAlign; 353 if (this.shadowColor) 354 this.context.shadowColor = this.shadowColor; 355 if (this.shadowBlur) 356 this.context.shadowBlur = this.shadowBlur; 357 if (this.shadowOffsetX) 358 this.context.shadowOffsetX = this.shadowOffsetX; 359 if (this.shadowOffsetY) 360 this.context.shadowOffsetY = this.shadowOffsetY; 361 var oldY = this.y; 362 var oldX = this.x; 363 if (this.wordWrap) 364 { 365 var words = this.text.split(' '); 366 var nextLine = ''; 367 368 for (var n = 0; n < words.length; n++) 369 { 370 var testLine = nextLine + words[n] + ' '; 371 var measurement = this.context.measureText(testLine); 372 if (this.y < oldY + this.height) 373 { 374 if (measurement.width > this.width) 375 { 376 this.context.fillText(nextLine, this.x, this.y); 377 nextLine = words[n] + ' '; 378 this.y += this.fontSize; 379 } 380 else { 381 nextLine = testLine; 382 } 383 this.context.fillText(nextLine, this.x, this.y); 384 } 385 } 386 } 387 else 388 { 389 if (this.context.measureText(this.text).width < this.width) 390 { 391 this.context.fillText(this.text, this.x, this.y); 392 } 393 else 394 { 395 var words = this.text.split(' '); 396 var nextLine = ' '; 397 for (var n = 0; n < words.length; n++) 398 { 399 var testLine = nextLine + words[n] + ' '; 400 if (this.context.measureText(testLine).width < Math.abs(this.width - this.x)) 401 { 402 this.context.fillText(testLine, this.x, this.y); 403 nextLine = words[n] + ' '; 404 nextLine = testLine; 405 } 406 } 407 } 408 } 409 this.y = oldY; 410 this.x = oldX; 411 this.context.restore(); 412 return this; 413 }; 414 415 /** 416 * Returns sprite as a canvas context. 417 * (For certain browsers, a canvas context is faster to work with then a pure image.) 418 * @public 419 * @this {jaws.Text} 420 */ 421 jaws.Text.prototype.asCanvasContext = function() { 422 var canvas = document.createElement("canvas"); 423 canvas.width = this.width; 424 canvas.height = this.height; 425 426 var context = canvas.getContext("2d"); 427 context.mozImageSmoothingEnabled = jaws.context.mozImageSmoothingEnabled; 428 429 this.context.fillStyle = this.color; 430 this.context.font = this.style + this.fontSize + "px " + this.fontFace; 431 this.context.textBaseline = this.textBaseline; 432 this.context.textAlign = this.textAlign; 433 if (this.shadowColor) 434 this.context.shadowColor = this.shadowColor; 435 if (this.shadowBlur) 436 this.context.shadowBlur = this.shadowBlur; 437 if (this.shadowOffsetX) 438 this.context.shadowOffsetX = this.shadowOffsetX; 439 if (this.shadowOffsetY) 440 this.context.shadowOffsetY = this.shadowOffsetY; 441 var oldY = this.y; 442 var oldX = this.x; 443 if (this.wordWrap) 444 { 445 var words = this.text.split(' '); 446 var nextLine = ''; 447 448 for (var n = 0; n < words.length; n++) 449 { 450 var testLine = nextLine + words[n] + ' '; 451 var measurement = this.context.measureText(testLine); 452 if (this.y < oldY + this.height) 453 { 454 if (measurement.width > this.width) 455 { 456 this.context.fillText(nextLine, this.x, this.y); 457 nextLine = words[n] + ' '; 458 this.y += this.fontSize; 459 } 460 else { 461 nextLine = testLine; 462 } 463 this.context.fillText(nextLine, this.x, this.y); 464 } 465 } 466 } 467 else 468 { 469 if (this.context.measureText(this.text).width < this.width) 470 { 471 this.context.fillText(this.text, this.x, this.y); 472 } 473 else 474 { 475 var words = this.text.split(' '); 476 var nextLine = ' '; 477 for (var n = 0; n < words.length; n++) 478 { 479 var testLine = nextLine + words[n] + ' '; 480 if (this.context.measureText(testLine).width < Math.abs(this.width - this.x)) 481 { 482 this.context.fillText(testLine, this.x, this.y); 483 nextLine = words[n] + ' '; 484 nextLine = testLine; 485 } 486 } 487 } 488 } 489 this.y = oldY; 490 this.x = oldX; 491 return context; 492 }; 493 494 /** 495 * Returns text as a canvas 496 * @this {jaws.Text} 497 */ 498 jaws.Text.prototype.asCanvas = function() { 499 var canvas = document.createElement("canvas"); 500 canvas.width = this.width; 501 canvas.height = this.height; 502 503 var context = canvas.getContext("2d"); 504 context.mozImageSmoothingEnabled = jaws.context.mozImageSmoothingEnabled; 505 506 this.context.fillStyle = this.color; 507 this.context.font = this.style + this.fontSize + "px " + this.fontFace; 508 this.context.textBaseline = this.textBaseline; 509 this.context.textAlign = this.textAlign; 510 if (this.shadowColor) 511 this.context.shadowColor = this.shadowColor; 512 if (this.shadowBlur) 513 this.context.shadowBlur = this.shadowBlur; 514 if (this.shadowOffsetX) 515 this.context.shadowOffsetX = this.shadowOffsetX; 516 if (this.shadowOffsetY) 517 this.context.shadowOffsetY = this.shadowOffsetY; 518 var oldY = this.y; 519 var oldX = this.x; 520 if (this.wordWrap) 521 { 522 var words = this.text.split(' '); 523 var nextLine = ''; 524 525 for (var n = 0; n < words.length; n++) 526 { 527 var testLine = nextLine + words[n] + ' '; 528 var measurement = context.measureText(testLine); 529 if (this.y < oldY + this.height) 530 { 531 if (measurement.width > this.width) 532 { 533 context.fillText(nextLine, this.x, this.y); 534 nextLine = words[n] + ' '; 535 this.y += this.fontSize; 536 } 537 else { 538 nextLine = testLine; 539 } 540 context.fillText(nextLine, this.x, this.y); 541 } 542 } 543 } 544 else 545 { 546 if (context.measureText(this.text).width < this.width) 547 { 548 this.context.fillText(this.text, this.x, this.y); 549 } 550 else 551 { 552 var words = this.text.split(' '); 553 var nextLine = ' '; 554 for (var n = 0; n < words.length; n++) 555 { 556 var testLine = nextLine + words[n] + ' '; 557 if (context.measureText(testLine).width < Math.abs(this.width - this.x)) 558 { 559 context.fillText(testLine, this.x, this.y); 560 nextLine = words[n] + ' '; 561 nextLine = testLine; 562 } 563 } 564 } 565 } 566 this.y = oldY; 567 this.x = oldX; 568 return canvas; 569 }; 570 571 /** 572 * Returns Text's properties as a String 573 * @returns {string} 574 */ 575 jaws.Text.prototype.toString = function() { 576 return "[Text " + this.x.toFixed(2) + ", " + this.y.toFixed(2) + ", " + this.width + ", " + this.height + "]"; 577 }; 578 579 /** 580 * Returns Text's properties as a pure object 581 * @returns {object} 582 */ 583 jaws.Text.prototype.attributes = function() { 584 var object = this.options; // Start with all creation time properties 585 object["_constructor"] = this._constructor || "jaws.Text"; 586 object["x"] = parseFloat(this.x.toFixed(2)); 587 object["y"] = parseFloat(this.y.toFixed(2)); 588 object["text"] = this.text; 589 object["alpha"] = this.alpha; 590 object["angle"] = parseFloat(this.angle.toFixed(2)); 591 object["anchor_x"] = this.anchor_x; 592 object["anchor_y"] = this.anchor_y; 593 object["style"] = this.style; 594 object["fontSize"] = this.fontSize; 595 object["fontFace"] = this.fontFace; 596 object["color"] = this.color; 597 object["textAlign"] = this.textAlign; 598 object["textBaseline"] = this.textBaseline; 599 object["wordWrap"] = this.wordWrap; 600 object["width"] = this.width; 601 object["height"] = this.height; 602 return object; 603 }; 604 605 /** 606 * Returns a JSON-string representing the properties of the Text. 607 * @returns {string} 608 */ 609 jaws.Text.prototype.toJSON = function() { 610 return JSON.stringify(this.attributes()); 611 }; 612 613 return jaws; 614 })(jaws || {}); 615 616 // Support CommonJS require() 617 if (typeof module !== "undefined" && ('exports' in module)) { 618 module.exports = jaws.Text; 619 } 620