1/* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */ 2/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ 3 4var fabric = fabric || { version: "1.5.0" }; 5if (typeof exports !== 'undefined') { 6 exports.fabric = fabric; 7} 8 9if (typeof document !== 'undefined' && typeof window !== 'undefined') { 10 fabric.document = document; 11 fabric.window = window; 12 // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system) 13 window.fabric = fabric; 14} 15else { 16 // assume we're running under node.js when document/window are not present 17 fabric.document = require("jsdom") 18 .jsdom("<!DOCTYPE html><html><head></head><body></body></html>"); 19 20 if (fabric.document.createWindow) { 21 fabric.window = fabric.document.createWindow(); 22 } else { 23 fabric.window = fabric.document.parentWindow; 24 } 25} 26 27/** 28 * True when in environment that supports touch events 29 * @type boolean 30 */ 31fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement; 32 33/** 34 * True when in environment that's probably Node.js 35 * @type boolean 36 */ 37fabric.isLikelyNode = typeof Buffer !== 'undefined' && 38 typeof window === 'undefined'; 39 40/* _FROM_SVG_START_ */ 41/** 42 * Attributes parsed from all SVG elements 43 * @type array 44 */ 45fabric.SHARED_ATTRIBUTES = [ 46 "display", 47 "transform", 48 "fill", "fill-opacity", "fill-rule", 49 "opacity", 50 "stroke", "stroke-dasharray", "stroke-linecap", 51 "stroke-linejoin", "stroke-miterlimit", 52 "stroke-opacity", "stroke-width" 53]; 54/* _FROM_SVG_END_ */ 55 56/** 57 * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. 58 */ 59fabric.DPI = 96; 60fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)'; 61 62 63(function() { 64 65 /** 66 * @private 67 * @param {String} eventName 68 * @param {Function} handler 69 */ 70 function _removeEventListener(eventName, handler) { 71 if (!this.__eventListeners[eventName]) { 72 return; 73 } 74 75 if (handler) { 76 fabric.util.removeFromArray(this.__eventListeners[eventName], handler); 77 } 78 else { 79 this.__eventListeners[eventName].length = 0; 80 } 81 } 82 83 /** 84 * Observes specified event 85 * @deprecated `observe` deprecated since 0.8.34 (use `on` instead) 86 * @memberOf fabric.Observable 87 * @alias on 88 * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) 89 * @param {Function} handler Function that receives a notification when an event of the specified type occurs 90 * @return {Self} thisArg 91 * @chainable 92 */ 93 function observe(eventName, handler) { 94 if (!this.__eventListeners) { 95 this.__eventListeners = { }; 96 } 97 // one object with key/value pairs was passed 98 if (arguments.length === 1) { 99 for (var prop in eventName) { 100 this.on(prop, eventName[prop]); 101 } 102 } 103 else { 104 if (!this.__eventListeners[eventName]) { 105 this.__eventListeners[eventName] = [ ]; 106 } 107 this.__eventListeners[eventName].push(handler); 108 } 109 return this; 110 } 111 112 /** 113 * Stops event observing for a particular event handler. Calling this method 114 * without arguments removes all handlers for all events 115 * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead) 116 * @memberOf fabric.Observable 117 * @alias off 118 * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) 119 * @param {Function} handler Function to be deleted from EventListeners 120 * @return {Self} thisArg 121 * @chainable 122 */ 123 function stopObserving(eventName, handler) { 124 if (!this.__eventListeners) { 125 return; 126 } 127 128 // remove all key/value pairs (event name -> event handler) 129 if (arguments.length === 0) { 130 this.__eventListeners = { }; 131 } 132 // one object with key/value pairs was passed 133 else if (arguments.length === 1 && typeof arguments[0] === 'object') { 134 for (var prop in eventName) { 135 _removeEventListener.call(this, prop, eventName[prop]); 136 } 137 } 138 else { 139 _removeEventListener.call(this, eventName, handler); 140 } 141 return this; 142 } 143 144 /** 145 * Fires event with an optional options object 146 * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead) 147 * @memberOf fabric.Observable 148 * @alias trigger 149 * @param {String} eventName Event name to fire 150 * @param {Object} [options] Options object 151 * @return {Self} thisArg 152 * @chainable 153 */ 154 function fire(eventName, options) { 155 if (!this.__eventListeners) { 156 return; 157 } 158 159 var listenersForEvent = this.__eventListeners[eventName]; 160 if (!listenersForEvent) { 161 return; 162 } 163 164 for (var i = 0, len = listenersForEvent.length; i < len; i++) { 165 // avoiding try/catch for perf. reasons 166 listenersForEvent[i].call(this, options || { }); 167 } 168 return this; 169 } 170 171 /** 172 * @namespace fabric.Observable 173 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events} 174 * @see {@link http://fabricjs.com/events/|Events demo} 175 */ 176 fabric.Observable = { 177 observe: observe, 178 stopObserving: stopObserving, 179 fire: fire, 180 181 on: observe, 182 off: stopObserving, 183 trigger: fire 184 }; 185})(); 186 187 188/** 189 * @namespace fabric.Collection 190 */ 191fabric.Collection = { 192 193 /** 194 * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`) 195 * Objects should be instances of (or inherit from) fabric.Object 196 * @param {...fabric.Object} object Zero or more fabric instances 197 * @return {Self} thisArg 198 */ 199 add: function () { 200 this._objects.push.apply(this._objects, arguments); 201 for (var i = 0, length = arguments.length; i < length; i++) { 202 this._onObjectAdded(arguments[i]); 203 } 204 this.renderOnAddRemove && this.renderAll(); 205 return this; 206 }, 207 208 /** 209 * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) 210 * An object should be an instance of (or inherit from) fabric.Object 211 * @param {Object} object Object to insert 212 * @param {Number} index Index to insert object at 213 * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs 214 * @return {Self} thisArg 215 * @chainable 216 */ 217 insertAt: function (object, index, nonSplicing) { 218 var objects = this.getObjects(); 219 if (nonSplicing) { 220 objects[index] = object; 221 } 222 else { 223 objects.splice(index, 0, object); 224 } 225 this._onObjectAdded(object); 226 this.renderOnAddRemove && this.renderAll(); 227 return this; 228 }, 229 230 /** 231 * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) 232 * @param {...fabric.Object} object Zero or more fabric instances 233 * @return {Self} thisArg 234 * @chainable 235 */ 236 remove: function() { 237 var objects = this.getObjects(), 238 index; 239 240 for (var i = 0, length = arguments.length; i < length; i++) { 241 index = objects.indexOf(arguments[i]); 242 243 // only call onObjectRemoved if an object was actually removed 244 if (index !== -1) { 245 objects.splice(index, 1); 246 this._onObjectRemoved(arguments[i]); 247 } 248 } 249 250 this.renderOnAddRemove && this.renderAll(); 251 return this; 252 }, 253 254 /** 255 * Executes given function for each object in this group 256 * @param {Function} callback 257 * Callback invoked with current object as first argument, 258 * index - as second and an array of all objects - as third. 259 * Iteration happens in reverse order (for performance reasons). 260 * Callback is invoked in a context of Global Object (e.g. `window`) 261 * when no `context` argument is given 262 * 263 * @param {Object} context Context (aka thisObject) 264 * @return {Self} thisArg 265 */ 266 forEachObject: function(callback, context) { 267 var objects = this.getObjects(), 268 i = objects.length; 269 while (i--) { 270 callback.call(context, objects[i], i, objects); 271 } 272 return this; 273 }, 274 275 /** 276 * Returns an array of children objects of this instance 277 * Type parameter introduced in 1.3.10 278 * @param {String} [type] When specified, only objects of this type are returned 279 * @return {Array} 280 */ 281 getObjects: function(type) { 282 if (typeof type === 'undefined') { 283 return this._objects; 284 } 285 return this._objects.filter(function(o) { 286 return o.type === type; 287 }); 288 }, 289 290 /** 291 * Returns object at specified index 292 * @param {Number} index 293 * @return {Self} thisArg 294 */ 295 item: function (index) { 296 return this.getObjects()[index]; 297 }, 298 299 /** 300 * Returns true if collection contains no objects 301 * @return {Boolean} true if collection is empty 302 */ 303 isEmpty: function () { 304 return this.getObjects().length === 0; 305 }, 306 307 /** 308 * Returns a size of a collection (i.e: length of an array containing its objects) 309 * @return {Number} Collection size 310 */ 311 size: function() { 312 return this.getObjects().length; 313 }, 314 315 /** 316 * Returns true if collection contains an object 317 * @param {Object} object Object to check against 318 * @return {Boolean} `true` if collection contains an object 319 */ 320 contains: function(object) { 321 return this.getObjects().indexOf(object) > -1; 322 }, 323 324 /** 325 * Returns number representation of a collection complexity 326 * @return {Number} complexity 327 */ 328 complexity: function () { 329 return this.getObjects().reduce(function (memo, current) { 330 memo += current.complexity ? current.complexity() : 0; 331 return memo; 332 }, 0); 333 } 334}; 335 336 337(function(global) { 338 339 var sqrt = Math.sqrt, 340 atan2 = Math.atan2, 341 PiBy180 = Math.PI / 180; 342 343 /** 344 * @namespace fabric.util 345 */ 346 fabric.util = { 347 348 /** 349 * Removes value from an array. 350 * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` 351 * @static 352 * @memberOf fabric.util 353 * @param {Array} array 354 * @param {Any} value 355 * @return {Array} original array 356 */ 357 removeFromArray: function(array, value) { 358 var idx = array.indexOf(value); 359 if (idx !== -1) { 360 array.splice(idx, 1); 361 } 362 return array; 363 }, 364 365 /** 366 * Returns random number between 2 specified ones. 367 * @static 368 * @memberOf fabric.util 369 * @param {Number} min lower limit 370 * @param {Number} max upper limit 371 * @return {Number} random value (between min and max) 372 */ 373 getRandomInt: function(min, max) { 374 return Math.floor(Math.random() * (max - min + 1)) + min; 375 }, 376 377 /** 378 * Transforms degrees to radians. 379 * @static 380 * @memberOf fabric.util 381 * @param {Number} degrees value in degrees 382 * @return {Number} value in radians 383 */ 384 degreesToRadians: function(degrees) { 385 return degrees * PiBy180; 386 }, 387 388 /** 389 * Transforms radians to degrees. 390 * @static 391 * @memberOf fabric.util 392 * @param {Number} radians value in radians 393 * @return {Number} value in degrees 394 */ 395 radiansToDegrees: function(radians) { 396 return radians / PiBy180; 397 }, 398 399 /** 400 * Rotates `point` around `origin` with `radians` 401 * @static 402 * @memberOf fabric.util 403 * @param {fabric.Point} point The point to rotate 404 * @param {fabric.Point} origin The origin of the rotation 405 * @param {Number} radians The radians of the angle for the rotation 406 * @return {fabric.Point} The new rotated point 407 */ 408 rotatePoint: function(point, origin, radians) { 409 var sin = Math.sin(radians), 410 cos = Math.cos(radians); 411 412 point.subtractEquals(origin); 413 414 var rx = point.x * cos - point.y * sin, 415 ry = point.x * sin + point.y * cos; 416 417 return new fabric.Point(rx, ry).addEquals(origin); 418 }, 419 420 /** 421 * Apply transform t to point p 422 * @static 423 * @memberOf fabric.util 424 * @param {fabric.Point} p The point to transform 425 * @param {Array} t The transform 426 * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied 427 * @return {fabric.Point} The transformed point 428 */ 429 transformPoint: function(p, t, ignoreOffset) { 430 if (ignoreOffset) { 431 return new fabric.Point( 432 t[0] * p.x + t[2] * p.y, 433 t[1] * p.x + t[3] * p.y 434 ); 435 } 436 return new fabric.Point( 437 t[0] * p.x + t[2] * p.y + t[4], 438 t[1] * p.x + t[3] * p.y + t[5] 439 ); 440 }, 441 442 /** 443 * Invert transformation t 444 * @static 445 * @memberOf fabric.util 446 * @param {Array} t The transform 447 * @return {Array} The inverted transform 448 */ 449 invertTransform: function(t) { 450 var r = t.slice(), 451 a = 1 / (t[0] * t[3] - t[1] * t[2]); 452 r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; 453 var o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r); 454 r[4] = -o.x; 455 r[5] = -o.y; 456 return r; 457 }, 458 459 /** 460 * A wrapper around Number#toFixed, which contrary to native method returns number, not string. 461 * @static 462 * @memberOf fabric.util 463 * @param {Number|String} number number to operate on 464 * @param {Number} fractionDigits number of fraction digits to "leave" 465 * @return {Number} 466 */ 467 toFixed: function(number, fractionDigits) { 468 return parseFloat(Number(number).toFixed(fractionDigits)); 469 }, 470 471 /** 472 * Converts from attribute value to pixel value if applicable. 473 * Returns converted pixels or original value not converted. 474 * @param {Number|String} value number to operate on 475 * @return {Number|String} 476 */ 477 parseUnit: function(value, fontSize) { 478 var unit = /\D{0,2}$/.exec(value), 479 number = parseFloat(value); 480 if (!fontSize) { 481 fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; 482 } 483 switch (unit[0]) { 484 case 'mm': 485 return number * fabric.DPI / 25.4; 486 487 case 'cm': 488 return number * fabric.DPI / 2.54; 489 490 case 'in': 491 return number * fabric.DPI; 492 493 case 'pt': 494 return number * fabric.DPI / 72; // or * 4 / 3 495 496 case 'pc': 497 return number * fabric.DPI / 72 * 12; // or * 16 498 499 case 'em': 500 return number * fontSize; 501 502 default: 503 return number; 504 } 505 }, 506 507 /** 508 * Function which always returns `false`. 509 * @static 510 * @memberOf fabric.util 511 * @return {Boolean} 512 */ 513 falseFunction: function() { 514 return false; 515 }, 516 517 /** 518 * Returns klass "Class" object of given namespace 519 * @memberOf fabric.util 520 * @param {String} type Type of object (eg. 'circle') 521 * @param {String} namespace Namespace to get klass "Class" object from 522 * @return {Object} klass "Class" 523 */ 524 getKlass: function(type, namespace) { 525 // capitalize first letter only 526 type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); 527 return fabric.util.resolveNamespace(namespace)[type]; 528 }, 529 530 /** 531 * Returns object of given namespace 532 * @memberOf fabric.util 533 * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' 534 * @return {Object} Object for given namespace (default fabric) 535 */ 536 resolveNamespace: function(namespace) { 537 if (!namespace) { 538 return fabric; 539 } 540 541 var parts = namespace.split('.'), 542 len = parts.length, 543 obj = global || fabric.window; 544 545 for (var i = 0; i < len; ++i) { 546 obj = obj[parts[i]]; 547 } 548 549 return obj; 550 }, 551 552 /** 553 * Loads image element from given url and passes it to a callback 554 * @memberOf fabric.util 555 * @param {String} url URL representing an image 556 * @param {Function} callback Callback; invoked with loaded image 557 * @param {Any} [context] Context to invoke callback in 558 * @param {Object} [crossOrigin] crossOrigin value to set image element to 559 */ 560 loadImage: function(url, callback, context, crossOrigin) { 561 if (!url) { 562 callback && callback.call(context, url); 563 return; 564 } 565 566 var img = fabric.util.createImage(); 567 568 /** @ignore */ 569 img.onload = function () { 570 callback && callback.call(context, img); 571 img = img.onload = img.onerror = null; 572 }; 573 574 /** @ignore */ 575 img.onerror = function() { 576 fabric.log('Error loading ' + img.src); 577 callback && callback.call(context, null, true); 578 img = img.onload = img.onerror = null; 579 }; 580 581 // data-urls appear to be buggy with crossOrigin 582 // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 583 // see https://code.google.com/p/chromium/issues/detail?id=315152 584 // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 585 if (url.indexOf('data') !== 0 && typeof crossOrigin !== 'undefined') { 586 img.crossOrigin = crossOrigin; 587 } 588 589 img.src = url; 590 }, 591 592 /** 593 * Creates corresponding fabric instances from their object representations 594 * @static 595 * @memberOf fabric.util 596 * @param {Array} objects Objects to enliven 597 * @param {Function} callback Callback to invoke when all objects are created 598 * @param {String} namespace Namespace to get klass "Class" object from 599 * @param {Function} reviver Method for further parsing of object elements, 600 * called after each fabric object created. 601 */ 602 enlivenObjects: function(objects, callback, namespace, reviver) { 603 objects = objects || [ ]; 604 605 function onLoaded() { 606 if (++numLoadedObjects === numTotalObjects) { 607 callback && callback(enlivenedObjects); 608 } 609 } 610 611 var enlivenedObjects = [ ], 612 numLoadedObjects = 0, 613 numTotalObjects = objects.length; 614 615 if (!numTotalObjects) { 616 callback && callback(enlivenedObjects); 617 return; 618 } 619 620 objects.forEach(function (o, index) { 621 // if sparse array 622 if (!o || !o.type) { 623 onLoaded(); 624 return; 625 } 626 var klass = fabric.util.getKlass(o.type, namespace); 627 if (klass.async) { 628 klass.fromObject(o, function (obj, error) { 629 if (!error) { 630 enlivenedObjects[index] = obj; 631 reviver && reviver(o, enlivenedObjects[index]); 632 } 633 onLoaded(); 634 }); 635 } 636 else { 637 enlivenedObjects[index] = klass.fromObject(o); 638 reviver && reviver(o, enlivenedObjects[index]); 639 onLoaded(); 640 } 641 }); 642 }, 643 644 /** 645 * Groups SVG elements (usually those retrieved from SVG document) 646 * @static 647 * @memberOf fabric.util 648 * @param {Array} elements SVG elements to group 649 * @param {Object} [options] Options object 650 * @return {fabric.Object|fabric.PathGroup} 651 */ 652 groupSVGElements: function(elements, options, path) { 653 var object; 654 655 object = new fabric.PathGroup(elements, options); 656 657 if (typeof path !== 'undefined') { 658 object.setSourcePath(path); 659 } 660 return object; 661 }, 662 663 /** 664 * Populates an object with properties of another object 665 * @static 666 * @memberOf fabric.util 667 * @param {Object} source Source object 668 * @param {Object} destination Destination object 669 * @return {Array} properties Propertie names to include 670 */ 671 populateWithProperties: function(source, destination, properties) { 672 if (properties && Object.prototype.toString.call(properties) === '[object Array]') { 673 for (var i = 0, len = properties.length; i < len; i++) { 674 if (properties[i] in source) { 675 destination[properties[i]] = source[properties[i]]; 676 } 677 } 678 } 679 }, 680 681 /** 682 * Draws a dashed line between two points 683 * 684 * This method is used to draw dashed line around selection area. 685 * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a> 686 * 687 * @param {CanvasRenderingContext2D} ctx context 688 * @param {Number} x start x coordinate 689 * @param {Number} y start y coordinate 690 * @param {Number} x2 end x coordinate 691 * @param {Number} y2 end y coordinate 692 * @param {Array} da dash array pattern 693 */ 694 drawDashedLine: function(ctx, x, y, x2, y2, da) { 695 var dx = x2 - x, 696 dy = y2 - y, 697 len = sqrt(dx * dx + dy * dy), 698 rot = atan2(dy, dx), 699 dc = da.length, 700 di = 0, 701 draw = true; 702 703 ctx.save(); 704 ctx.translate(x, y); 705 ctx.moveTo(0, 0); 706 ctx.rotate(rot); 707 708 x = 0; 709 while (len > x) { 710 x += da[di++ % dc]; 711 if (x > len) { 712 x = len; 713 } 714 ctx[draw ? 'lineTo' : 'moveTo'](x, 0); 715 draw = !draw; 716 } 717 718 ctx.restore(); 719 }, 720 721 /** 722 * Creates canvas element and initializes it via excanvas if necessary 723 * @static 724 * @memberOf fabric.util 725 * @param {CanvasElement} [canvasEl] optional canvas element to initialize; 726 * when not given, element is created implicitly 727 * @return {CanvasElement} initialized canvas element 728 */ 729 createCanvasElement: function(canvasEl) { 730 canvasEl || (canvasEl = fabric.document.createElement('canvas')); 731 //jscs:disable requireCamelCaseOrUpperCaseIdentifiers 732 if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { 733 G_vmlCanvasManager.initElement(canvasEl); 734 } 735 //jscs:enable requireCamelCaseOrUpperCaseIdentifiers 736 return canvasEl; 737 }, 738 739 /** 740 * Creates image element (works on client and node) 741 * @static 742 * @memberOf fabric.util 743 * @return {HTMLImageElement} HTML image element 744 */ 745 createImage: function() { 746 return fabric.isLikelyNode 747 ? new (require('canvas').Image)() 748 : fabric.document.createElement('img'); 749 }, 750 751 /** 752 * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array 753 * @static 754 * @memberOf fabric.util 755 * @param {Object} klass "Class" to create accessors for 756 */ 757 createAccessors: function(klass) { 758 var proto = klass.prototype; 759 760 for (var i = proto.stateProperties.length; i--; ) { 761 762 var propName = proto.stateProperties[i], 763 capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1), 764 setterName = 'set' + capitalizedPropName, 765 getterName = 'get' + capitalizedPropName; 766 767 // using `new Function` for better introspection 768 if (!proto[getterName]) { 769 proto[getterName] = (function(property) { 770 return new Function('return this.get("' + property + '")'); 771 })(propName); 772 } 773 if (!proto[setterName]) { 774 proto[setterName] = (function(property) { 775 return new Function('value', 'return this.set("' + property + '", value)'); 776 })(propName); 777 } 778 } 779 }, 780 781 /** 782 * @static 783 * @memberOf fabric.util 784 * @param {fabric.Object} receiver Object implementing `clipTo` method 785 * @param {CanvasRenderingContext2D} ctx Context to clip 786 */ 787 clipContext: function(receiver, ctx) { 788 ctx.save(); 789 ctx.beginPath(); 790 receiver.clipTo(ctx); 791 ctx.clip(); 792 }, 793 794 /** 795 * Multiply matrix A by matrix B to nest transformations 796 * @static 797 * @memberOf fabric.util 798 * @param {Array} a First transformMatrix 799 * @param {Array} b Second transformMatrix 800 * @return {Array} The product of the two transform matrices 801 */ 802 multiplyTransformMatrices: function(a, b) { 803 // Matrix multiply a * b 804 return [ 805 a[0] * b[0] + a[2] * b[1], 806 a[1] * b[0] + a[3] * b[1], 807 a[0] * b[2] + a[2] * b[3], 808 a[1] * b[2] + a[3] * b[3], 809 a[0] * b[4] + a[2] * b[5] + a[4], 810 a[1] * b[4] + a[3] * b[5] + a[5] 811 ]; 812 }, 813 814 /** 815 * Returns string representation of function body 816 * @param {Function} fn Function to get body of 817 * @return {String} Function body 818 */ 819 getFunctionBody: function(fn) { 820 return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; 821 }, 822 823 /** 824 * Returns true if context has transparent pixel 825 * at specified location (taking tolerance into account) 826 * @param {CanvasRenderingContext2D} ctx context 827 * @param {Number} x x coordinate 828 * @param {Number} y y coordinate 829 * @param {Number} tolerance Tolerance 830 */ 831 isTransparent: function(ctx, x, y, tolerance) { 832 833 // If tolerance is > 0 adjust start coords to take into account. 834 // If moves off Canvas fix to 0 835 if (tolerance > 0) { 836 if (x > tolerance) { 837 x -= tolerance; 838 } 839 else { 840 x = 0; 841 } 842 if (y > tolerance) { 843 y -= tolerance; 844 } 845 else { 846 y = 0; 847 } 848 } 849 850 var _isTransparent = true, 851 imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); 852 853 // Split image data - for tolerance > 1, pixelDataSize = 4; 854 for (var i = 3, l = imageData.data.length; i < l; i += 4) { 855 var temp = imageData.data[i]; 856 _isTransparent = temp <= 0; 857 if (_isTransparent === false) { 858 break; // Stop if colour found 859 } 860 } 861 862 imageData = null; 863 864 return _isTransparent; 865 } 866 }; 867 868})(typeof exports !== 'undefined' ? exports : this); 869 870 871(function() { 872 873 var arcToSegmentsCache = { }, 874 segmentToBezierCache = { }, 875 boundsOfCurveCache = { }, 876 _join = Array.prototype.join; 877 878 /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp 879 * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here 880 * http://mozilla.org/MPL/2.0/ 881 */ 882 function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { 883 var argsString = _join.call(arguments); 884 if (arcToSegmentsCache[argsString]) { 885 return arcToSegmentsCache[argsString]; 886 } 887 888 var PI = Math.PI, th = rotateX * PI / 180, 889 sinTh = Math.sin(th), 890 cosTh = Math.cos(th), 891 fromX = 0, fromY = 0; 892 893 rx = Math.abs(rx); 894 ry = Math.abs(ry); 895 896 var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5, 897 py = -cosTh * toY * 0.5 + sinTh * toX * 0.5, 898 rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, 899 pl = rx2 * ry2 - rx2 * py2 - ry2 * px2, 900 root = 0; 901 902 if (pl < 0) { 903 var s = Math.sqrt(1 - pl/(rx2 * ry2)); 904 rx *= s; 905 ry *= s; 906 } 907 else { 908 root = (large === sweep ? -1.0 : 1.0) * 909 Math.sqrt( pl /(rx2 * py2 + ry2 * px2)); 910 } 911 912 var cx = root * rx * py / ry, 913 cy = -root * ry * px / rx, 914 cx1 = cosTh * cx - sinTh * cy + toX * 0.5, 915 cy1 = sinTh * cx + cosTh * cy + toY * 0.5, 916 mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), 917 dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); 918 919 if (sweep === 0 && dtheta > 0) { 920 dtheta -= 2 * PI; 921 } 922 else if (sweep === 1 && dtheta < 0) { 923 dtheta += 2 * PI; 924 } 925 926 // Convert into cubic bezier segments <= 90deg 927 var segments = Math.ceil(Math.abs(dtheta / PI * 2)), 928 result = [], mDelta = dtheta / segments, 929 mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), 930 th3 = mTheta + mDelta; 931 932 for (var i = 0; i < segments; i++) { 933 result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); 934 fromX = result[i][4]; 935 fromY = result[i][5]; 936 mTheta = th3; 937 th3 += mDelta; 938 } 939 arcToSegmentsCache[argsString] = result; 940 return result; 941 } 942 943 function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { 944 var argsString2 = _join.call(arguments); 945 if (segmentToBezierCache[argsString2]) { 946 return segmentToBezierCache[argsString2]; 947 } 948 949 var costh2 = Math.cos(th2), 950 sinth2 = Math.sin(th2), 951 costh3 = Math.cos(th3), 952 sinth3 = Math.sin(th3), 953 toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, 954 toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, 955 cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2), 956 cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2), 957 cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3), 958 cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3); 959 960 segmentToBezierCache[argsString2] = [ 961 cp1X, cp1Y, 962 cp2X, cp2Y, 963 toX, toY 964 ]; 965 return segmentToBezierCache[argsString2]; 966 } 967 968 /* 969 * Private 970 */ 971 function calcVectorAngle(ux, uy, vx, vy) { 972 var ta = Math.atan2(uy, ux), 973 tb = Math.atan2(vy, vx); 974 if (tb >= ta) { 975 return tb - ta; 976 } 977 else { 978 return 2 * Math.PI - (ta - tb); 979 } 980 } 981 982 /** 983 * Draws arc 984 * @param {CanvasRenderingContext2D} ctx 985 * @param {Number} fx 986 * @param {Number} fy 987 * @param {Array} coords 988 */ 989 fabric.util.drawArc = function(ctx, fx, fy, coords) { 990 var rx = coords[0], 991 ry = coords[1], 992 rot = coords[2], 993 large = coords[3], 994 sweep = coords[4], 995 tx = coords[5], 996 ty = coords[6], 997 segs = [[ ], [ ], [ ], [ ]], 998 segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); 999 1000 for (var i = 0, len = segsNorm.length; i < len; i++) { 1001 segs[i][0] = segsNorm[i][0] + fx; 1002 segs[i][1] = segsNorm[i][1] + fy; 1003 segs[i][2] = segsNorm[i][2] + fx; 1004 segs[i][3] = segsNorm[i][3] + fy; 1005 segs[i][4] = segsNorm[i][4] + fx; 1006 segs[i][5] = segsNorm[i][5] + fy; 1007 ctx.bezierCurveTo.apply(ctx, segs[i]); 1008 } 1009 }; 1010 1011 /** 1012 * Calculate bounding box of a elliptic-arc 1013 * @param {Number} fx start point of arc 1014 * @param {Number} fy 1015 * @param {Number} rx horizontal radius 1016 * @param {Number} ry vertical radius 1017 * @param {Number} rot angle of horizontal axe 1018 * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points 1019 * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction 1020 * @param {Number} tx end point of arc 1021 * @param {Number} ty 1022 */ 1023 fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) { 1024 1025 var fromX = 0, fromY = 0, bound = [ ], bounds = [ ], 1026 segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot), 1027 boundCopy = [[ ], [ ]]; 1028 1029 for (var i = 0, len = segs.length; i < len; i++) { 1030 bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]); 1031 boundCopy[0].x = bound[0].x + fx; 1032 boundCopy[0].y = bound[0].y + fy; 1033 boundCopy[1].x = bound[1].x + fx; 1034 boundCopy[1].y = bound[1].y + fy; 1035 bounds.push(boundCopy[0]); 1036 bounds.push(boundCopy[1]); 1037 fromX = segs[i][4]; 1038 fromY = segs[i][5]; 1039 } 1040 return bounds; 1041 }; 1042 1043 /** 1044 * Calculate bounding box of a beziercurve 1045 * @param {Number} x0 starting point 1046 * @param {Number} y0 1047 * @param {Number} x1 first control point 1048 * @param {Number} y1 1049 * @param {Number} x2 secondo control point 1050 * @param {Number} y2 1051 * @param {Number} x3 end of beizer 1052 * @param {Number} y3 1053 */ 1054 // taken from http://jsbin.com/ivomiq/56/edit no credits available for that. 1055 function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { 1056 var argsString = _join.call(arguments); 1057 if (boundsOfCurveCache[argsString]) { 1058 return boundsOfCurveCache[argsString]; 1059 } 1060 1061 var sqrt = Math.sqrt, 1062 min = Math.min, max = Math.max, 1063 abs = Math.abs, tvalues = [ ], 1064 bounds = [[ ], [ ]], 1065 a, b, c, t, t1, t2, b2ac, sqrtb2ac; 1066 1067 b = 6 * x0 - 12 * x1 + 6 * x2; 1068 a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; 1069 c = 3 * x1 - 3 * x0; 1070 1071 for (var i = 0; i < 2; ++i) { 1072 if (i > 0) { 1073 b = 6 * y0 - 12 * y1 + 6 * y2; 1074 a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; 1075 c = 3 * y1 - 3 * y0; 1076 } 1077 1078 if (abs(a) < 1e-12) { 1079 if (abs(b) < 1e-12) { 1080 continue; 1081 } 1082 t = -c / b; 1083 if (0 < t && t < 1) { 1084 tvalues.push(t); 1085 } 1086 continue; 1087 } 1088 b2ac = b * b - 4 * c * a; 1089 if (b2ac < 0) { 1090 continue; 1091 } 1092 sqrtb2ac = sqrt(b2ac); 1093 t1 = (-b + sqrtb2ac) / (2 * a); 1094 if (0 < t1 && t1 < 1) { 1095 tvalues.push(t1); 1096 } 1097 t2 = (-b - sqrtb2ac) / (2 * a); 1098 if (0 < t2 && t2 < 1) { 1099 tvalues.push(t2); 1100 } 1101 } 1102 1103 var x, y, j = tvalues.length, jlen = j, mt; 1104 while (j--) { 1105 t = tvalues[j]; 1106 mt = 1 - t; 1107 x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); 1108 bounds[0][j] = x; 1109 1110 y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); 1111 bounds[1][j] = y; 1112 } 1113 1114 bounds[0][jlen] = x0; 1115 bounds[1][jlen] = y0; 1116 bounds[0][jlen + 1] = x3; 1117 bounds[1][jlen + 1] = y3; 1118 var result = [ 1119 { 1120 x: min.apply(null, bounds[0]), 1121 y: min.apply(null, bounds[1]) 1122 }, 1123 { 1124 x: max.apply(null, bounds[0]), 1125 y: max.apply(null, bounds[1]) 1126 } 1127 ]; 1128 boundsOfCurveCache[argsString] = result; 1129 return result; 1130 } 1131 1132 fabric.util.getBoundsOfCurve = getBoundsOfCurve; 1133 1134})(); 1135 1136 1137(function() { 1138 1139 var slice = Array.prototype.slice; 1140 1141 /* _ES5_COMPAT_START_ */ 1142 1143 if (!Array.prototype.indexOf) { 1144 /** 1145 * Finds index of an element in an array 1146 * @param {Any} searchElement 1147 * @param {Number} [fromIndex] 1148 * @return {Number} 1149 */ 1150 Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { 1151 if (this === void 0 || this === null) { 1152 throw new TypeError(); 1153 } 1154 var t = Object(this), len = t.length >>> 0; 1155 if (len === 0) { 1156 return -1; 1157 } 1158 var n = 0; 1159 if (arguments.length > 0) { 1160 n = Number(arguments[1]); 1161 if (n !== n) { // shortcut for verifying if it's NaN 1162 n = 0; 1163 } 1164 else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) { 1165 n = (n > 0 || -1) * Math.floor(Math.abs(n)); 1166 } 1167 } 1168 if (n >= len) { 1169 return -1; 1170 } 1171 var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); 1172 for (; k < len; k++) { 1173 if (k in t && t[k] === searchElement) { 1174 return k; 1175 } 1176 } 1177 return -1; 1178 }; 1179 } 1180 1181 if (!Array.prototype.forEach) { 1182 /** 1183 * Iterates an array, invoking callback for each element 1184 * @param {Function} fn Callback to invoke for each element 1185 * @param {Object} [context] Context to invoke callback in 1186 * @return {Array} 1187 */ 1188 Array.prototype.forEach = function(fn, context) { 1189 for (var i = 0, len = this.length >>> 0; i < len; i++) { 1190 if (i in this) { 1191 fn.call(context, this[i], i, this); 1192 } 1193 } 1194 }; 1195 } 1196 1197 if (!Array.prototype.map) { 1198 /** 1199 * Returns a result of iterating over an array, invoking callback for each element 1200 * @param {Function} fn Callback to invoke for each element 1201 * @param {Object} [context] Context to invoke callback in 1202 * @return {Array} 1203 */ 1204 Array.prototype.map = function(fn, context) { 1205 var result = [ ]; 1206 for (var i = 0, len = this.length >>> 0; i < len; i++) { 1207 if (i in this) { 1208 result[i] = fn.call(context, this[i], i, this); 1209 } 1210 } 1211 return result; 1212 }; 1213 } 1214 1215 if (!Array.prototype.every) { 1216 /** 1217 * Returns true if a callback returns truthy value for all elements in an array 1218 * @param {Function} fn Callback to invoke for each element 1219 * @param {Object} [context] Context to invoke callback in 1220 * @return {Boolean} 1221 */ 1222 Array.prototype.every = function(fn, context) { 1223 for (var i = 0, len = this.length >>> 0; i < len; i++) { 1224 if (i in this && !fn.call(context, this[i], i, this)) { 1225 return false; 1226 } 1227 } 1228 return true; 1229 }; 1230 } 1231 1232 if (!Array.prototype.some) { 1233 /** 1234 * Returns true if a callback returns truthy value for at least one element in an array 1235 * @param {Function} fn Callback to invoke for each element 1236 * @param {Object} [context] Context to invoke callback in 1237 * @return {Boolean} 1238 */ 1239 Array.prototype.some = function(fn, context) { 1240 for (var i = 0, len = this.length >>> 0; i < len; i++) { 1241 if (i in this && fn.call(context, this[i], i, this)) { 1242 return true; 1243 } 1244 } 1245 return false; 1246 }; 1247 } 1248 1249 if (!Array.prototype.filter) { 1250 /** 1251 * Returns the result of iterating over elements in an array 1252 * @param {Function} fn Callback to invoke for each element 1253 * @param {Object} [context] Context to invoke callback in 1254 * @return {Array} 1255 */ 1256 Array.prototype.filter = function(fn, context) { 1257 var result = [ ], val; 1258 for (var i = 0, len = this.length >>> 0; i < len; i++) { 1259 if (i in this) { 1260 val = this[i]; // in case fn mutates this 1261 if (fn.call(context, val, i, this)) { 1262 result.push(val); 1263 } 1264 } 1265 } 1266 return result; 1267 }; 1268 } 1269 1270 if (!Array.prototype.reduce) { 1271 /** 1272 * Returns "folded" (reduced) result of iterating over elements in an array 1273 * @param {Function} fn Callback to invoke for each element 1274 * @param {Object} [initial] Object to use as the first argument to the first call of the callback 1275 * @return {Any} 1276 */ 1277 Array.prototype.reduce = function(fn /*, initial*/) { 1278 var len = this.length >>> 0, 1279 i = 0, 1280 rv; 1281 1282 if (arguments.length > 1) { 1283 rv = arguments[1]; 1284 } 1285 else { 1286 do { 1287 if (i in this) { 1288 rv = this[i++]; 1289 break; 1290 } 1291 // if array contains no values, no initial value to return 1292 if (++i >= len) { 1293 throw new TypeError(); 1294 } 1295 } 1296 while (true); 1297 } 1298 for (; i < len; i++) { 1299 if (i in this) { 1300 rv = fn.call(null, rv, this[i], i, this); 1301 } 1302 } 1303 return rv; 1304 }; 1305 } 1306 1307 /* _ES5_COMPAT_END_ */ 1308 1309 /** 1310 * Invokes method on all items in a given array 1311 * @memberOf fabric.util.array 1312 * @param {Array} array Array to iterate over 1313 * @param {String} method Name of a method to invoke 1314 * @return {Array} 1315 */ 1316 function invoke(array, method) { 1317 var args = slice.call(arguments, 2), result = [ ]; 1318 for (var i = 0, len = array.length; i < len; i++) { 1319 result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); 1320 } 1321 return result; 1322 } 1323 1324 /** 1325 * Finds maximum value in array (not necessarily "first" one) 1326 * @memberOf fabric.util.array 1327 * @param {Array} array Array to iterate over 1328 * @param {String} byProperty 1329 * @return {Any} 1330 */ 1331 function max(array, byProperty) { 1332 return find(array, byProperty, function(value1, value2) { 1333 return value1 >= value2; 1334 }); 1335 } 1336 1337 /** 1338 * Finds minimum value in array (not necessarily "first" one) 1339 * @memberOf fabric.util.array 1340 * @param {Array} array Array to iterate over 1341 * @param {String} byProperty 1342 * @return {Any} 1343 */ 1344 function min(array, byProperty) { 1345 return find(array, byProperty, function(value1, value2) { 1346 return value1 < value2; 1347 }); 1348 } 1349 1350 /** 1351 * @private 1352 */ 1353 function find(array, byProperty, condition) { 1354 if (!array || array.length === 0) { 1355 return; 1356 } 1357 1358 var i = array.length - 1, 1359 result = byProperty ? array[i][byProperty] : array[i]; 1360 if (byProperty) { 1361 while (i--) { 1362 if (condition(array[i][byProperty], result)) { 1363 result = array[i][byProperty]; 1364 } 1365 } 1366 } 1367 else { 1368 while (i--) { 1369 if (condition(array[i], result)) { 1370 result = array[i]; 1371 } 1372 } 1373 } 1374 return result; 1375 } 1376 1377 /** 1378 * @namespace fabric.util.array 1379 */ 1380 fabric.util.array = { 1381 invoke: invoke, 1382 min: min, 1383 max: max 1384 }; 1385 1386})(); 1387 1388 1389(function() { 1390 1391 /** 1392 * Copies all enumerable properties of one object to another 1393 * @memberOf fabric.util.object 1394 * @param {Object} destination Where to copy to 1395 * @param {Object} source Where to copy from 1396 * @return {Object} 1397 */ 1398 function extend(destination, source) { 1399 // JScript DontEnum bug is not taken care of 1400 for (var property in source) { 1401 destination[property] = source[property]; 1402 } 1403 return destination; 1404 } 1405 1406 /** 1407 * Creates an empty object and copies all enumerable properties of another object to it 1408 * @memberOf fabric.util.object 1409 * @param {Object} object Object to clone 1410 * @return {Object} 1411 */ 1412 function clone(object) { 1413 return extend({ }, object); 1414 } 1415 1416 /** @namespace fabric.util.object */ 1417 fabric.util.object = { 1418 extend: extend, 1419 clone: clone 1420 }; 1421 1422})(); 1423 1424 1425(function() { 1426 1427 /* _ES5_COMPAT_START_ */ 1428 if (!String.prototype.trim) { 1429 /** 1430 * Trims a string (removing whitespace from the beginning and the end) 1431 * @function external:String#trim 1432 * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a> 1433 */ 1434 String.prototype.trim = function () { 1435 // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now 1436 return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, ''); 1437 }; 1438 } 1439 /* _ES5_COMPAT_END_ */ 1440 1441 /** 1442 * Camelizes a string 1443 * @memberOf fabric.util.string 1444 * @param {String} string String to camelize 1445 * @return {String} Camelized version of a string 1446 */ 1447 function camelize(string) { 1448 return string.replace(/-+(.)?/g, function(match, character) { 1449 return character ? character.toUpperCase() : ''; 1450 }); 1451 } 1452 1453 /** 1454 * Capitalizes a string 1455 * @memberOf fabric.util.string 1456 * @param {String} string String to capitalize 1457 * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized 1458 * and other letters stay untouched, if false first letter is capitalized 1459 * and other letters are converted to lowercase. 1460 * @return {String} Capitalized version of a string 1461 */ 1462 function capitalize(string, firstLetterOnly) { 1463 return string.charAt(0).toUpperCase() + 1464 (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); 1465 } 1466 1467 /** 1468 * Escapes XML in a string 1469 * @memberOf fabric.util.string 1470 * @param {String} string String to escape 1471 * @return {String} Escaped version of a string 1472 */ 1473 function escapeXml(string) { 1474 return string.replace(/&/g, '&') 1475 .replace(/"/g, '"') 1476 .replace(/'/g, ''') 1477 .replace(/</g, '<') 1478 .replace(/>/g, '>'); 1479 } 1480 1481 /** 1482 * String utilities 1483 * @namespace fabric.util.string 1484 */ 1485 fabric.util.string = { 1486 camelize: camelize, 1487 capitalize: capitalize, 1488 escapeXml: escapeXml 1489 }; 1490}()); 1491 1492 1493/* _ES5_COMPAT_START_ */ 1494(function() { 1495 1496 var slice = Array.prototype.slice, 1497 apply = Function.prototype.apply, 1498 Dummy = function() { }; 1499 1500 if (!Function.prototype.bind) { 1501 /** 1502 * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming) 1503 * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function#bind on MDN</a> 1504 * @param {Object} thisArg Object to bind function to 1505 * @param {Any[]} [...] Values to pass to a bound function 1506 * @return {Function} 1507 */ 1508 Function.prototype.bind = function(thisArg) { 1509 var _this = this, args = slice.call(arguments, 1), bound; 1510 if (args.length) { 1511 bound = function() { 1512 return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments))); 1513 }; 1514 } 1515 else { 1516 /** @ignore */ 1517 bound = function() { 1518 return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); 1519 }; 1520 } 1521 Dummy.prototype = this.prototype; 1522 bound.prototype = new Dummy(); 1523 1524 return bound; 1525 }; 1526 } 1527 1528})(); 1529/* _ES5_COMPAT_END_ */ 1530 1531 1532(function() { 1533 1534 var slice = Array.prototype.slice, emptyFunction = function() { }, 1535 1536 IS_DONTENUM_BUGGY = (function() { 1537 for (var p in { toString: 1 }) { 1538 if (p === 'toString') { 1539 return false; 1540 } 1541 } 1542 return true; 1543 })(), 1544 1545 /** @ignore */ 1546 addMethods = function(klass, source, parent) { 1547 for (var property in source) { 1548 1549 if (property in klass.prototype && 1550 typeof klass.prototype[property] === 'function' && 1551 (source[property] + '').indexOf('callSuper') > -1) { 1552 1553 klass.prototype[property] = (function(property) { 1554 return function() { 1555 1556 var superclass = this.constructor.superclass; 1557 this.constructor.superclass = parent; 1558 var returnValue = source[property].apply(this, arguments); 1559 this.constructor.superclass = superclass; 1560 1561 if (property !== 'initialize') { 1562 return returnValue; 1563 } 1564 }; 1565 })(property); 1566 } 1567 else { 1568 klass.prototype[property] = source[property]; 1569 } 1570 1571 if (IS_DONTENUM_BUGGY) { 1572 if (source.toString !== Object.prototype.toString) { 1573 klass.prototype.toString = source.toString; 1574 } 1575 if (source.valueOf !== Object.prototype.valueOf) { 1576 klass.prototype.valueOf = source.valueOf; 1577 } 1578 } 1579 } 1580 }; 1581 1582 function Subclass() { } 1583 1584 function callSuper(methodName) { 1585 var fn = this.constructor.superclass.prototype[methodName]; 1586 return (arguments.length > 1) 1587 ? fn.apply(this, slice.call(arguments, 1)) 1588 : fn.call(this); 1589 } 1590 1591 /** 1592 * Helper for creation of "classes". 1593 * @memberOf fabric.util 1594 * @param {Function} [parent] optional "Class" to inherit from 1595 * @param {Object} [properties] Properties shared by all instances of this class 1596 * (be careful modifying objects defined here as this would affect all instances) 1597 */ 1598 function createClass() { 1599 var parent = null, 1600 properties = slice.call(arguments, 0); 1601 1602 if (typeof properties[0] === 'function') { 1603 parent = properties.shift(); 1604 } 1605 function klass() { 1606 this.initialize.apply(this, arguments); 1607 } 1608 1609 klass.superclass = parent; 1610 klass.subclasses = [ ]; 1611 1612 if (parent) { 1613 Subclass.prototype = parent.prototype; 1614 klass.prototype = new Subclass(); 1615 parent.subclasses.push(klass); 1616 } 1617 for (var i = 0, length = properties.length; i < length; i++) { 1618 addMethods(klass, properties[i], parent); 1619 } 1620 if (!klass.prototype.initialize) { 1621 klass.prototype.initialize = emptyFunction; 1622 } 1623 klass.prototype.constructor = klass; 1624 klass.prototype.callSuper = callSuper; 1625 return klass; 1626 } 1627 1628 fabric.util.createClass = createClass; 1629})(); 1630 1631 1632(function () { 1633 1634 var unknown = 'unknown'; 1635 1636 /* EVENT HANDLING */ 1637 1638 function areHostMethods(object) { 1639 var methodNames = Array.prototype.slice.call(arguments, 1), 1640 t, i, len = methodNames.length; 1641 for (i = 0; i < len; i++) { 1642 t = typeof object[methodNames[i]]; 1643 if (!(/^(?:function|object|unknown)$/).test(t)) { 1644 return false; 1645 } 1646 } 1647 return true; 1648 } 1649 1650 /** @ignore */ 1651 var getElement, 1652 setElement, 1653 getUniqueId = (function () { 1654 var uid = 0; 1655 return function (element) { 1656 return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); 1657 }; 1658 })(); 1659 1660 (function () { 1661 var elements = { }; 1662 /** @ignore */ 1663 getElement = function (uid) { 1664 return elements[uid]; 1665 }; 1666 /** @ignore */ 1667 setElement = function (uid, element) { 1668 elements[uid] = element; 1669 }; 1670 })(); 1671 1672 function createListener(uid, handler) { 1673 return { 1674 handler: handler, 1675 wrappedHandler: createWrappedHandler(uid, handler) 1676 }; 1677 } 1678 1679 function createWrappedHandler(uid, handler) { 1680 return function (e) { 1681 handler.call(getElement(uid), e || fabric.window.event); 1682 }; 1683 } 1684 1685 function createDispatcher(uid, eventName) { 1686 return function (e) { 1687 if (handlers[uid] && handlers[uid][eventName]) { 1688 var handlersForEvent = handlers[uid][eventName]; 1689 for (var i = 0, len = handlersForEvent.length; i < len; i++) { 1690 handlersForEvent[i].call(this, e || fabric.window.event); 1691 } 1692 } 1693 }; 1694 } 1695 1696 var shouldUseAddListenerRemoveListener = ( 1697 areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') && 1698 areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')), 1699 1700 shouldUseAttachEventDetachEvent = ( 1701 areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') && 1702 areHostMethods(fabric.window, 'attachEvent', 'detachEvent')), 1703 1704 // IE branch 1705 listeners = { }, 1706 1707 // DOM L0 branch 1708 handlers = { }, 1709 1710 addListener, removeListener; 1711 1712 if (shouldUseAddListenerRemoveListener) { 1713 /** @ignore */ 1714 addListener = function (element, eventName, handler) { 1715 element.addEventListener(eventName, handler, false); 1716 }; 1717 /** @ignore */ 1718 removeListener = function (element, eventName, handler) { 1719 element.removeEventListener(eventName, handler, false); 1720 }; 1721 } 1722 1723 else if (shouldUseAttachEventDetachEvent) { 1724 /** @ignore */ 1725 addListener = function (element, eventName, handler) { 1726 var uid = getUniqueId(element); 1727 setElement(uid, element); 1728 if (!listeners[uid]) { 1729 listeners[uid] = { }; 1730 } 1731 if (!listeners[uid][eventName]) { 1732 listeners[uid][eventName] = [ ]; 1733 1734 } 1735 var listener = createListener(uid, handler); 1736 listeners[uid][eventName].push(listener); 1737 element.attachEvent('on' + eventName, listener.wrappedHandler); 1738 }; 1739 /** @ignore */ 1740 removeListener = function (element, eventName, handler) { 1741 var uid = getUniqueId(element), listener; 1742 if (listeners[uid] && listeners[uid][eventName]) { 1743 for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { 1744 listener = listeners[uid][eventName][i]; 1745 if (listener && listener.handler === handler) { 1746 element.detachEvent('on' + eventName, listener.wrappedHandler); 1747 listeners[uid][eventName][i] = null; 1748 } 1749 } 1750 } 1751 }; 1752 } 1753 else { 1754 /** @ignore */ 1755 addListener = function (element, eventName, handler) { 1756 var uid = getUniqueId(element); 1757 if (!handlers[uid]) { 1758 handlers[uid] = { }; 1759 } 1760 if (!handlers[uid][eventName]) { 1761 handlers[uid][eventName] = [ ]; 1762 var existingHandler = element['on' + eventName]; 1763 if (existingHandler) { 1764 handlers[uid][eventName].push(existingHandler); 1765 } 1766 element['on' + eventName] = createDispatcher(uid, eventName); 1767 } 1768 handlers[uid][eventName].push(handler); 1769 }; 1770 /** @ignore */ 1771 removeListener = function (element, eventName, handler) { 1772 var uid = getUniqueId(element); 1773 if (handlers[uid] && handlers[uid][eventName]) { 1774 var handlersForEvent = handlers[uid][eventName]; 1775 for (var i = 0, len = handlersForEvent.length; i < len; i++) { 1776 if (handlersForEvent[i] === handler) { 1777 handlersForEvent.splice(i, 1); 1778 } 1779 } 1780 } 1781 }; 1782 } 1783 1784 /** 1785 * Adds an event listener to an element 1786 * @function 1787 * @memberOf fabric.util 1788 * @param {HTMLElement} element 1789 * @param {String} eventName 1790 * @param {Function} handler 1791 */ 1792 fabric.util.addListener = addListener; 1793 1794 /** 1795 * Removes an event listener from an element 1796 * @function 1797 * @memberOf fabric.util 1798 * @param {HTMLElement} element 1799 * @param {String} eventName 1800 * @param {Function} handler 1801 */ 1802 fabric.util.removeListener = removeListener; 1803 1804 /** 1805 * Cross-browser wrapper for getting event's coordinates 1806 * @memberOf fabric.util 1807 * @param {Event} event Event object 1808 * @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn 1809 */ 1810 function getPointer(event, upperCanvasEl) { 1811 event || (event = fabric.window.event); 1812 1813 var element = event.target || 1814 (typeof event.srcElement !== unknown ? event.srcElement : null), 1815 1816 scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); 1817 1818 return { 1819 x: pointerX(event) + scroll.left, 1820 y: pointerY(event) + scroll.top 1821 }; 1822 } 1823 1824 var pointerX = function(event) { 1825 // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element) 1826 // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]] 1827 // need to investigate later 1828 return (typeof event.clientX !== unknown ? event.clientX : 0); 1829 }, 1830 1831 pointerY = function(event) { 1832 return (typeof event.clientY !== unknown ? event.clientY : 0); 1833 }; 1834 1835 function _getPointer(event, pageProp, clientProp) { 1836 var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches'; 1837 1838 return (event[touchProp] && event[touchProp][0] 1839 ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp])) 1840 || event[clientProp] 1841 : event[clientProp]); 1842 } 1843 1844 if (fabric.isTouchSupported) { 1845 pointerX = function(event) { 1846 return _getPointer(event, 'pageX', 'clientX'); 1847 }; 1848 pointerY = function(event) { 1849 return _getPointer(event, 'pageY', 'clientY'); 1850 }; 1851 } 1852 1853 fabric.util.getPointer = getPointer; 1854 1855 fabric.util.object.extend(fabric.util, fabric.Observable); 1856 1857})(); 1858 1859 1860(function () { 1861 1862 /** 1863 * Cross-browser wrapper for setting element's style 1864 * @memberOf fabric.util 1865 * @param {HTMLElement} element 1866 * @param {Object} styles 1867 * @return {HTMLElement} Element that was passed as a first argument 1868 */ 1869 function setStyle(element, styles) { 1870 var elementStyle = element.style; 1871 if (!elementStyle) { 1872 return element; 1873 } 1874 if (typeof styles === 'string') { 1875 element.style.cssText += ';' + styles; 1876 return styles.indexOf('opacity') > -1 1877 ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) 1878 : element; 1879 } 1880 for (var property in styles) { 1881 if (property === 'opacity') { 1882 setOpacity(element, styles[property]); 1883 } 1884 else { 1885 var normalizedProperty = (property === 'float' || property === 'cssFloat') 1886 ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') 1887 : property; 1888 elementStyle[normalizedProperty] = styles[property]; 1889 } 1890 } 1891 return element; 1892 } 1893 1894 var parseEl = fabric.document.createElement('div'), 1895 supportsOpacity = typeof parseEl.style.opacity === 'string', 1896 supportsFilters = typeof parseEl.style.filter === 'string', 1897 reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, 1898 1899 /** @ignore */ 1900 setOpacity = function (element) { return element; }; 1901 1902 if (supportsOpacity) { 1903 /** @ignore */ 1904 setOpacity = function(element, value) { 1905 element.style.opacity = value; 1906 return element; 1907 }; 1908 } 1909 else if (supportsFilters) { 1910 /** @ignore */ 1911 setOpacity = function(element, value) { 1912 var es = element.style; 1913 if (element.currentStyle && !element.currentStyle.hasLayout) { 1914 es.zoom = 1; 1915 } 1916 if (reOpacity.test(es.filter)) { 1917 value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); 1918 es.filter = es.filter.replace(reOpacity, value); 1919 } 1920 else { 1921 es.filter += ' alpha(opacity=' + (value * 100) + ')'; 1922 } 1923 return element; 1924 }; 1925 } 1926 1927 fabric.util.setStyle = setStyle; 1928 1929})(); 1930 1931 1932(function() { 1933 1934 var _slice = Array.prototype.slice; 1935 1936 /** 1937 * Takes id and returns an element with that id (if one exists in a document) 1938 * @memberOf fabric.util 1939 * @param {String|HTMLElement} id 1940 * @return {HTMLElement|null} 1941 */ 1942 function getById(id) { 1943 return typeof id === 'string' ? fabric.document.getElementById(id) : id; 1944 } 1945 1946 var sliceCanConvertNodelists, 1947 /** 1948 * Converts an array-like object (e.g. arguments or NodeList) to an array 1949 * @memberOf fabric.util 1950 * @param {Object} arrayLike 1951 * @return {Array} 1952 */ 1953 toArray = function(arrayLike) { 1954 return _slice.call(arrayLike, 0); 1955 }; 1956 1957 try { 1958 sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; 1959 } 1960 catch (err) { } 1961 1962 if (!sliceCanConvertNodelists) { 1963 toArray = function(arrayLike) { 1964 var arr = new Array(arrayLike.length), i = arrayLike.length; 1965 while (i--) { 1966 arr[i] = arrayLike[i]; 1967 } 1968 return arr; 1969 }; 1970 } 1971 1972 /** 1973 * Creates specified element with specified attributes 1974 * @memberOf fabric.util 1975 * @param {String} tagName Type of an element to create 1976 * @param {Object} [attributes] Attributes to set on an element 1977 * @return {HTMLElement} Newly created element 1978 */ 1979 function makeElement(tagName, attributes) { 1980 var el = fabric.document.createElement(tagName); 1981 for (var prop in attributes) { 1982 if (prop === 'class') { 1983 el.className = attributes[prop]; 1984 } 1985 else if (prop === 'for') { 1986 el.htmlFor = attributes[prop]; 1987 } 1988 else { 1989 el.setAttribute(prop, attributes[prop]); 1990 } 1991 } 1992 return el; 1993 } 1994 1995 /** 1996 * Adds class to an element 1997 * @memberOf fabric.util 1998 * @param {HTMLElement} element Element to add class to 1999 * @param {String} className Class to add to an element 2000 */ 2001 function addClass(element, className) { 2002 if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { 2003 element.className += (element.className ? ' ' : '') + className; 2004 } 2005 } 2006 2007 /** 2008 * Wraps element with another element 2009 * @memberOf fabric.util 2010 * @param {HTMLElement} element Element to wrap 2011 * @param {HTMLElement|String} wrapper Element to wrap with 2012 * @param {Object} [attributes] Attributes to set on a wrapper 2013 * @return {HTMLElement} wrapper 2014 */ 2015 function wrapElement(element, wrapper, attributes) { 2016 if (typeof wrapper === 'string') { 2017 wrapper = makeElement(wrapper, attributes); 2018 } 2019 if (element.parentNode) { 2020 element.parentNode.replaceChild(wrapper, element); 2021 } 2022 wrapper.appendChild(element); 2023 return wrapper; 2024 } 2025 2026 /** 2027 * Returns element scroll offsets 2028 * @memberOf fabric.util 2029 * @param {HTMLElement} element Element to operate on 2030 * @param {HTMLElement} upperCanvasEl Upper canvas element 2031 * @return {Object} Object with left/top values 2032 */ 2033 function getScrollLeftTop(element, upperCanvasEl) { 2034 2035 var firstFixedAncestor, 2036 origElement, 2037 left = 0, 2038 top = 0, 2039 docElement = fabric.document.documentElement, 2040 body = fabric.document.body || { 2041 scrollLeft: 0, scrollTop: 0 2042 }; 2043 2044 origElement = element; 2045 2046 while (element && element.parentNode && !firstFixedAncestor) { 2047 2048 element = element.parentNode; 2049 2050 if (element.nodeType === 1 && 2051 fabric.util.getElementStyle(element, 'position') === 'fixed') { 2052 firstFixedAncestor = element; 2053 } 2054 2055 if (element.nodeType === 1 && 2056 origElement !== upperCanvasEl && 2057 fabric.util.getElementStyle(element, 'position') === 'absolute') { 2058 left = 0; 2059 top = 0; 2060 } 2061 else if (element === fabric.document) { 2062 left = body.scrollLeft || docElement.scrollLeft || 0; 2063 top = body.scrollTop || docElement.scrollTop || 0; 2064 } 2065 else { 2066 left += element.scrollLeft || 0; 2067 top += element.scrollTop || 0; 2068 } 2069 } 2070 2071 return { left: left, top: top }; 2072 } 2073 2074 /** 2075 * Returns offset for a given element 2076 * @function 2077 * @memberOf fabric.util 2078 * @param {HTMLElement} element Element to get offset for 2079 * @return {Object} Object with "left" and "top" properties 2080 */ 2081 function getElementOffset(element) { 2082 var docElem, 2083 doc = element && element.ownerDocument, 2084 box = { left: 0, top: 0 }, 2085 offset = { left: 0, top: 0 }, 2086 scrollLeftTop, 2087 offsetAttributes = { 2088 borderLeftWidth: 'left', 2089 borderTopWidth: 'top', 2090 paddingLeft: 'left', 2091 paddingTop: 'top' 2092 }; 2093 2094 if (!doc) { 2095 return { left: 0, top: 0 }; 2096 } 2097 2098 for (var attr in offsetAttributes) { 2099 offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; 2100 } 2101 2102 docElem = doc.documentElement; 2103 if ( typeof element.getBoundingClientRect !== 'undefined' ) { 2104 box = element.getBoundingClientRect(); 2105 } 2106 2107 scrollLeftTop = fabric.util.getScrollLeftTop(element, null); 2108 2109 return { 2110 left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, 2111 top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top 2112 }; 2113 } 2114 2115 /** 2116 * Returns style attribute value of a given element 2117 * @memberOf fabric.util 2118 * @param {HTMLElement} element Element to get style attribute for 2119 * @param {String} attr Style attribute to get for element 2120 * @return {String} Style attribute value of the given element. 2121 */ 2122 var getElementStyle; 2123 if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { 2124 getElementStyle = function(element, attr) { 2125 var style = fabric.document.defaultView.getComputedStyle(element, null); 2126 return style ? style[attr] : undefined; 2127 }; 2128 } 2129 else { 2130 getElementStyle = function(element, attr) { 2131 var value = element.style[attr]; 2132 if (!value && element.currentStyle) { 2133 value = element.currentStyle[attr]; 2134 } 2135 return value; 2136 }; 2137 } 2138 2139 (function () { 2140 var style = fabric.document.documentElement.style, 2141 selectProp = 'userSelect' in style 2142 ? 'userSelect' 2143 : 'MozUserSelect' in style 2144 ? 'MozUserSelect' 2145 : 'WebkitUserSelect' in style 2146 ? 'WebkitUserSelect' 2147 : 'KhtmlUserSelect' in style 2148 ? 'KhtmlUserSelect' 2149 : ''; 2150 2151 /** 2152 * Makes element unselectable 2153 * @memberOf fabric.util 2154 * @param {HTMLElement} element Element to make unselectable 2155 * @return {HTMLElement} Element that was passed in 2156 */ 2157 function makeElementUnselectable(element) { 2158 if (typeof element.onselectstart !== 'undefined') { 2159 element.onselectstart = fabric.util.falseFunction; 2160 } 2161 if (selectProp) { 2162 element.style[selectProp] = 'none'; 2163 } 2164 else if (typeof element.unselectable === 'string') { 2165 element.unselectable = 'on'; 2166 } 2167 return element; 2168 } 2169 2170 /** 2171 * Makes element selectable 2172 * @memberOf fabric.util 2173 * @param {HTMLElement} element Element to make selectable 2174 * @return {HTMLElement} Element that was passed in 2175 */ 2176 function makeElementSelectable(element) { 2177 if (typeof element.onselectstart !== 'undefined') { 2178 element.onselectstart = null; 2179 } 2180 if (selectProp) { 2181 element.style[selectProp] = ''; 2182 } 2183 else if (typeof element.unselectable === 'string') { 2184 element.unselectable = ''; 2185 } 2186 return element; 2187 } 2188 2189 fabric.util.makeElementUnselectable = makeElementUnselectable; 2190 fabric.util.makeElementSelectable = makeElementSelectable; 2191 })(); 2192 2193 (function() { 2194 2195 /** 2196 * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading 2197 * @memberOf fabric.util 2198 * @param {String} url URL of a script to load 2199 * @param {Function} callback Callback to execute when script is finished loading 2200 */ 2201 function getScript(url, callback) { 2202 var headEl = fabric.document.getElementsByTagName('head')[0], 2203 scriptEl = fabric.document.createElement('script'), 2204 loading = true; 2205 2206 /** @ignore */ 2207 scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) { 2208 if (loading) { 2209 if (typeof this.readyState === 'string' && 2210 this.readyState !== 'loaded' && 2211 this.readyState !== 'complete') { 2212 return; 2213 } 2214 loading = false; 2215 callback(e || fabric.window.event); 2216 scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null; 2217 } 2218 }; 2219 scriptEl.src = url; 2220 headEl.appendChild(scriptEl); 2221 // causes issue in Opera 2222 // headEl.removeChild(scriptEl); 2223 } 2224 2225 fabric.util.getScript = getScript; 2226 })(); 2227 2228 fabric.util.getById = getById; 2229 fabric.util.toArray = toArray; 2230 fabric.util.makeElement = makeElement; 2231 fabric.util.addClass = addClass; 2232 fabric.util.wrapElement = wrapElement; 2233 fabric.util.getScrollLeftTop = getScrollLeftTop; 2234 fabric.util.getElementOffset = getElementOffset; 2235 fabric.util.getElementStyle = getElementStyle; 2236 2237})(); 2238 2239 2240(function() { 2241 2242 function addParamToUrl(url, param) { 2243 return url + (/\?/.test(url) ? '&' : '?') + param; 2244 } 2245 2246 var makeXHR = (function() { 2247 var factories = [ 2248 function() { return new ActiveXObject('Microsoft.XMLHTTP'); }, 2249 function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, 2250 function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }, 2251 function() { return new XMLHttpRequest(); } 2252 ]; 2253 for (var i = factories.length; i--; ) { 2254 try { 2255 var req = factories[i](); 2256 if (req) { 2257 return factories[i]; 2258 } 2259 } 2260 catch (err) { } 2261 } 2262 })(); 2263 2264 function emptyFn() { } 2265 2266 /** 2267 * Cross-browser abstraction for sending XMLHttpRequest 2268 * @memberOf fabric.util 2269 * @param {String} url URL to send XMLHttpRequest to 2270 * @param {Object} [options] Options object 2271 * @param {String} [options.method="GET"] 2272 * @param {Function} options.onComplete Callback to invoke when request is completed 2273 * @return {XMLHttpRequest} request 2274 */ 2275 function request(url, options) { 2276 2277 options || (options = { }); 2278 2279 var method = options.method ? options.method.toUpperCase() : 'GET', 2280 onComplete = options.onComplete || function() { }, 2281 xhr = makeXHR(), 2282 body; 2283 2284 /** @ignore */ 2285 xhr.onreadystatechange = function() { 2286 if (xhr.readyState === 4) { 2287 onComplete(xhr); 2288 xhr.onreadystatechange = emptyFn; 2289 } 2290 }; 2291 2292 if (method === 'GET') { 2293 body = null; 2294 if (typeof options.parameters === 'string') { 2295 url = addParamToUrl(url, options.parameters); 2296 } 2297 } 2298 2299 xhr.open(method, url, true); 2300 2301 if (method === 'POST' || method === 'PUT') { 2302 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 2303 } 2304 2305 xhr.send(body); 2306 return xhr; 2307 } 2308 2309 fabric.util.request = request; 2310})(); 2311 2312 2313/** 2314 * Wrapper around `console.log` (when available) 2315 * @param {Any} [values] Values to log 2316 */ 2317fabric.log = function() { }; 2318 2319/** 2320 * Wrapper around `console.warn` (when available) 2321 * @param {Any} [values] Values to log as a warning 2322 */ 2323fabric.warn = function() { }; 2324 2325if (typeof console !== 'undefined') { 2326 2327 ['log', 'warn'].forEach(function(methodName) { 2328 2329 if (typeof console[methodName] !== 'undefined' && 2330 typeof console[methodName].apply === 'function') { 2331 2332 fabric[methodName] = function() { 2333 return console[methodName].apply(console, arguments); 2334 }; 2335 } 2336 }); 2337} 2338 2339 2340(function() { 2341 2342 /** 2343 * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. 2344 * @memberOf fabric.util 2345 * @param {Object} [options] Animation options 2346 * @param {Function} [options.onChange] Callback; invoked on every value change 2347 * @param {Function} [options.onComplete] Callback; invoked when value change is completed 2348 * @param {Number} [options.startValue=0] Starting value 2349 * @param {Number} [options.endValue=100] Ending value 2350 * @param {Number} [options.byValue=100] Value to modify the property by 2351 * @param {Function} [options.easing] Easing function 2352 * @param {Number} [options.duration=500] Duration of change (in ms) 2353 */ 2354 function animate(options) { 2355 2356 requestAnimFrame(function(timestamp) { 2357 options || (options = { }); 2358 2359 var start = timestamp || +new Date(), 2360 duration = options.duration || 500, 2361 finish = start + duration, time, 2362 onChange = options.onChange || function() { }, 2363 abort = options.abort || function() { return false; }, 2364 easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;}, 2365 startValue = 'startValue' in options ? options.startValue : 0, 2366 endValue = 'endValue' in options ? options.endValue : 100, 2367 byValue = options.byValue || endValue - startValue; 2368 2369 options.onStart && options.onStart(); 2370 2371 (function tick(ticktime) { 2372 time = ticktime || +new Date(); 2373 var currentTime = time > finish ? duration : (time - start); 2374 if (abort()) { 2375 options.onComplete && options.onComplete(); 2376 return; 2377 } 2378 onChange(easing(currentTime, startValue, byValue, duration)); 2379 if (time > finish) { 2380 options.onComplete && options.onComplete(); 2381 return; 2382 } 2383 requestAnimFrame(tick); 2384 })(start); 2385 }); 2386 2387 } 2388 2389 var _requestAnimFrame = fabric.window.requestAnimationFrame || 2390 fabric.window.webkitRequestAnimationFrame || 2391 fabric.window.mozRequestAnimationFrame || 2392 fabric.window.oRequestAnimationFrame || 2393 fabric.window.msRequestAnimationFrame || 2394 function(callback) { 2395 fabric.window.setTimeout(callback, 1000 / 60); 2396 }; 2397 /** 2398 * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 2399 * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method 2400 * @memberOf fabric.util 2401 * @param {Function} callback Callback to invoke 2402 * @param {DOMElement} element optional Element to associate with animation 2403 */ 2404 function requestAnimFrame() { 2405 return _requestAnimFrame.apply(fabric.window, arguments); 2406 } 2407 2408 fabric.util.animate = animate; 2409 fabric.util.requestAnimFrame = requestAnimFrame; 2410 2411})(); 2412 2413 2414(function() { 2415 2416 function normalize(a, c, p, s) { 2417 if (a < Math.abs(c)) { 2418 a = c; 2419 s = p / 4; 2420 } 2421 else { 2422 s = p / (2 * Math.PI) * Math.asin(c / a); 2423 } 2424 return { a: a, c: c, p: p, s: s }; 2425 } 2426 2427 function elastic(opts, t, d) { 2428 return opts.a * 2429 Math.pow(2, 10 * (t -= 1)) * 2430 Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p ); 2431 } 2432 2433 /** 2434 * Cubic easing out 2435 * @memberOf fabric.util.ease 2436 */ 2437 function easeOutCubic(t, b, c, d) { 2438 return c * ((t = t / d - 1) * t * t + 1) + b; 2439 } 2440 2441 /** 2442 * Cubic easing in and out 2443 * @memberOf fabric.util.ease 2444 */ 2445 function easeInOutCubic(t, b, c, d) { 2446 t /= d/2; 2447 if (t < 1) { 2448 return c / 2 * t * t * t + b; 2449 } 2450 return c / 2 * ((t -= 2) * t * t + 2) + b; 2451 } 2452 2453 /** 2454 * Quartic easing in 2455 * @memberOf fabric.util.ease 2456 */ 2457 function easeInQuart(t, b, c, d) { 2458 return c * (t /= d) * t * t * t + b; 2459 } 2460 2461 /** 2462 * Quartic easing out 2463 * @memberOf fabric.util.ease 2464 */ 2465 function easeOutQuart(t, b, c, d) { 2466 return -c * ((t = t / d - 1) * t * t * t - 1) + b; 2467 } 2468 2469 /** 2470 * Quartic easing in and out 2471 * @memberOf fabric.util.ease 2472 */ 2473 function easeInOutQuart(t, b, c, d) { 2474 t /= d / 2; 2475 if (t < 1) { 2476 return c / 2 * t * t * t * t + b; 2477 } 2478 return -c / 2 * ((t -= 2) * t * t * t - 2) + b; 2479 } 2480 2481 /** 2482 * Quintic easing in 2483 * @memberOf fabric.util.ease 2484 */ 2485 function easeInQuint(t, b, c, d) { 2486 return c * (t /= d) * t * t * t * t + b; 2487 } 2488 2489 /** 2490 * Quintic easing out 2491 * @memberOf fabric.util.ease 2492 */ 2493 function easeOutQuint(t, b, c, d) { 2494 return c * ((t = t / d - 1) * t * t * t * t + 1) + b; 2495 } 2496 2497 /** 2498 * Quintic easing in and out 2499 * @memberOf fabric.util.ease 2500 */ 2501 function easeInOutQuint(t, b, c, d) { 2502 t /= d / 2; 2503 if (t < 1) { 2504 return c / 2 * t * t * t * t * t + b; 2505 } 2506 return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; 2507 } 2508 2509 /** 2510 * Sinusoidal easing in 2511 * @memberOf fabric.util.ease 2512 */ 2513 function easeInSine(t, b, c, d) { 2514 return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; 2515 } 2516 2517 /** 2518 * Sinusoidal easing out 2519 * @memberOf fabric.util.ease 2520 */ 2521 function easeOutSine(t, b, c, d) { 2522 return c * Math.sin(t / d * (Math.PI / 2)) + b; 2523 } 2524 2525 /** 2526 * Sinusoidal easing in and out 2527 * @memberOf fabric.util.ease 2528 */ 2529 function easeInOutSine(t, b, c, d) { 2530 return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; 2531 } 2532 2533 /** 2534 * Exponential easing in 2535 * @memberOf fabric.util.ease 2536 */ 2537 function easeInExpo(t, b, c, d) { 2538 return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; 2539 } 2540 2541 /** 2542 * Exponential easing out 2543 * @memberOf fabric.util.ease 2544 */ 2545 function easeOutExpo(t, b, c, d) { 2546 return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; 2547 } 2548 2549 /** 2550 * Exponential easing in and out 2551 * @memberOf fabric.util.ease 2552 */ 2553 function easeInOutExpo(t, b, c, d) { 2554 if (t === 0) { 2555 return b; 2556 } 2557 if (t === d) { 2558 return b + c; 2559 } 2560 t /= d / 2; 2561 if (t < 1) { 2562 return c / 2 * Math.pow(2, 10 * (t - 1)) + b; 2563 } 2564 return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; 2565 } 2566 2567 /** 2568 * Circular easing in 2569 * @memberOf fabric.util.ease 2570 */ 2571 function easeInCirc(t, b, c, d) { 2572 return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; 2573 } 2574 2575 /** 2576 * Circular easing out 2577 * @memberOf fabric.util.ease 2578 */ 2579 function easeOutCirc(t, b, c, d) { 2580 return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; 2581 } 2582 2583 /** 2584 * Circular easing in and out 2585 * @memberOf fabric.util.ease 2586 */ 2587 function easeInOutCirc(t, b, c, d) { 2588 t /= d / 2; 2589 if (t < 1) { 2590 return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; 2591 } 2592 return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; 2593 } 2594 2595 /** 2596 * Elastic easing in 2597 * @memberOf fabric.util.ease 2598 */ 2599 function easeInElastic(t, b, c, d) { 2600 var s = 1.70158, p = 0, a = c; 2601 if (t === 0) { 2602 return b; 2603 } 2604 t /= d; 2605 if (t === 1) { 2606 return b + c; 2607 } 2608 if (!p) { 2609 p = d * 0.3; 2610 } 2611 var opts = normalize(a, c, p, s); 2612 return -elastic(opts, t, d) + b; 2613 } 2614 2615 /** 2616 * Elastic easing out 2617 * @memberOf fabric.util.ease 2618 */ 2619 function easeOutElastic(t, b, c, d) { 2620 var s = 1.70158, p = 0, a = c; 2621 if (t === 0) { 2622 return b; 2623 } 2624 t /= d; 2625 if (t === 1) { 2626 return b + c; 2627 } 2628 if (!p) { 2629 p = d * 0.3; 2630 } 2631 var opts = normalize(a, c, p, s); 2632 return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b; 2633 } 2634 2635 /** 2636 * Elastic easing in and out 2637 * @memberOf fabric.util.ease 2638 */ 2639 function easeInOutElastic(t, b, c, d) { 2640 var s = 1.70158, p = 0, a = c; 2641 if (t === 0) { 2642 return b; 2643 } 2644 t /= d / 2; 2645 if (t === 2) { 2646 return b + c; 2647 } 2648 if (!p) { 2649 p = d * (0.3 * 1.5); 2650 } 2651 var opts = normalize(a, c, p, s); 2652 if (t < 1) { 2653 return -0.5 * elastic(opts, t, d) + b; 2654 } 2655 return opts.a * Math.pow(2, -10 * (t -= 1)) * 2656 Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b; 2657 } 2658 2659 /** 2660 * Backwards easing in 2661 * @memberOf fabric.util.ease 2662 */ 2663 function easeInBack(t, b, c, d, s) { 2664 if (s === undefined) { 2665 s = 1.70158; 2666 } 2667 return c * (t /= d) * t * ((s + 1) * t - s) + b; 2668 } 2669 2670 /** 2671 * Backwards easing out 2672 * @memberOf fabric.util.ease 2673 */ 2674 function easeOutBack(t, b, c, d, s) { 2675 if (s === undefined) { 2676 s = 1.70158; 2677 } 2678 return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; 2679 } 2680 2681 /** 2682 * Backwards easing in and out 2683 * @memberOf fabric.util.ease 2684 */ 2685 function easeInOutBack(t, b, c, d, s) { 2686 if (s === undefined) { 2687 s = 1.70158; 2688 } 2689 t /= d / 2; 2690 if (t < 1) { 2691 return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; 2692 } 2693 return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; 2694 } 2695 2696 /** 2697 * Bouncing easing in 2698 * @memberOf fabric.util.ease 2699 */ 2700 function easeInBounce(t, b, c, d) { 2701 return c - easeOutBounce (d - t, 0, c, d) + b; 2702 } 2703 2704 /** 2705 * Bouncing easing out 2706 * @memberOf fabric.util.ease 2707 */ 2708 function easeOutBounce(t, b, c, d) { 2709 if ((t /= d) < (1 / 2.75)) { 2710 return c * (7.5625 * t * t) + b; 2711 } 2712 else if (t < (2/2.75)) { 2713 return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; 2714 } 2715 else if (t < (2.5/2.75)) { 2716 return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; 2717 } 2718 else { 2719 return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; 2720 } 2721 } 2722 2723 /** 2724 * Bouncing easing in and out 2725 * @memberOf fabric.util.ease 2726 */ 2727 function easeInOutBounce(t, b, c, d) { 2728 if (t < d / 2) { 2729 return easeInBounce (t * 2, 0, c, d) * 0.5 + b; 2730 } 2731 return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; 2732 } 2733 2734 /** 2735 * Easing functions 2736 * See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a> 2737 * @namespace fabric.util.ease 2738 */ 2739 fabric.util.ease = { 2740 2741 /** 2742 * Quadratic easing in 2743 * @memberOf fabric.util.ease 2744 */ 2745 easeInQuad: function(t, b, c, d) { 2746 return c * (t /= d) * t + b; 2747 }, 2748 2749 /** 2750 * Quadratic easing out 2751 * @memberOf fabric.util.ease 2752 */ 2753 easeOutQuad: function(t, b, c, d) { 2754 return -c * (t /= d) * (t - 2) + b; 2755 }, 2756 2757 /** 2758 * Quadratic easing in and out 2759 * @memberOf fabric.util.ease 2760 */ 2761 easeInOutQuad: function(t, b, c, d) { 2762 t /= (d / 2); 2763 if (t < 1) { 2764 return c / 2 * t * t + b; 2765 } 2766 return -c / 2 * ((--t) * (t - 2) - 1) + b; 2767 }, 2768 2769 /** 2770 * Cubic easing in 2771 * @memberOf fabric.util.ease 2772 */ 2773 easeInCubic: function(t, b, c, d) { 2774 return c * (t /= d) * t * t + b; 2775 }, 2776 2777 easeOutCubic: easeOutCubic, 2778 easeInOutCubic: easeInOutCubic, 2779 easeInQuart: easeInQuart, 2780 easeOutQuart: easeOutQuart, 2781 easeInOutQuart: easeInOutQuart, 2782 easeInQuint: easeInQuint, 2783 easeOutQuint: easeOutQuint, 2784 easeInOutQuint: easeInOutQuint, 2785 easeInSine: easeInSine, 2786 easeOutSine: easeOutSine, 2787 easeInOutSine: easeInOutSine, 2788 easeInExpo: easeInExpo, 2789 easeOutExpo: easeOutExpo, 2790 easeInOutExpo: easeInOutExpo, 2791 easeInCirc: easeInCirc, 2792 easeOutCirc: easeOutCirc, 2793 easeInOutCirc: easeInOutCirc, 2794 easeInElastic: easeInElastic, 2795 easeOutElastic: easeOutElastic, 2796 easeInOutElastic: easeInOutElastic, 2797 easeInBack: easeInBack, 2798 easeOutBack: easeOutBack, 2799 easeInOutBack: easeInOutBack, 2800 easeInBounce: easeInBounce, 2801 easeOutBounce: easeOutBounce, 2802 easeInOutBounce: easeInOutBounce 2803 }; 2804 2805}()); 2806 2807 2808(function(global) { 2809 2810 'use strict'; 2811 2812 /** 2813 * @name fabric 2814 * @namespace 2815 */ 2816 2817 var fabric = global.fabric || (global.fabric = { }), 2818 extend = fabric.util.object.extend, 2819 capitalize = fabric.util.string.capitalize, 2820 clone = fabric.util.object.clone, 2821 toFixed = fabric.util.toFixed, 2822 parseUnit = fabric.util.parseUnit, 2823 multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, 2824 2825 attributesMap = { 2826 cx: 'left', 2827 x: 'left', 2828 r: 'radius', 2829 cy: 'top', 2830 y: 'top', 2831 display: 'visible', 2832 visibility: 'visible', 2833 transform: 'transformMatrix', 2834 'fill-opacity': 'fillOpacity', 2835 'fill-rule': 'fillRule', 2836 'font-family': 'fontFamily', 2837 'font-size': 'fontSize', 2838 'font-style': 'fontStyle', 2839 'font-weight': 'fontWeight', 2840 'stroke-dasharray': 'strokeDashArray', 2841 'stroke-linecap': 'strokeLineCap', 2842 'stroke-linejoin': 'strokeLineJoin', 2843 'stroke-miterlimit': 'strokeMiterLimit', 2844 'stroke-opacity': 'strokeOpacity', 2845 'stroke-width': 'strokeWidth', 2846 'text-decoration': 'textDecoration', 2847 'text-anchor': 'originX' 2848 }, 2849 2850 colorAttributes = { 2851 stroke: 'strokeOpacity', 2852 fill: 'fillOpacity' 2853 }; 2854 2855 fabric.cssRules = { }; 2856 fabric.gradientDefs = { }; 2857 2858 function normalizeAttr(attr) { 2859 // transform attribute names 2860 if (attr in attributesMap) { 2861 return attributesMap[attr]; 2862 } 2863 return attr; 2864 } 2865 2866 function normalizeValue(attr, value, parentAttributes, fontSize) { 2867 var isArray = Object.prototype.toString.call(value) === '[object Array]', 2868 parsed; 2869 2870 if ((attr === 'fill' || attr === 'stroke') && value === 'none') { 2871 value = ''; 2872 } 2873 else if (attr === 'strokeDashArray') { 2874 value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) { 2875 return parseFloat(n); 2876 }); 2877 } 2878 else if (attr === 'transformMatrix') { 2879 if (parentAttributes && parentAttributes.transformMatrix) { 2880 value = multiplyTransformMatrices( 2881 parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); 2882 } 2883 else { 2884 value = fabric.parseTransformAttribute(value); 2885 } 2886 } 2887 else if (attr === 'visible') { 2888 value = (value === 'none' || value === 'hidden') ? false : true; 2889 // display=none on parent element always takes precedence over child element 2890 if (parentAttributes && parentAttributes.visible === false) { 2891 value = false; 2892 } 2893 } 2894 else if (attr === 'originX' /* text-anchor */) { 2895 value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; 2896 } 2897 else { 2898 parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); 2899 } 2900 2901 return (!isArray && isNaN(parsed) ? value : parsed); 2902 } 2903 2904 /** 2905 * @private 2906 * @param {Object} attributes Array of attributes to parse 2907 */ 2908 function _setStrokeFillOpacity(attributes) { 2909 for (var attr in colorAttributes) { 2910 2911 if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') { 2912 continue; 2913 } 2914 2915 if (attributes[attr].indexOf('url(') === 0) { 2916 continue; 2917 } 2918 2919 var color = new fabric.Color(attributes[attr]); 2920 attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); 2921 } 2922 return attributes; 2923 } 2924 2925 /** 2926 * Parses "transform" attribute, returning an array of values 2927 * @static 2928 * @function 2929 * @memberOf fabric 2930 * @param {String} attributeValue String containing attribute value 2931 * @return {Array} Array of 6 elements representing transformation matrix 2932 */ 2933 fabric.parseTransformAttribute = (function() { 2934 function rotateMatrix(matrix, args) { 2935 var angle = args[0]; 2936 2937 matrix[0] = Math.cos(angle); 2938 matrix[1] = Math.sin(angle); 2939 matrix[2] = -Math.sin(angle); 2940 matrix[3] = Math.cos(angle); 2941 } 2942 2943 function scaleMatrix(matrix, args) { 2944 var multiplierX = args[0], 2945 multiplierY = (args.length === 2) ? args[1] : args[0]; 2946 2947 matrix[0] = multiplierX; 2948 matrix[3] = multiplierY; 2949 } 2950 2951 function skewXMatrix(matrix, args) { 2952 matrix[2] = Math.tan(fabric.util.degreesToRadians(args[0])); 2953 } 2954 2955 function skewYMatrix(matrix, args) { 2956 matrix[1] = Math.tan(fabric.util.degreesToRadians(args[0])); 2957 } 2958 2959 function translateMatrix(matrix, args) { 2960 matrix[4] = args[0]; 2961 if (args.length === 2) { 2962 matrix[5] = args[1]; 2963 } 2964 } 2965 2966 // identity matrix 2967 var iMatrix = [ 2968 1, // a 2969 0, // b 2970 0, // c 2971 1, // d 2972 0, // e 2973 0 // f 2974 ], 2975 2976 // == begin transform regexp 2977 number = fabric.reNum, 2978 2979 commaWsp = '(?:\\s+,?\\s*|,\\s*)', 2980 2981 skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', 2982 2983 skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', 2984 2985 rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + 2986 commaWsp + '(' + number + ')' + 2987 commaWsp + '(' + number + '))?\\s*\\))', 2988 2989 scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + 2990 commaWsp + '(' + number + '))?\\s*\\))', 2991 2992 translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + 2993 commaWsp + '(' + number + '))?\\s*\\))', 2994 2995 matrix = '(?:(matrix)\\s*\\(\\s*' + 2996 '(' + number + ')' + commaWsp + 2997 '(' + number + ')' + commaWsp + 2998 '(' + number + ')' + commaWsp + 2999 '(' + number + ')' + commaWsp + 3000 '(' + number + ')' + commaWsp + 3001 '(' + number + ')' + 3002 '\\s*\\))', 3003 3004 transform = '(?:' + 3005 matrix + '|' + 3006 translate + '|' + 3007 scale + '|' + 3008 rotate + '|' + 3009 skewX + '|' + 3010 skewY + 3011 ')', 3012 3013 transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')', 3014 3015 transformList = '^\\s*(?:' + transforms + '?)\\s*$', 3016 3017 // http://www.w3.org/TR/SVG/coords.html#TransformAttribute 3018 reTransformList = new RegExp(transformList), 3019 // == end transform regexp 3020 3021 reTransform = new RegExp(transform, 'g'); 3022 3023 return function(attributeValue) { 3024 3025 // start with identity matrix 3026 var matrix = iMatrix.concat(), 3027 matrices = [ ]; 3028 3029 // return if no argument was given or 3030 // an argument does not match transform attribute regexp 3031 if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { 3032 return matrix; 3033 } 3034 3035 attributeValue.replace(reTransform, function(match) { 3036 3037 var m = new RegExp(transform).exec(match).filter(function (match) { 3038 return (match !== '' && match != null); 3039 }), 3040 operation = m[1], 3041 args = m.slice(2).map(parseFloat); 3042 3043 switch (operation) { 3044 case 'translate': 3045 translateMatrix(matrix, args); 3046 break; 3047 case 'rotate': 3048 args[0] = fabric.util.degreesToRadians(args[0]); 3049 rotateMatrix(matrix, args); 3050 break; 3051 case 'scale': 3052 scaleMatrix(matrix, args); 3053 break; 3054 case 'skewX': 3055 skewXMatrix(matrix, args); 3056 break; 3057 case 'skewY': 3058 skewYMatrix(matrix, args); 3059 break; 3060 case 'matrix': 3061 matrix = args; 3062 break; 3063 } 3064 3065 // snapshot current matrix into matrices array 3066 matrices.push(matrix.concat()); 3067 // reset 3068 matrix = iMatrix.concat(); 3069 }); 3070 3071 var combinedMatrix = matrices[0]; 3072 while (matrices.length > 1) { 3073 matrices.shift(); 3074 combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); 3075 } 3076 return combinedMatrix; 3077 }; 3078 })(); 3079 3080 /** 3081 * @private 3082 */ 3083 function parseStyleString(style, oStyle) { 3084 var attr, value; 3085 style.replace(/;$/, '').split(';').forEach(function (chunk) { 3086 var pair = chunk.split(':'); 3087 3088 attr = normalizeAttr(pair[0].trim().toLowerCase()); 3089 value = normalizeValue(attr, pair[1].trim()); 3090 3091 oStyle[attr] = value; 3092 }); 3093 } 3094 3095 /** 3096 * @private 3097 */ 3098 function parseStyleObject(style, oStyle) { 3099 var attr, value; 3100 for (var prop in style) { 3101 if (typeof style[prop] === 'undefined') { 3102 continue; 3103 } 3104 3105 attr = normalizeAttr(prop.toLowerCase()); 3106 value = normalizeValue(attr, style[prop]); 3107 3108 oStyle[attr] = value; 3109 } 3110 } 3111 3112 /** 3113 * @private 3114 */ 3115 function getGlobalStylesForElement(element, svgUid) { 3116 var styles = { }; 3117 for (var rule in fabric.cssRules[svgUid]) { 3118 if (elementMatchesRule(element, rule.split(' '))) { 3119 for (var property in fabric.cssRules[svgUid][rule]) { 3120 styles[property] = fabric.cssRules[svgUid][rule][property]; 3121 } 3122 } 3123 } 3124 return styles; 3125 } 3126 3127 /** 3128 * @private 3129 */ 3130 function elementMatchesRule(element, selectors) { 3131 var firstMatching, parentMatching = true; 3132 //start from rightmost selector. 3133 firstMatching = selectorMatches(element, selectors.pop()); 3134 if (firstMatching && selectors.length) { 3135 parentMatching = doesSomeParentMatch(element, selectors); 3136 } 3137 return firstMatching && parentMatching && (selectors.length === 0); 3138 } 3139 3140 function doesSomeParentMatch(element, selectors) { 3141 var selector, parentMatching = true; 3142 while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { 3143 if (parentMatching) { 3144 selector = selectors.pop(); 3145 } 3146 element = element.parentNode; 3147 parentMatching = selectorMatches(element, selector); 3148 } 3149 return selectors.length === 0; 3150 } 3151 /** 3152 * @private 3153 */ 3154 function selectorMatches(element, selector) { 3155 var nodeName = element.nodeName, 3156 classNames = element.getAttribute('class'), 3157 id = element.getAttribute('id'), matcher; 3158 // i check if a selector matches slicing away part from it. 3159 // if i get empty string i should match 3160 matcher = new RegExp('^' + nodeName, 'i'); 3161 selector = selector.replace(matcher, ''); 3162 if (id && selector.length) { 3163 matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i'); 3164 selector = selector.replace(matcher, ''); 3165 } 3166 if (classNames && selector.length) { 3167 classNames = classNames.split(' '); 3168 for (var i = classNames.length; i--;) { 3169 matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); 3170 selector = selector.replace(matcher, ''); 3171 } 3172 } 3173 return selector.length === 0; 3174 } 3175 3176 /** 3177 * @private 3178 */ 3179 function parseUseDirectives(doc) { 3180 var nodelist = doc.getElementsByTagName('use'); 3181 while (nodelist.length) { 3182 var el = nodelist[0], 3183 xlink = el.getAttribute('xlink:href').substr(1), 3184 x = el.getAttribute('x') || 0, 3185 y = el.getAttribute('y') || 0, 3186 el2 = doc.getElementById(xlink).cloneNode(true), 3187 currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')', 3188 parentNode; 3189 3190 for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) { 3191 var attr = attrs.item(j); 3192 if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') { 3193 continue; 3194 } 3195 3196 if (attr.nodeName === 'transform') { 3197 currentTrans = attr.nodeValue + ' ' + currentTrans; 3198 } 3199 else { 3200 el2.setAttribute(attr.nodeName, attr.nodeValue); 3201 } 3202 } 3203 3204 el2.setAttribute('transform', currentTrans); 3205 el2.setAttribute('instantiated_by_use', '1'); 3206 el2.removeAttribute('id'); 3207 parentNode = el.parentNode; 3208 parentNode.replaceChild(el2, el); 3209 } 3210 } 3211 3212 // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute 3213 // matches, e.g.: +14.56e-12, etc. 3214 var reViewBoxAttrValue = new RegExp( 3215 '^' + 3216 '\\s*(' + fabric.reNum + '+)\\s*,?' + 3217 '\\s*(' + fabric.reNum + '+)\\s*,?' + 3218 '\\s*(' + fabric.reNum + '+)\\s*,?' + 3219 '\\s*(' + fabric.reNum + '+)\\s*' + 3220 '$' 3221 ); 3222 3223 /** 3224 * Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements 3225 */ 3226 function addVBTransform(element, widthAttr, heightAttr) { 3227 3228 var viewBoxAttr = element.getAttribute('viewBox'), 3229 scaleX = 1, 3230 scaleY = 1, 3231 minX = 0, 3232 minY = 0, 3233 viewBoxWidth, viewBoxHeight, matrix, el; 3234 3235 if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { 3236 minX = -parseFloat(viewBoxAttr[1]), 3237 minY = -parseFloat(viewBoxAttr[2]), 3238 viewBoxWidth = parseFloat(viewBoxAttr[3]), 3239 viewBoxHeight = parseFloat(viewBoxAttr[4]); 3240 } 3241 else { 3242 return; 3243 } 3244 if (widthAttr && widthAttr !== viewBoxWidth) { 3245 scaleX = widthAttr / viewBoxWidth; 3246 } 3247 if (heightAttr && heightAttr !== viewBoxHeight) { 3248 scaleY = heightAttr / viewBoxHeight; 3249 } 3250 3251 // default is to preserve aspect ratio 3252 // preserveAspectRatio attribute to be implemented 3253 scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX); 3254 3255 if (!(scaleX !== 1 || scaleY !== 1 || minX !== 0 || minY !== 0)) { 3256 return; 3257 } 3258 matrix = ' matrix(' + scaleX + 3259 ' 0' + 3260 ' 0 ' + 3261 scaleY + ' ' + 3262 (minX * scaleX) + ' ' + 3263 (minY * scaleY) + ') '; 3264 3265 if (element.tagName === 'svg') { 3266 el = element.ownerDocument.createElement('g'); 3267 while (element.firstChild != null) { 3268 el.appendChild(element.firstChild); 3269 } 3270 element.appendChild(el); 3271 } 3272 else { 3273 el = element; 3274 matrix = el.getAttribute('transform') + matrix; 3275 } 3276 3277 el.setAttribute('transform', matrix); 3278 } 3279 3280 /** 3281 * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback 3282 * @static 3283 * @function 3284 * @memberOf fabric 3285 * @param {SVGDocument} doc SVG document to parse 3286 * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document). 3287 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 3288 */ 3289 fabric.parseSVGDocument = (function() { 3290 3291 var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/, 3292 reViewBoxTagNames = /^(symbol|image|marker|pattern|view)$/; 3293 3294 function hasAncestorWithNodeName(element, nodeName) { 3295 while (element && (element = element.parentNode)) { 3296 if (nodeName.test(element.nodeName) && !element.getAttribute('instantiated_by_use')) { 3297 return true; 3298 } 3299 } 3300 return false; 3301 } 3302 3303 return function(doc, callback, reviver) { 3304 if (!doc) { 3305 return; 3306 } 3307 3308 parseUseDirectives(doc); 3309 3310 var startTime = new Date(), 3311 svgUid = fabric.Object.__uid++, 3312 widthAttr, heightAttr, toBeParsed = false; 3313 3314 if (doc.getAttribute('width') && doc.getAttribute('width') !== '100%') { 3315 widthAttr = parseUnit(doc.getAttribute('width')); 3316 } 3317 if (doc.getAttribute('height') && doc.getAttribute('height') !== '100%') { 3318 heightAttr = parseUnit(doc.getAttribute('height')); 3319 } 3320 3321 if (!widthAttr || !heightAttr) { 3322 var viewBoxAttr = doc.getAttribute('viewBox'); 3323 if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { 3324 widthAttr = parseFloat(viewBoxAttr[3]), 3325 heightAttr = parseFloat(viewBoxAttr[4]); 3326 } 3327 else { 3328 toBeParsed = true; 3329 } 3330 } 3331 3332 addVBTransform(doc, widthAttr, heightAttr); 3333 3334 var descendants = fabric.util.toArray(doc.getElementsByTagName('*')); 3335 3336 if (descendants.length === 0 && fabric.isLikelyNode) { 3337 // we're likely in node, where "o3-xml" library fails to gEBTN("*") 3338 // https://github.com/ajaxorg/node-o3-xml/issues/21 3339 descendants = doc.selectNodes('//*[name(.)!="svg"]'); 3340 var arr = [ ]; 3341 for (var i = 0, len = descendants.length; i < len; i++) { 3342 arr[i] = descendants[i]; 3343 } 3344 descendants = arr; 3345 } 3346 3347 var elements = descendants.filter(function(el) { 3348 reViewBoxTagNames.test(el.tagName) && addVBTransform(el, 0, 0); 3349 return reAllowedSVGTagNames.test(el.tagName) && 3350 !hasAncestorWithNodeName(el, /^(?:pattern|defs|symbol)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement 3351 }); 3352 3353 if (!elements || (elements && !elements.length)) { 3354 callback && callback([], {}); 3355 return; 3356 } 3357 3358 var options = { 3359 width: widthAttr, 3360 height: heightAttr, 3361 svgUid: svgUid, 3362 toBeParsed: toBeParsed 3363 }; 3364 3365 fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); 3366 fabric.cssRules[svgUid] = fabric.getCSSRules(doc); 3367 // Precedence of rules: style > class > attribute 3368 fabric.parseElements(elements, function(instances) { 3369 fabric.documentParsingTime = new Date() - startTime; 3370 if (callback) { 3371 callback(instances, options); 3372 } 3373 }, clone(options), reviver); 3374 }; 3375 })(); 3376 3377 /** 3378 * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`) 3379 * @namespace 3380 */ 3381 var svgCache = { 3382 3383 /** 3384 * @param {String} name 3385 * @param {Function} callback 3386 */ 3387 has: function (name, callback) { 3388 callback(false); 3389 }, 3390 3391 get: function () { 3392 /* NOOP */ 3393 }, 3394 3395 set: function () { 3396 /* NOOP */ 3397 } 3398 }; 3399 3400 /** 3401 * @private 3402 */ 3403 function _enlivenCachedObject(cachedObject) { 3404 3405 var objects = cachedObject.objects, 3406 options = cachedObject.options; 3407 3408 objects = objects.map(function (o) { 3409 return fabric[capitalize(o.type)].fromObject(o); 3410 }); 3411 3412 return ({ objects: objects, options: options }); 3413 } 3414 3415 /** 3416 * @private 3417 */ 3418 function _createSVGPattern(markup, canvas, property) { 3419 if (canvas[property] && canvas[property].toSVG) { 3420 markup.push( 3421 '<pattern x="0" y="0" id="', property, 'Pattern" ', 3422 'width="', canvas[property].source.width, 3423 '" height="', canvas[property].source.height, 3424 '" patternUnits="userSpaceOnUse">', 3425 '<image x="0" y="0" ', 3426 'width="', canvas[property].source.width, 3427 '" height="', canvas[property].source.height, 3428 '" xlink:href="', canvas[property].source.src, 3429 '"></image></pattern>' 3430 ); 3431 } 3432 } 3433 3434 var reFontDeclaration = new RegExp( 3435 '(normal|italic)?\\s*(normal|small-caps)?\\s*' + 3436 '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' + 3437 fabric.reNum + 3438 '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)'); 3439 3440 extend(fabric, { 3441 /** 3442 * Parses a short font declaration, building adding its properties to a style object 3443 * @static 3444 * @function 3445 * @memberOf fabric 3446 * @param {String} value font declaration 3447 * @param {Object} oStyle definition 3448 */ 3449 parseFontDeclaration: function(value, oStyle) { 3450 var match = value.match(reFontDeclaration); 3451 3452 if (!match) { 3453 return; 3454 } 3455 var fontStyle = match[1], 3456 // font variant is not used 3457 // fontVariant = match[2], 3458 fontWeight = match[3], 3459 fontSize = match[4], 3460 lineHeight = match[5], 3461 fontFamily = match[6]; 3462 3463 if (fontStyle) { 3464 oStyle.fontStyle = fontStyle; 3465 } 3466 if (fontWeight) { 3467 oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); 3468 } 3469 if (fontSize) { 3470 oStyle.fontSize = parseUnit(fontSize); 3471 } 3472 if (fontFamily) { 3473 oStyle.fontFamily = fontFamily; 3474 } 3475 if (lineHeight) { 3476 oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; 3477 } 3478 }, 3479 3480 /** 3481 * Parses an SVG document, returning all of the gradient declarations found in it 3482 * @static 3483 * @function 3484 * @memberOf fabric 3485 * @param {SVGDocument} doc SVG document to parse 3486 * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element 3487 */ 3488 getGradientDefs: function(doc) { 3489 var linearGradientEls = doc.getElementsByTagName('linearGradient'), 3490 radialGradientEls = doc.getElementsByTagName('radialGradient'), 3491 el, i, j = 0, id, xlink, elList = [ ], 3492 gradientDefs = { }, idsToXlinkMap = { }; 3493 3494 elList.length = linearGradientEls.length + radialGradientEls.length; 3495 i = linearGradientEls.length; 3496 while (i--) { 3497 elList[j++] = linearGradientEls[i]; 3498 } 3499 i = radialGradientEls.length; 3500 while (i--) { 3501 elList[j++] = radialGradientEls[i]; 3502 } 3503 3504 while (j--) { 3505 el = elList[j]; 3506 xlink = el.getAttribute('xlink:href'); 3507 id = el.getAttribute('id'); 3508 if (xlink) { 3509 idsToXlinkMap[id] = xlink.substr(1); 3510 } 3511 gradientDefs[id] = el; 3512 } 3513 3514 for (id in idsToXlinkMap) { 3515 var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true); 3516 el = gradientDefs[id]; 3517 while (el2.firstChild) { 3518 el.appendChild(el2.firstChild); 3519 } 3520 } 3521 return gradientDefs; 3522 }, 3523 3524 /** 3525 * Returns an object of attributes' name/value, given element and an array of attribute names; 3526 * Parses parent "g" nodes recursively upwards. 3527 * @static 3528 * @memberOf fabric 3529 * @param {DOMElement} element Element to parse 3530 * @param {Array} attributes Array of attributes to parse 3531 * @return {Object} object containing parsed attributes' names/values 3532 */ 3533 parseAttributes: function(element, attributes, svgUid) { 3534 3535 if (!element) { 3536 return; 3537 } 3538 3539 var value, 3540 parentAttributes = { }, 3541 fontSize; 3542 3543 if (typeof svgUid === 'undefined') { 3544 svgUid = element.getAttribute('svgUid'); 3545 } 3546 // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards 3547 if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) { 3548 parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); 3549 } 3550 fontSize = (parentAttributes && parentAttributes.fontSize ) || 3551 element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE; 3552 3553 var ownAttributes = attributes.reduce(function(memo, attr) { 3554 value = element.getAttribute(attr); 3555 if (value) { 3556 attr = normalizeAttr(attr); 3557 value = normalizeValue(attr, value, parentAttributes, fontSize); 3558 3559 memo[attr] = value; 3560 } 3561 return memo; 3562 }, { }); 3563 3564 // add values parsed from style, which take precedence over attributes 3565 // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) 3566 ownAttributes = extend(ownAttributes, 3567 extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element))); 3568 if (ownAttributes.font) { 3569 fabric.parseFontDeclaration(ownAttributes.font, ownAttributes); 3570 } 3571 return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); 3572 }, 3573 3574 /** 3575 * Transforms an array of svg elements to corresponding fabric.* instances 3576 * @static 3577 * @memberOf fabric 3578 * @param {Array} elements Array of elements to parse 3579 * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) 3580 * @param {Object} [options] Options object 3581 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 3582 */ 3583 parseElements: function(elements, callback, options, reviver) { 3584 new fabric.ElementsParser(elements, callback, options, reviver).parse(); 3585 }, 3586 3587 /** 3588 * Parses "style" attribute, retuning an object with values 3589 * @static 3590 * @memberOf fabric 3591 * @param {SVGElement} element Element to parse 3592 * @return {Object} Objects with values parsed from style attribute of an element 3593 */ 3594 parseStyleAttribute: function(element) { 3595 var oStyle = { }, 3596 style = element.getAttribute('style'); 3597 3598 if (!style) { 3599 return oStyle; 3600 } 3601 3602 if (typeof style === 'string') { 3603 parseStyleString(style, oStyle); 3604 } 3605 else { 3606 parseStyleObject(style, oStyle); 3607 } 3608 3609 return oStyle; 3610 }, 3611 3612 /** 3613 * Parses "points" attribute, returning an array of values 3614 * @static 3615 * @memberOf fabric 3616 * @param {String} points points attribute string 3617 * @return {Array} array of points 3618 */ 3619 parsePointsAttribute: function(points) { 3620 3621 // points attribute is required and must not be empty 3622 if (!points) { 3623 return null; 3624 } 3625 3626 // replace commas with whitespace and remove bookending whitespace 3627 points = points.replace(/,/g, ' ').trim(); 3628 3629 points = points.split(/\s+/); 3630 var parsedPoints = [ ], i, len; 3631 3632 i = 0; 3633 len = points.length; 3634 for (; i < len; i+=2) { 3635 parsedPoints.push({ 3636 x: parseFloat(points[i]), 3637 y: parseFloat(points[i + 1]) 3638 }); 3639 } 3640 3641 // odd number of points is an error 3642 // if (parsedPoints.length % 2 !== 0) { 3643 // return null; 3644 // } 3645 3646 return parsedPoints; 3647 }, 3648 3649 /** 3650 * Returns CSS rules for a given SVG document 3651 * @static 3652 * @function 3653 * @memberOf fabric 3654 * @param {SVGDocument} doc SVG document to parse 3655 * @return {Object} CSS rules of this document 3656 */ 3657 getCSSRules: function(doc) { 3658 var styles = doc.getElementsByTagName('style'), 3659 allRules = { }, rules; 3660 3661 // very crude parsing of style contents 3662 for (var i = 0, len = styles.length; i < len; i++) { 3663 var styleContents = styles[i].textContent; 3664 3665 // remove comments 3666 styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); 3667 if (styleContents.trim() === '') { 3668 continue; 3669 } 3670 rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); 3671 rules = rules.map(function(rule) { return rule.trim(); }); 3672 3673 rules.forEach(function(rule) { 3674 3675 var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), 3676 ruleObj = { }, declaration = match[2].trim(), 3677 propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); 3678 3679 for (var i = 0, len = propertyValuePairs.length; i < len; i++) { 3680 var pair = propertyValuePairs[i].split(/\s*:\s*/), 3681 property = normalizeAttr(pair[0]), 3682 value = normalizeValue(property, pair[1], pair[0]); 3683 ruleObj[property] = value; 3684 } 3685 rule = match[1]; 3686 rule.split(',').forEach(function(_rule) { 3687 _rule = _rule.replace(/^svg/i, '').trim(); 3688 if (_rule === '') { 3689 return; 3690 } 3691 allRules[_rule] = fabric.util.object.clone(ruleObj); 3692 }); 3693 }); 3694 } 3695 return allRules; 3696 }, 3697 3698 /** 3699 * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) 3700 * @memberof fabric 3701 * @param {String} url 3702 * @param {Function} callback 3703 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 3704 */ 3705 loadSVGFromURL: function(url, callback, reviver) { 3706 3707 url = url.replace(/^\n\s*/, '').trim(); 3708 svgCache.has(url, function (hasUrl) { 3709 if (hasUrl) { 3710 svgCache.get(url, function (value) { 3711 var enlivedRecord = _enlivenCachedObject(value); 3712 callback(enlivedRecord.objects, enlivedRecord.options); 3713 }); 3714 } 3715 else { 3716 new fabric.util.request(url, { 3717 method: 'get', 3718 onComplete: onComplete 3719 }); 3720 } 3721 }); 3722 3723 function onComplete(r) { 3724 3725 var xml = r.responseXML; 3726 if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { 3727 xml = new ActiveXObject('Microsoft.XMLDOM'); 3728 xml.async = 'false'; 3729 //IE chokes on DOCTYPE 3730 xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, '')); 3731 } 3732 if (!xml || !xml.documentElement) { 3733 return; 3734 } 3735 3736 fabric.parseSVGDocument(xml.documentElement, function (results, options) { 3737 svgCache.set(url, { 3738 objects: fabric.util.array.invoke(results, 'toObject'), 3739 options: options 3740 }); 3741 callback(results, options); 3742 }, reviver); 3743 } 3744 }, 3745 3746 /** 3747 * Takes string corresponding to an SVG document, and parses it into a set of fabric objects 3748 * @memberof fabric 3749 * @param {String} string 3750 * @param {Function} callback 3751 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 3752 */ 3753 loadSVGFromString: function(string, callback, reviver) { 3754 string = string.trim(); 3755 var doc; 3756 if (typeof DOMParser !== 'undefined') { 3757 var parser = new DOMParser(); 3758 if (parser && parser.parseFromString) { 3759 doc = parser.parseFromString(string, 'text/xml'); 3760 } 3761 } 3762 else if (fabric.window.ActiveXObject) { 3763 doc = new ActiveXObject('Microsoft.XMLDOM'); 3764 doc.async = 'false'; 3765 // IE chokes on DOCTYPE 3766 doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, '')); 3767 } 3768 3769 fabric.parseSVGDocument(doc.documentElement, function (results, options) { 3770 callback(results, options); 3771 }, reviver); 3772 }, 3773 3774 /** 3775 * Creates markup containing SVG font faces 3776 * @param {Array} objects Array of fabric objects 3777 * @return {String} 3778 */ 3779 createSVGFontFacesMarkup: function(objects) { 3780 var markup = ''; 3781 3782 for (var i = 0, len = objects.length; i < len; i++) { 3783 if (objects[i].type !== 'text' || !objects[i].path) { 3784 continue; 3785 } 3786 3787 markup += [ 3788 //jscs:disable validateIndentation 3789 '@font-face {', 3790 'font-family: ', objects[i].fontFamily, '; ', 3791 'src: url(\'', objects[i].path, '\')', 3792 '}' 3793 //jscs:enable validateIndentation 3794 ].join(''); 3795 } 3796 3797 if (markup) { 3798 markup = [ 3799 //jscs:disable validateIndentation 3800 '<style type="text/css">', 3801 '<![CDATA[', 3802 markup, 3803 ']]>', 3804 '</style>' 3805 //jscs:enable validateIndentation 3806 ].join(''); 3807 } 3808 3809 return markup; 3810 }, 3811 3812 /** 3813 * Creates markup containing SVG referenced elements like patterns, gradients etc. 3814 * @param {fabric.Canvas} canvas instance of fabric.Canvas 3815 * @return {String} 3816 */ 3817 createSVGRefElementsMarkup: function(canvas) { 3818 var markup = [ ]; 3819 3820 _createSVGPattern(markup, canvas, 'backgroundColor'); 3821 _createSVGPattern(markup, canvas, 'overlayColor'); 3822 3823 return markup.join(''); 3824 } 3825 }); 3826 3827})(typeof exports !== 'undefined' ? exports : this); 3828 3829 3830fabric.ElementsParser = function(elements, callback, options, reviver) { 3831 this.elements = elements; 3832 this.callback = callback; 3833 this.options = options; 3834 this.reviver = reviver; 3835 this.svgUid = (options && options.svgUid) || 0; 3836}; 3837 3838fabric.ElementsParser.prototype.parse = function() { 3839 this.instances = new Array(this.elements.length); 3840 this.numElements = this.elements.length; 3841 3842 this.createObjects(); 3843}; 3844 3845fabric.ElementsParser.prototype.createObjects = function() { 3846 for (var i = 0, len = this.elements.length; i < len; i++) { 3847 this.elements[i].setAttribute('svgUid', this.svgUid); 3848 (function(_this, i) { 3849 setTimeout(function() { 3850 _this.createObject(_this.elements[i], i); 3851 }, 0); 3852 })(this, i); 3853 } 3854}; 3855 3856fabric.ElementsParser.prototype.createObject = function(el, index) { 3857 var klass = fabric[fabric.util.string.capitalize(el.tagName)]; 3858 if (klass && klass.fromElement) { 3859 try { 3860 this._createObject(klass, el, index); 3861 } 3862 catch (err) { 3863 fabric.log(err); 3864 } 3865 } 3866 else { 3867 this.checkIfDone(); 3868 } 3869}; 3870 3871fabric.ElementsParser.prototype._createObject = function(klass, el, index) { 3872 if (klass.async) { 3873 klass.fromElement(el, this.createCallback(index, el), this.options); 3874 } 3875 else { 3876 var obj = klass.fromElement(el, this.options); 3877 this.resolveGradient(obj, 'fill'); 3878 this.resolveGradient(obj, 'stroke'); 3879 this.reviver && this.reviver(el, obj); 3880 this.instances[index] = obj; 3881 this.checkIfDone(); 3882 } 3883}; 3884 3885fabric.ElementsParser.prototype.createCallback = function(index, el) { 3886 var _this = this; 3887 return function(obj) { 3888 _this.resolveGradient(obj, 'fill'); 3889 _this.resolveGradient(obj, 'stroke'); 3890 _this.reviver && _this.reviver(el, obj); 3891 _this.instances[index] = obj; 3892 _this.checkIfDone(); 3893 }; 3894}; 3895 3896fabric.ElementsParser.prototype.resolveGradient = function(obj, property) { 3897 3898 var instanceFillValue = obj.get(property); 3899 if (!(/^url\(/).test(instanceFillValue)) { 3900 return; 3901 } 3902 var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); 3903 if (fabric.gradientDefs[this.svgUid][gradientId]) { 3904 obj.set(property, 3905 fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj)); 3906 } 3907}; 3908 3909fabric.ElementsParser.prototype.checkIfDone = function() { 3910 if (--this.numElements === 0) { 3911 this.instances = this.instances.filter(function(el) { 3912 return el != null; 3913 }); 3914 this.callback(this.instances); 3915 } 3916}; 3917 3918 3919(function(global) { 3920 3921 'use strict'; 3922 3923 /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ 3924 3925 var fabric = global.fabric || (global.fabric = { }); 3926 3927 if (fabric.Point) { 3928 fabric.warn('fabric.Point is already defined'); 3929 return; 3930 } 3931 3932 fabric.Point = Point; 3933 3934 /** 3935 * Point class 3936 * @class fabric.Point 3937 * @memberOf fabric 3938 * @constructor 3939 * @param {Number} x 3940 * @param {Number} y 3941 * @return {fabric.Point} thisArg 3942 */ 3943 function Point(x, y) { 3944 this.x = x; 3945 this.y = y; 3946 } 3947 3948 Point.prototype = /** @lends fabric.Point.prototype */ { 3949 3950 constructor: Point, 3951 3952 /** 3953 * Adds another point to this one and returns another one 3954 * @param {fabric.Point} that 3955 * @return {fabric.Point} new Point instance with added values 3956 */ 3957 add: function (that) { 3958 return new Point(this.x + that.x, this.y + that.y); 3959 }, 3960 3961 /** 3962 * Adds another point to this one 3963 * @param {fabric.Point} that 3964 * @return {fabric.Point} thisArg 3965 */ 3966 addEquals: function (that) { 3967 this.x += that.x; 3968 this.y += that.y; 3969 return this; 3970 }, 3971 3972 /** 3973 * Adds value to this point and returns a new one 3974 * @param {Number} scalar 3975 * @return {fabric.Point} new Point with added value 3976 */ 3977 scalarAdd: function (scalar) { 3978 return new Point(this.x + scalar, this.y + scalar); 3979 }, 3980 3981 /** 3982 * Adds value to this point 3983 * @param {Number} scalar 3984 * @return {fabric.Point} thisArg 3985 */ 3986 scalarAddEquals: function (scalar) { 3987 this.x += scalar; 3988 this.y += scalar; 3989 return this; 3990 }, 3991 3992 /** 3993 * Subtracts another point from this point and returns a new one 3994 * @param {fabric.Point} that 3995 * @return {fabric.Point} new Point object with subtracted values 3996 */ 3997 subtract: function (that) { 3998 return new Point(this.x - that.x, this.y - that.y); 3999 }, 4000 4001 /** 4002 * Subtracts another point from this point 4003 * @param {fabric.Point} that 4004 * @return {fabric.Point} thisArg 4005 */ 4006 subtractEquals: function (that) { 4007 this.x -= that.x; 4008 this.y -= that.y; 4009 return this; 4010 }, 4011 4012 /** 4013 * Subtracts value from this point and returns a new one 4014 * @param {Number} scalar 4015 * @return {fabric.Point} 4016 */ 4017 scalarSubtract: function (scalar) { 4018 return new Point(this.x - scalar, this.y - scalar); 4019 }, 4020 4021 /** 4022 * Subtracts value from this point 4023 * @param {Number} scalar 4024 * @return {fabric.Point} thisArg 4025 */ 4026 scalarSubtractEquals: function (scalar) { 4027 this.x -= scalar; 4028 this.y -= scalar; 4029 return this; 4030 }, 4031 4032 /** 4033 * Miltiplies this point by a value and returns a new one 4034 * @param {Number} scalar 4035 * @return {fabric.Point} 4036 */ 4037 multiply: function (scalar) { 4038 return new Point(this.x * scalar, this.y * scalar); 4039 }, 4040 4041 /** 4042 * Miltiplies this point by a value 4043 * @param {Number} scalar 4044 * @return {fabric.Point} thisArg 4045 */ 4046 multiplyEquals: function (scalar) { 4047 this.x *= scalar; 4048 this.y *= scalar; 4049 return this; 4050 }, 4051 4052 /** 4053 * Divides this point by a value and returns a new one 4054 * @param {Number} scalar 4055 * @return {fabric.Point} 4056 */ 4057 divide: function (scalar) { 4058 return new Point(this.x / scalar, this.y / scalar); 4059 }, 4060 4061 /** 4062 * Divides this point by a value 4063 * @param {Number} scalar 4064 * @return {fabric.Point} thisArg 4065 */ 4066 divideEquals: function (scalar) { 4067 this.x /= scalar; 4068 this.y /= scalar; 4069 return this; 4070 }, 4071 4072 /** 4073 * Returns true if this point is equal to another one 4074 * @param {fabric.Point} that 4075 * @return {Boolean} 4076 */ 4077 eq: function (that) { 4078 return (this.x === that.x && this.y === that.y); 4079 }, 4080 4081 /** 4082 * Returns true if this point is less than another one 4083 * @param {fabric.Point} that 4084 * @return {Boolean} 4085 */ 4086 lt: function (that) { 4087 return (this.x < that.x && this.y < that.y); 4088 }, 4089 4090 /** 4091 * Returns true if this point is less than or equal to another one 4092 * @param {fabric.Point} that 4093 * @return {Boolean} 4094 */ 4095 lte: function (that) { 4096 return (this.x <= that.x && this.y <= that.y); 4097 }, 4098 4099 /** 4100 4101 * Returns true if this point is greater another one 4102 * @param {fabric.Point} that 4103 * @return {Boolean} 4104 */ 4105 gt: function (that) { 4106 return (this.x > that.x && this.y > that.y); 4107 }, 4108 4109 /** 4110 * Returns true if this point is greater than or equal to another one 4111 * @param {fabric.Point} that 4112 * @return {Boolean} 4113 */ 4114 gte: function (that) { 4115 return (this.x >= that.x && this.y >= that.y); 4116 }, 4117 4118 /** 4119 * Returns new point which is the result of linear interpolation with this one and another one 4120 * @param {fabric.Point} that 4121 * @param {Number} t 4122 * @return {fabric.Point} 4123 */ 4124 lerp: function (that, t) { 4125 return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); 4126 }, 4127 4128 /** 4129 * Returns distance from this point and another one 4130 * @param {fabric.Point} that 4131 * @return {Number} 4132 */ 4133 distanceFrom: function (that) { 4134 var dx = this.x - that.x, 4135 dy = this.y - that.y; 4136 return Math.sqrt(dx * dx + dy * dy); 4137 }, 4138 4139 /** 4140 * Returns the point between this point and another one 4141 * @param {fabric.Point} that 4142 * @return {fabric.Point} 4143 */ 4144 midPointFrom: function (that) { 4145 return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2); 4146 }, 4147 4148 /** 4149 * Returns a new point which is the min of this and another one 4150 * @param {fabric.Point} that 4151 * @return {fabric.Point} 4152 */ 4153 min: function (that) { 4154 return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); 4155 }, 4156 4157 /** 4158 * Returns a new point which is the max of this and another one 4159 * @param {fabric.Point} that 4160 * @return {fabric.Point} 4161 */ 4162 max: function (that) { 4163 return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); 4164 }, 4165 4166 /** 4167 * Returns string representation of this point 4168 * @return {String} 4169 */ 4170 toString: function () { 4171 return this.x + ',' + this.y; 4172 }, 4173 4174 /** 4175 * Sets x/y of this point 4176 * @param {Number} x 4177 * @param {Number} y 4178 */ 4179 setXY: function (x, y) { 4180 this.x = x; 4181 this.y = y; 4182 }, 4183 4184 /** 4185 * Sets x/y of this point from another point 4186 * @param {fabric.Point} that 4187 */ 4188 setFromPoint: function (that) { 4189 this.x = that.x; 4190 this.y = that.y; 4191 }, 4192 4193 /** 4194 * Swaps x/y of this point and another point 4195 * @param {fabric.Point} that 4196 */ 4197 swap: function (that) { 4198 var x = this.x, 4199 y = this.y; 4200 this.x = that.x; 4201 this.y = that.y; 4202 that.x = x; 4203 that.y = y; 4204 } 4205 }; 4206 4207})(typeof exports !== 'undefined' ? exports : this); 4208 4209 4210(function(global) { 4211 4212 'use strict'; 4213 4214 /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ 4215 var fabric = global.fabric || (global.fabric = { }); 4216 4217 if (fabric.Intersection) { 4218 fabric.warn('fabric.Intersection is already defined'); 4219 return; 4220 } 4221 4222 /** 4223 * Intersection class 4224 * @class fabric.Intersection 4225 * @memberOf fabric 4226 * @constructor 4227 */ 4228 function Intersection(status) { 4229 this.status = status; 4230 this.points = []; 4231 } 4232 4233 fabric.Intersection = Intersection; 4234 4235 fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { 4236 4237 /** 4238 * Appends a point to intersection 4239 * @param {fabric.Point} point 4240 */ 4241 appendPoint: function (point) { 4242 this.points.push(point); 4243 }, 4244 4245 /** 4246 * Appends points to intersection 4247 * @param {Array} points 4248 */ 4249 appendPoints: function (points) { 4250 this.points = this.points.concat(points); 4251 } 4252 }; 4253 4254 /** 4255 * Checks if one line intersects another 4256 * @static 4257 * @param {fabric.Point} a1 4258 * @param {fabric.Point} a2 4259 * @param {fabric.Point} b1 4260 * @param {fabric.Point} b2 4261 * @return {fabric.Intersection} 4262 */ 4263 fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { 4264 var result, 4265 uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), 4266 ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), 4267 uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); 4268 if (uB !== 0) { 4269 var ua = uaT / uB, 4270 ub = ubT / uB; 4271 if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { 4272 result = new Intersection('Intersection'); 4273 result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); 4274 } 4275 else { 4276 result = new Intersection(); 4277 } 4278 } 4279 else { 4280 if (uaT === 0 || ubT === 0) { 4281 result = new Intersection('Coincident'); 4282 } 4283 else { 4284 result = new Intersection('Parallel'); 4285 } 4286 } 4287 return result; 4288 }; 4289 4290 /** 4291 * Checks if line intersects polygon 4292 * @static 4293 * @param {fabric.Point} a1 4294 * @param {fabric.Point} a2 4295 * @param {Array} points 4296 * @return {fabric.Intersection} 4297 */ 4298 fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { 4299 var result = new Intersection(), 4300 length = points.length; 4301 4302 for (var i = 0; i < length; i++) { 4303 var b1 = points[i], 4304 b2 = points[(i + 1) % length], 4305 inter = Intersection.intersectLineLine(a1, a2, b1, b2); 4306 4307 result.appendPoints(inter.points); 4308 } 4309 if (result.points.length > 0) { 4310 result.status = 'Intersection'; 4311 } 4312 return result; 4313 }; 4314 4315 /** 4316 * Checks if polygon intersects another polygon 4317 * @static 4318 * @param {Array} points1 4319 * @param {Array} points2 4320 * @return {fabric.Intersection} 4321 */ 4322 fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { 4323 var result = new Intersection(), 4324 length = points1.length; 4325 4326 for (var i = 0; i < length; i++) { 4327 var a1 = points1[i], 4328 a2 = points1[(i + 1) % length], 4329 inter = Intersection.intersectLinePolygon(a1, a2, points2); 4330 4331 result.appendPoints(inter.points); 4332 } 4333 if (result.points.length > 0) { 4334 result.status = 'Intersection'; 4335 } 4336 return result; 4337 }; 4338 4339 /** 4340 * Checks if polygon intersects rectangle 4341 * @static 4342 * @param {Array} points 4343 * @param {Number} r1 4344 * @param {Number} r2 4345 * @return {fabric.Intersection} 4346 */ 4347 fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { 4348 var min = r1.min(r2), 4349 max = r1.max(r2), 4350 topRight = new fabric.Point(max.x, min.y), 4351 bottomLeft = new fabric.Point(min.x, max.y), 4352 inter1 = Intersection.intersectLinePolygon(min, topRight, points), 4353 inter2 = Intersection.intersectLinePolygon(topRight, max, points), 4354 inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), 4355 inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), 4356 result = new Intersection(); 4357 4358 result.appendPoints(inter1.points); 4359 result.appendPoints(inter2.points); 4360 result.appendPoints(inter3.points); 4361 result.appendPoints(inter4.points); 4362 4363 if (result.points.length > 0) { 4364 result.status = 'Intersection'; 4365 } 4366 return result; 4367 }; 4368 4369})(typeof exports !== 'undefined' ? exports : this); 4370 4371 4372(function(global) { 4373 4374 'use strict'; 4375 4376 var fabric = global.fabric || (global.fabric = { }); 4377 4378 if (fabric.Color) { 4379 fabric.warn('fabric.Color is already defined.'); 4380 return; 4381 } 4382 4383 /** 4384 * Color class 4385 * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; 4386 * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. 4387 * 4388 * @class fabric.Color 4389 * @param {String} color optional in hex or rgb(a) format 4390 * @return {fabric.Color} thisArg 4391 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} 4392 */ 4393 function Color(color) { 4394 if (!color) { 4395 this.setSource([0, 0, 0, 1]); 4396 } 4397 else { 4398 this._tryParsingColor(color); 4399 } 4400 } 4401 4402 fabric.Color = Color; 4403 4404 fabric.Color.prototype = /** @lends fabric.Color.prototype */ { 4405 4406 /** 4407 * @private 4408 * @param {String|Array} color Color value to parse 4409 */ 4410 _tryParsingColor: function(color) { 4411 var source; 4412 4413 if (color in Color.colorNameMap) { 4414 color = Color.colorNameMap[color]; 4415 } 4416 4417 if (color === 'transparent') { 4418 this.setSource([255, 255, 255, 0]); 4419 return; 4420 } 4421 4422 source = Color.sourceFromHex(color); 4423 4424 if (!source) { 4425 source = Color.sourceFromRgb(color); 4426 } 4427 if (!source) { 4428 source = Color.sourceFromHsl(color); 4429 } 4430 if (source) { 4431 this.setSource(source); 4432 } 4433 }, 4434 4435 /** 4436 * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a> 4437 * @private 4438 * @param {Number} r Red color value 4439 * @param {Number} g Green color value 4440 * @param {Number} b Blue color value 4441 * @return {Array} Hsl color 4442 */ 4443 _rgbToHsl: function(r, g, b) { 4444 r /= 255, g /= 255, b /= 255; 4445 4446 var h, s, l, 4447 max = fabric.util.array.max([r, g, b]), 4448 min = fabric.util.array.min([r, g, b]); 4449 4450 l = (max + min) / 2; 4451 4452 if (max === min) { 4453 h = s = 0; // achromatic 4454 } 4455 else { 4456 var d = max - min; 4457 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 4458 switch (max) { 4459 case r: 4460 h = (g - b) / d + (g < b ? 6 : 0); 4461 break; 4462 case g: 4463 h = (b - r) / d + 2; 4464 break; 4465 case b: 4466 h = (r - g) / d + 4; 4467 break; 4468 } 4469 h /= 6; 4470 } 4471 4472 return [ 4473 Math.round(h * 360), 4474 Math.round(s * 100), 4475 Math.round(l * 100) 4476 ]; 4477 }, 4478 4479 /** 4480 * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) 4481 * @return {Array} 4482 */ 4483 getSource: function() { 4484 return this._source; 4485 }, 4486 4487 /** 4488 * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) 4489 * @param {Array} source 4490 */ 4491 setSource: function(source) { 4492 this._source = source; 4493 }, 4494 4495 /** 4496 * Returns color represenation in RGB format 4497 * @return {String} ex: rgb(0-255,0-255,0-255) 4498 */ 4499 toRgb: function() { 4500 var source = this.getSource(); 4501 return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; 4502 }, 4503 4504 /** 4505 * Returns color represenation in RGBA format 4506 * @return {String} ex: rgba(0-255,0-255,0-255,0-1) 4507 */ 4508 toRgba: function() { 4509 var source = this.getSource(); 4510 return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; 4511 }, 4512 4513 /** 4514 * Returns color represenation in HSL format 4515 * @return {String} ex: hsl(0-360,0%-100%,0%-100%) 4516 */ 4517 toHsl: function() { 4518 var source = this.getSource(), 4519 hsl = this._rgbToHsl(source[0], source[1], source[2]); 4520 4521 return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; 4522 }, 4523 4524 /** 4525 * Returns color represenation in HSLA format 4526 * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) 4527 */ 4528 toHsla: function() { 4529 var source = this.getSource(), 4530 hsl = this._rgbToHsl(source[0], source[1], source[2]); 4531 4532 return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; 4533 }, 4534 4535 /** 4536 * Returns color represenation in HEX format 4537 * @return {String} ex: FF5555 4538 */ 4539 toHex: function() { 4540 var source = this.getSource(), r, g, b; 4541 4542 r = source[0].toString(16); 4543 r = (r.length === 1) ? ('0' + r) : r; 4544 4545 g = source[1].toString(16); 4546 g = (g.length === 1) ? ('0' + g) : g; 4547 4548 b = source[2].toString(16); 4549 b = (b.length === 1) ? ('0' + b) : b; 4550 4551 return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); 4552 }, 4553 4554 /** 4555 * Gets value of alpha channel for this color 4556 * @return {Number} 0-1 4557 */ 4558 getAlpha: function() { 4559 return this.getSource()[3]; 4560 }, 4561 4562 /** 4563 * Sets value of alpha channel for this color 4564 * @param {Number} alpha Alpha value 0-1 4565 * @return {fabric.Color} thisArg 4566 */ 4567 setAlpha: function(alpha) { 4568 var source = this.getSource(); 4569 source[3] = alpha; 4570 this.setSource(source); 4571 return this; 4572 }, 4573 4574 /** 4575 * Transforms color to its grayscale representation 4576 * @return {fabric.Color} thisArg 4577 */ 4578 toGrayscale: function() { 4579 var source = this.getSource(), 4580 average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), 4581 currentAlpha = source[3]; 4582 this.setSource([average, average, average, currentAlpha]); 4583 return this; 4584 }, 4585 4586 /** 4587 * Transforms color to its black and white representation 4588 * @param {Number} threshold 4589 * @return {fabric.Color} thisArg 4590 */ 4591 toBlackWhite: function(threshold) { 4592 var source = this.getSource(), 4593 average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 4594 currentAlpha = source[3]; 4595 4596 threshold = threshold || 127; 4597 4598 average = (Number(average) < Number(threshold)) ? 0 : 255; 4599 this.setSource([average, average, average, currentAlpha]); 4600 return this; 4601 }, 4602 4603 /** 4604 * Overlays color with another color 4605 * @param {String|fabric.Color} otherColor 4606 * @return {fabric.Color} thisArg 4607 */ 4608 overlayWith: function(otherColor) { 4609 if (!(otherColor instanceof Color)) { 4610 otherColor = new Color(otherColor); 4611 } 4612 4613 var result = [], 4614 alpha = this.getAlpha(), 4615 otherAlpha = 0.5, 4616 source = this.getSource(), 4617 otherSource = otherColor.getSource(); 4618 4619 for (var i = 0; i < 3; i++) { 4620 result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); 4621 } 4622 4623 result[3] = alpha; 4624 this.setSource(result); 4625 return this; 4626 } 4627 }; 4628 4629 /** 4630 * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) 4631 * @static 4632 * @field 4633 * @memberOf fabric.Color 4634 */ 4635 fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; 4636 4637 /** 4638 * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) 4639 * @static 4640 * @field 4641 * @memberOf fabric.Color 4642 */ 4643 fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; 4644 4645 /** 4646 * Regex matching color in HEX format (ex: #FF5555, 010155, aff) 4647 * @static 4648 * @field 4649 * @memberOf fabric.Color 4650 */ 4651 fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i; 4652 4653 /** 4654 * Map of the 17 basic color names with HEX code 4655 * @static 4656 * @field 4657 * @memberOf fabric.Color 4658 * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units 4659 */ 4660 fabric.Color.colorNameMap = { 4661 aqua: '#00FFFF', 4662 black: '#000000', 4663 blue: '#0000FF', 4664 fuchsia: '#FF00FF', 4665 gray: '#808080', 4666 green: '#008000', 4667 lime: '#00FF00', 4668 maroon: '#800000', 4669 navy: '#000080', 4670 olive: '#808000', 4671 orange: '#FFA500', 4672 purple: '#800080', 4673 red: '#FF0000', 4674 silver: '#C0C0C0', 4675 teal: '#008080', 4676 white: '#FFFFFF', 4677 yellow: '#FFFF00' 4678 }; 4679 4680 /** 4681 * @private 4682 * @param {Number} p 4683 * @param {Number} q 4684 * @param {Number} t 4685 * @return {Number} 4686 */ 4687 function hue2rgb(p, q, t) { 4688 if (t < 0) { 4689 t += 1; 4690 } 4691 if (t > 1) { 4692 t -= 1; 4693 } 4694 if (t < 1/6) { 4695 return p + (q - p) * 6 * t; 4696 } 4697 if (t < 1/2) { 4698 return q; 4699 } 4700 if (t < 2/3) { 4701 return p + (q - p) * (2/3 - t) * 6; 4702 } 4703 return p; 4704 } 4705 4706 /** 4707 * Returns new color object, when given a color in RGB format 4708 * @memberOf fabric.Color 4709 * @param {String} color Color value ex: rgb(0-255,0-255,0-255) 4710 * @return {fabric.Color} 4711 */ 4712 fabric.Color.fromRgb = function(color) { 4713 return Color.fromSource(Color.sourceFromRgb(color)); 4714 }; 4715 4716 /** 4717 * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format 4718 * @memberOf fabric.Color 4719 * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) 4720 * @return {Array} source 4721 */ 4722 fabric.Color.sourceFromRgb = function(color) { 4723 var match = color.match(Color.reRGBa); 4724 if (match) { 4725 var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), 4726 g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), 4727 b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); 4728 4729 return [ 4730 parseInt(r, 10), 4731 parseInt(g, 10), 4732 parseInt(b, 10), 4733 match[4] ? parseFloat(match[4]) : 1 4734 ]; 4735 } 4736 }; 4737 4738 /** 4739 * Returns new color object, when given a color in RGBA format 4740 * @static 4741 * @function 4742 * @memberOf fabric.Color 4743 * @param {String} color 4744 * @return {fabric.Color} 4745 */ 4746 fabric.Color.fromRgba = Color.fromRgb; 4747 4748 /** 4749 * Returns new color object, when given a color in HSL format 4750 * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) 4751 * @memberOf fabric.Color 4752 * @return {fabric.Color} 4753 */ 4754 fabric.Color.fromHsl = function(color) { 4755 return Color.fromSource(Color.sourceFromHsl(color)); 4756 }; 4757 4758 /** 4759 * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. 4760 * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a> 4761 * @memberOf fabric.Color 4762 * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) 4763 * @return {Array} source 4764 * @see http://http://www.w3.org/TR/css3-color/#hsl-color 4765 */ 4766 fabric.Color.sourceFromHsl = function(color) { 4767 var match = color.match(Color.reHSLa); 4768 if (!match) { 4769 return; 4770 } 4771 4772 var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, 4773 s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), 4774 l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), 4775 r, g, b; 4776 4777 if (s === 0) { 4778 r = g = b = l; 4779 } 4780 else { 4781 var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, 4782 p = l * 2 - q; 4783 4784 r = hue2rgb(p, q, h + 1/3); 4785 g = hue2rgb(p, q, h); 4786 b = hue2rgb(p, q, h - 1/3); 4787 } 4788 4789 return [ 4790 Math.round(r * 255), 4791 Math.round(g * 255), 4792 Math.round(b * 255), 4793 match[4] ? parseFloat(match[4]) : 1 4794 ]; 4795 }; 4796 4797 /** 4798 * Returns new color object, when given a color in HSLA format 4799 * @static 4800 * @function 4801 * @memberOf fabric.Color 4802 * @param {String} color 4803 * @return {fabric.Color} 4804 */ 4805 fabric.Color.fromHsla = Color.fromHsl; 4806 4807 /** 4808 * Returns new color object, when given a color in HEX format 4809 * @static 4810 * @memberOf fabric.Color 4811 * @param {String} color Color value ex: FF5555 4812 * @return {fabric.Color} 4813 */ 4814 fabric.Color.fromHex = function(color) { 4815 return Color.fromSource(Color.sourceFromHex(color)); 4816 }; 4817 4818 /** 4819 * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format 4820 * @static 4821 * @memberOf fabric.Color 4822 * @param {String} color ex: FF5555 4823 * @return {Array} source 4824 */ 4825 fabric.Color.sourceFromHex = function(color) { 4826 if (color.match(Color.reHex)) { 4827 var value = color.slice(color.indexOf('#') + 1), 4828 isShortNotation = (value.length === 3), 4829 r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), 4830 g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), 4831 b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6); 4832 4833 return [ 4834 parseInt(r, 16), 4835 parseInt(g, 16), 4836 parseInt(b, 16), 4837 1 4838 ]; 4839 } 4840 }; 4841 4842 /** 4843 * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) 4844 * @static 4845 * @memberOf fabric.Color 4846 * @param {Array} source 4847 * @return {fabric.Color} 4848 */ 4849 fabric.Color.fromSource = function(source) { 4850 var oColor = new Color(); 4851 oColor.setSource(source); 4852 return oColor; 4853 }; 4854 4855})(typeof exports !== 'undefined' ? exports : this); 4856 4857 4858(function() { 4859 4860 /* _FROM_SVG_START_ */ 4861 function getColorStop(el) { 4862 var style = el.getAttribute('style'), 4863 offset = el.getAttribute('offset'), 4864 color, colorAlpha, opacity; 4865 4866 // convert percents to absolute values 4867 offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); 4868 offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; 4869 if (style) { 4870 var keyValuePairs = style.split(/\s*;\s*/); 4871 4872 if (keyValuePairs[keyValuePairs.length - 1] === '') { 4873 keyValuePairs.pop(); 4874 } 4875 4876 for (var i = keyValuePairs.length; i--; ) { 4877 4878 var split = keyValuePairs[i].split(/\s*:\s*/), 4879 key = split[0].trim(), 4880 value = split[1].trim(); 4881 4882 if (key === 'stop-color') { 4883 color = value; 4884 } 4885 else if (key === 'stop-opacity') { 4886 opacity = value; 4887 } 4888 } 4889 } 4890 4891 if (!color) { 4892 color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; 4893 } 4894 if (!opacity) { 4895 opacity = el.getAttribute('stop-opacity'); 4896 } 4897 4898 color = new fabric.Color(color); 4899 colorAlpha = color.getAlpha(); 4900 opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); 4901 opacity *= colorAlpha; 4902 4903 return { 4904 offset: offset, 4905 color: color.toRgb(), 4906 opacity: opacity 4907 }; 4908 } 4909 4910 function getLinearCoords(el) { 4911 return { 4912 x1: el.getAttribute('x1') || 0, 4913 y1: el.getAttribute('y1') || 0, 4914 x2: el.getAttribute('x2') || '100%', 4915 y2: el.getAttribute('y2') || 0 4916 }; 4917 } 4918 4919 function getRadialCoords(el) { 4920 return { 4921 x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', 4922 y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', 4923 r1: 0, 4924 x2: el.getAttribute('cx') || '50%', 4925 y2: el.getAttribute('cy') || '50%', 4926 r2: el.getAttribute('r') || '50%' 4927 }; 4928 } 4929 /* _FROM_SVG_END_ */ 4930 4931 /** 4932 * Gradient class 4933 * @class fabric.Gradient 4934 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients} 4935 * @see {@link fabric.Gradient#initialize} for constructor definition 4936 */ 4937 fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { 4938 4939 /** 4940 * Horizontal offset for aligning gradients coming from SVG when outside pathgroups 4941 * @type Number 4942 * @default 0 4943 */ 4944 offsetX: 0, 4945 4946 /** 4947 * Vertical offset for aligning gradients coming from SVG when outside pathgroups 4948 * @type Number 4949 * @default 0 4950 */ 4951 offsetY: 0, 4952 4953 /** 4954 * Constructor 4955 * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops 4956 * @return {fabric.Gradient} thisArg 4957 */ 4958 initialize: function(options) { 4959 options || (options = { }); 4960 4961 var coords = { }; 4962 4963 this.id = fabric.Object.__uid++; 4964 this.type = options.type || 'linear'; 4965 4966 coords = { 4967 x1: options.coords.x1 || 0, 4968 y1: options.coords.y1 || 0, 4969 x2: options.coords.x2 || 0, 4970 y2: options.coords.y2 || 0 4971 }; 4972 4973 if (this.type === 'radial') { 4974 coords.r1 = options.coords.r1 || 0; 4975 coords.r2 = options.coords.r2 || 0; 4976 } 4977 this.coords = coords; 4978 this.colorStops = options.colorStops.slice(); 4979 if (options.gradientTransform) { 4980 this.gradientTransform = options.gradientTransform; 4981 } 4982 this.offsetX = options.offsetX || this.offsetX; 4983 this.offsetY = options.offsetY || this.offsetY; 4984 }, 4985 4986 /** 4987 * Adds another colorStop 4988 * @param {Object} colorStop Object with offset and color 4989 * @return {fabric.Gradient} thisArg 4990 */ 4991 addColorStop: function(colorStop) { 4992 for (var position in colorStop) { 4993 var color = new fabric.Color(colorStop[position]); 4994 this.colorStops.push({ 4995 offset: position, 4996 color: color.toRgb(), 4997 opacity: color.getAlpha() 4998 }); 4999 } 5000 return this; 5001 }, 5002 5003 /** 5004 * Returns object representation of a gradient 5005 * @return {Object} 5006 */ 5007 toObject: function() { 5008 return { 5009 type: this.type, 5010 coords: this.coords, 5011 colorStops: this.colorStops, 5012 offsetX: this.offsetX, 5013 offsetY: this.offsetY 5014 }; 5015 }, 5016 5017 /* _TO_SVG_START_ */ 5018 /** 5019 * Returns SVG representation of an gradient 5020 * @param {Object} object Object to create a gradient for 5021 * @param {Boolean} normalize Whether coords should be normalized 5022 * @return {String} SVG representation of an gradient (linear/radial) 5023 */ 5024 toSVG: function(object) { 5025 var coords = fabric.util.object.clone(this.coords), 5026 markup, commonAttributes; 5027 5028 // colorStops must be sorted ascending 5029 this.colorStops.sort(function(a, b) { 5030 return a.offset - b.offset; 5031 }); 5032 5033 if (!(object.group && object.group.type === 'path-group')) { 5034 for (var prop in coords) { 5035 if (prop === 'x1' || prop === 'x2' || prop === 'r2') { 5036 coords[prop] += this.offsetX - object.width / 2; 5037 } 5038 else if (prop === 'y1' || prop === 'y2') { 5039 coords[prop] += this.offsetY - object.height / 2; 5040 } 5041 } 5042 } 5043 5044 commonAttributes = 'id="SVGID_' + this.id + 5045 '" gradientUnits="userSpaceOnUse"'; 5046 if (this.gradientTransform) { 5047 commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" '; 5048 } 5049 if (this.type === 'linear') { 5050 markup = [ 5051 //jscs:disable validateIndentation 5052 '<linearGradient ', 5053 commonAttributes, 5054 ' x1="', coords.x1, 5055 '" y1="', coords.y1, 5056 '" x2="', coords.x2, 5057 '" y2="', coords.y2, 5058 '">\n' 5059 //jscs:enable validateIndentation 5060 ]; 5061 } 5062 else if (this.type === 'radial') { 5063 markup = [ 5064 //jscs:disable validateIndentation 5065 '<radialGradient ', 5066 commonAttributes, 5067 ' cx="', coords.x2, 5068 '" cy="', coords.y2, 5069 '" r="', coords.r2, 5070 '" fx="', coords.x1, 5071 '" fy="', coords.y1, 5072 '">\n' 5073 //jscs:enable validateIndentation 5074 ]; 5075 } 5076 5077 for (var i = 0; i < this.colorStops.length; i++) { 5078 markup.push( 5079 //jscs:disable validateIndentation 5080 '<stop ', 5081 'offset="', (this.colorStops[i].offset * 100) + '%', 5082 '" style="stop-color:', this.colorStops[i].color, 5083 (this.colorStops[i].opacity != null ? ';stop-opacity: ' + this.colorStops[i].opacity : ';'), 5084 '"/>\n' 5085 //jscs:enable validateIndentation 5086 ); 5087 } 5088 5089 markup.push((this.type === 'linear' ? '</linearGradient>\n' : '</radialGradient>\n')); 5090 5091 return markup.join(''); 5092 }, 5093 /* _TO_SVG_END_ */ 5094 5095 /** 5096 * Returns an instance of CanvasGradient 5097 * @param {CanvasRenderingContext2D} ctx Context to render on 5098 * @return {CanvasGradient} 5099 */ 5100 toLive: function(ctx, object) { 5101 var gradient, prop, coords = fabric.util.object.clone(this.coords); 5102 5103 if (!this.type) { 5104 return; 5105 } 5106 5107 if (object.group && object.group.type === 'path-group') { 5108 for (prop in coords) { 5109 if (prop === 'x1' || prop === 'x2') { 5110 coords[prop] += -this.offsetX + object.width / 2; 5111 } 5112 else if (prop === 'y1' || prop === 'y2') { 5113 coords[prop] += -this.offsetY + object.height / 2; 5114 } 5115 } 5116 } 5117 5118 if (object.type === 'text' || object.type === 'i-text') { 5119 for (prop in coords) { 5120 if (prop === 'x1' || prop === 'x2') { 5121 coords[prop] -= object.width / 2; 5122 } 5123 else if (prop === 'y1' || prop === 'y2') { 5124 coords[prop] -= object.height / 2; 5125 } 5126 } 5127 } 5128 5129 if (this.type === 'linear') { 5130 gradient = ctx.createLinearGradient( 5131 coords.x1, coords.y1, coords.x2, coords.y2); 5132 } 5133 else if (this.type === 'radial') { 5134 gradient = ctx.createRadialGradient( 5135 coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); 5136 } 5137 5138 for (var i = 0, len = this.colorStops.length; i < len; i++) { 5139 var color = this.colorStops[i].color, 5140 opacity = this.colorStops[i].opacity, 5141 offset = this.colorStops[i].offset; 5142 5143 if (typeof opacity !== 'undefined') { 5144 color = new fabric.Color(color).setAlpha(opacity).toRgba(); 5145 } 5146 gradient.addColorStop(parseFloat(offset), color); 5147 } 5148 5149 return gradient; 5150 } 5151 }); 5152 5153 fabric.util.object.extend(fabric.Gradient, { 5154 5155 /* _FROM_SVG_START_ */ 5156 /** 5157 * Returns {@link fabric.Gradient} instance from an SVG element 5158 * @static 5159 * @memberof fabric.Gradient 5160 * @param {SVGGradientElement} el SVG gradient element 5161 * @param {fabric.Object} instance 5162 * @return {fabric.Gradient} Gradient instance 5163 * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement 5164 * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement 5165 */ 5166 fromElement: function(el, instance) { 5167 5168 /** 5169 * @example: 5170 * 5171 * <linearGradient id="linearGrad1"> 5172 * <stop offset="0%" stop-color="white"/> 5173 * <stop offset="100%" stop-color="black"/> 5174 * </linearGradient> 5175 * 5176 * OR 5177 * 5178 * <linearGradient id="linearGrad2"> 5179 * <stop offset="0" style="stop-color:rgb(255,255,255)"/> 5180 * <stop offset="1" style="stop-color:rgb(0,0,0)"/> 5181 * </linearGradient> 5182 * 5183 * OR 5184 * 5185 * <radialGradient id="radialGrad1"> 5186 * <stop offset="0%" stop-color="white" stop-opacity="1" /> 5187 * <stop offset="50%" stop-color="black" stop-opacity="0.5" /> 5188 * <stop offset="100%" stop-color="white" stop-opacity="1" /> 5189 * </radialGradient> 5190 * 5191 * OR 5192 * 5193 * <radialGradient id="radialGrad2"> 5194 * <stop offset="0" stop-color="rgb(255,255,255)" /> 5195 * <stop offset="0.5" stop-color="rgb(0,0,0)" /> 5196 * <stop offset="1" stop-color="rgb(255,255,255)" /> 5197 * </radialGradient> 5198 * 5199 */ 5200 5201 var colorStopEls = el.getElementsByTagName('stop'), 5202 type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'), 5203 gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', 5204 gradientTransform = el.getAttribute('gradientTransform'), 5205 colorStops = [], 5206 coords = { }, ellipseMatrix; 5207 5208 if (type === 'linear') { 5209 coords = getLinearCoords(el); 5210 } 5211 else if (type === 'radial') { 5212 coords = getRadialCoords(el); 5213 } 5214 5215 for (var i = colorStopEls.length; i--; ) { 5216 colorStops.push(getColorStop(colorStopEls[i])); 5217 } 5218 5219 ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); 5220 5221 var gradient = new fabric.Gradient({ 5222 type: type, 5223 coords: coords, 5224 colorStops: colorStops, 5225 offsetX: -instance.left, 5226 offsetY: -instance.top 5227 }); 5228 5229 if (gradientTransform || ellipseMatrix !== '') { 5230 gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); 5231 } 5232 return gradient; 5233 }, 5234 /* _FROM_SVG_END_ */ 5235 5236 /** 5237 * Returns {@link fabric.Gradient} instance from its object representation 5238 * @static 5239 * @memberof fabric.Gradient 5240 * @param {Object} obj 5241 * @param {Object} [options] Options object 5242 */ 5243 forObject: function(obj, options) { 5244 options || (options = { }); 5245 _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); 5246 return new fabric.Gradient(options); 5247 } 5248 }); 5249 5250 /** 5251 * @private 5252 */ 5253 function _convertPercentUnitsToValues(object, options, gradientUnits) { 5254 var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = ''; 5255 for (var prop in options) { 5256 propValue = parseFloat(options[prop], 10); 5257 if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { 5258 multFactor = 0.01; 5259 } 5260 else { 5261 multFactor = 1; 5262 } 5263 if (prop === 'x1' || prop === 'x2' || prop === 'r2') { 5264 multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1; 5265 addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0; 5266 } 5267 else if (prop === 'y1' || prop === 'y2') { 5268 multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1; 5269 addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0; 5270 } 5271 options[prop] = propValue * multFactor + addFactor; 5272 } 5273 if (object.type === 'ellipse' && 5274 options.r2 !== null && 5275 gradientUnits === 'objectBoundingBox' && 5276 object.rx !== object.ry) { 5277 5278 var scaleFactor = object.ry/object.rx; 5279 ellipseMatrix = ' scale(1, ' + scaleFactor + ')'; 5280 if (options.y1) { 5281 options.y1 /= scaleFactor; 5282 } 5283 if (options.y2) { 5284 options.y2 /= scaleFactor; 5285 } 5286 } 5287 return ellipseMatrix; 5288 } 5289})(); 5290 5291 5292/** 5293 * Pattern class 5294 * @class fabric.Pattern 5295 * @see {@link http://fabricjs.com/patterns/|Pattern demo} 5296 * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo} 5297 * @see {@link fabric.Pattern#initialize} for constructor definition 5298 */ 5299fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { 5300 5301 /** 5302 * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) 5303 * @type String 5304 * @default 5305 */ 5306 repeat: 'repeat', 5307 5308 /** 5309 * Pattern horizontal offset from object's left/top corner 5310 * @type Number 5311 * @default 5312 */ 5313 offsetX: 0, 5314 5315 /** 5316 * Pattern vertical offset from object's left/top corner 5317 * @type Number 5318 * @default 5319 */ 5320 offsetY: 0, 5321 5322 /** 5323 * Constructor 5324 * @param {Object} [options] Options object 5325 * @return {fabric.Pattern} thisArg 5326 */ 5327 initialize: function(options) { 5328 options || (options = { }); 5329 5330 this.id = fabric.Object.__uid++; 5331 5332 if (options.source) { 5333 if (typeof options.source === 'string') { 5334 // function string 5335 if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') { 5336 this.source = new Function(fabric.util.getFunctionBody(options.source)); 5337 } 5338 else { 5339 // img src string 5340 var _this = this; 5341 this.source = fabric.util.createImage(); 5342 fabric.util.loadImage(options.source, function(img) { 5343 _this.source = img; 5344 }); 5345 } 5346 } 5347 else { 5348 // img element 5349 this.source = options.source; 5350 } 5351 } 5352 if (options.repeat) { 5353 this.repeat = options.repeat; 5354 } 5355 if (options.offsetX) { 5356 this.offsetX = options.offsetX; 5357 } 5358 if (options.offsetY) { 5359 this.offsetY = options.offsetY; 5360 } 5361 }, 5362 5363 /** 5364 * Returns object representation of a pattern 5365 * @return {Object} Object representation of a pattern instance 5366 */ 5367 toObject: function() { 5368 5369 var source; 5370 5371 // callback 5372 if (typeof this.source === 'function') { 5373 source = String(this.source); 5374 } 5375 // <img> element 5376 else if (typeof this.source.src === 'string') { 5377 source = this.source.src; 5378 } 5379 5380 return { 5381 source: source, 5382 repeat: this.repeat, 5383 offsetX: this.offsetX, 5384 offsetY: this.offsetY 5385 }; 5386 }, 5387 5388 /* _TO_SVG_START_ */ 5389 /** 5390 * Returns SVG representation of a pattern 5391 * @param {fabric.Object} object 5392 * @return {String} SVG representation of a pattern 5393 */ 5394 toSVG: function(object) { 5395 var patternSource = typeof this.source === 'function' ? this.source() : this.source, 5396 patternWidth = patternSource.width / object.getWidth(), 5397 patternHeight = patternSource.height / object.getHeight(), 5398 patternOffsetX = this.offsetX / object.getWidth(), 5399 patternOffsetY = this.offsetY / object.getHeight(), 5400 patternImgSrc = ''; 5401 if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') { 5402 patternHeight = 1; 5403 } 5404 if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') { 5405 patternWidth = 1; 5406 } 5407 if (patternSource.src) { 5408 patternImgSrc = patternSource.src; 5409 } 5410 else if (patternSource.toDataURL) { 5411 patternImgSrc = patternSource.toDataURL(); 5412 } 5413 5414 return '<pattern id="SVGID_' + this.id + 5415 '" x="' + patternOffsetX + 5416 '" y="' + patternOffsetY + 5417 '" width="' + patternWidth + 5418 '" height="' + patternHeight + '">\n' + 5419 '<image x="0" y="0"' + 5420 ' width="' + patternSource.width + 5421 '" height="' + patternSource.height + 5422 '" xlink:href="' + patternImgSrc + 5423 '"></image>\n' + 5424 '</pattern>\n'; 5425 }, 5426 /* _TO_SVG_END_ */ 5427 5428 /** 5429 * Returns an instance of CanvasPattern 5430 * @param {CanvasRenderingContext2D} ctx Context to create pattern 5431 * @return {CanvasPattern} 5432 */ 5433 toLive: function(ctx) { 5434 var source = typeof this.source === 'function' 5435 ? this.source() 5436 : this.source; 5437 5438 // if the image failed to load, return, and allow rest to continue loading 5439 if (!source) { 5440 return ''; 5441 } 5442 5443 // if an image 5444 if (typeof source.src !== 'undefined') { 5445 if (!source.complete) { 5446 return ''; 5447 } 5448 if (source.naturalWidth === 0 || source.naturalHeight === 0) { 5449 return ''; 5450 } 5451 } 5452 return ctx.createPattern(source, this.repeat); 5453 } 5454}); 5455 5456 5457(function(global) { 5458 5459 'use strict'; 5460 5461 var fabric = global.fabric || (global.fabric = { }), 5462 toFixed = fabric.util.toFixed; 5463 5464 if (fabric.Shadow) { 5465 fabric.warn('fabric.Shadow is already defined.'); 5466 return; 5467 } 5468 5469 /** 5470 * Shadow class 5471 * @class fabric.Shadow 5472 * @see {@link http://fabricjs.com/shadows/|Shadow demo} 5473 * @see {@link fabric.Shadow#initialize} for constructor definition 5474 */ 5475 fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { 5476 5477 /** 5478 * Shadow color 5479 * @type String 5480 * @default 5481 */ 5482 color: 'rgb(0,0,0)', 5483 5484 /** 5485 * Shadow blur 5486 * @type Number 5487 */ 5488 blur: 0, 5489 5490 /** 5491 * Shadow horizontal offset 5492 * @type Number 5493 * @default 5494 */ 5495 offsetX: 0, 5496 5497 /** 5498 * Shadow vertical offset 5499 * @type Number 5500 * @default 5501 */ 5502 offsetY: 0, 5503 5504 /** 5505 * Whether the shadow should affect stroke operations 5506 * @type Boolean 5507 * @default 5508 */ 5509 affectStroke: false, 5510 5511 /** 5512 * Indicates whether toObject should include default values 5513 * @type Boolean 5514 * @default 5515 */ 5516 includeDefaultValues: true, 5517 5518 /** 5519 * Constructor 5520 * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)") 5521 * @return {fabric.Shadow} thisArg 5522 */ 5523 initialize: function(options) { 5524 5525 if (typeof options === 'string') { 5526 options = this._parseShadow(options); 5527 } 5528 5529 for (var prop in options) { 5530 this[prop] = options[prop]; 5531 } 5532 5533 this.id = fabric.Object.__uid++; 5534 }, 5535 5536 /** 5537 * @private 5538 * @param {String} shadow Shadow value to parse 5539 * @return {Object} Shadow object with color, offsetX, offsetY and blur 5540 */ 5541 _parseShadow: function(shadow) { 5542 var shadowStr = shadow.trim(), 5543 offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], 5544 color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; 5545 5546 return { 5547 color: color.trim(), 5548 offsetX: parseInt(offsetsAndBlur[1], 10) || 0, 5549 offsetY: parseInt(offsetsAndBlur[2], 10) || 0, 5550 blur: parseInt(offsetsAndBlur[3], 10) || 0 5551 }; 5552 }, 5553 5554 /** 5555 * Returns a string representation of an instance 5556 * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow 5557 * @return {String} Returns CSS3 text-shadow declaration 5558 */ 5559 toString: function() { 5560 return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); 5561 }, 5562 5563 /* _TO_SVG_START_ */ 5564 /** 5565 * Returns SVG representation of a shadow 5566 * @param {fabric.Object} object 5567 * @return {String} SVG representation of a shadow 5568 */ 5569 toSVG: function(object) { 5570 var mode = 'SourceAlpha', fBoxX = 40, fBoxY = 40; 5571 5572 if (object && (object.fill === this.color || object.stroke === this.color)) { 5573 mode = 'SourceGraphic'; 5574 } 5575 5576 if (object.width && object.height) { 5577 //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion 5578 // we add some extra space to filter box to contain the blur ( 20 ) 5579 fBoxX = toFixed(Math.abs(this.offsetX / object.getWidth()), 2) * 100 + 20; 5580 fBoxY = toFixed(Math.abs(this.offsetY / object.getHeight()), 2) * 100 + 20; 5581 } 5582 5583 return ( 5584 '<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' + 5585 'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + '>\n' + 5586 '\t<feGaussianBlur in="' + mode + '" stdDeviation="' + 5587 toFixed(this.blur ? this.blur / 2 : 0, 3) + 5588 '" result="blurOut"></feGaussianBlur>\n' + 5589 '\t<feColorMatrix result="matrixOut" in="blurOut" type="matrix" ' + 5590 'values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.30 0" ></feColorMatrix >\n' + 5591 '\t<feOffset dx="' + this.offsetX + '" dy="' + this.offsetY + '"></feOffset>\n' + 5592 '\t<feMerge>\n' + 5593 '\t\t<feMergeNode></feMergeNode>\n' + 5594 '\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' + 5595 '\t</feMerge>\n' + 5596 '</filter>\n'); 5597 }, 5598 /* _TO_SVG_END_ */ 5599 5600 /** 5601 * Returns object representation of a shadow 5602 * @return {Object} Object representation of a shadow instance 5603 */ 5604 toObject: function() { 5605 if (this.includeDefaultValues) { 5606 return { 5607 color: this.color, 5608 blur: this.blur, 5609 offsetX: this.offsetX, 5610 offsetY: this.offsetY 5611 }; 5612 } 5613 var obj = { }, proto = fabric.Shadow.prototype; 5614 if (this.color !== proto.color) { 5615 obj.color = this.color; 5616 } 5617 if (this.blur !== proto.blur) { 5618 obj.blur = this.blur; 5619 } 5620 if (this.offsetX !== proto.offsetX) { 5621 obj.offsetX = this.offsetX; 5622 } 5623 if (this.offsetY !== proto.offsetY) { 5624 obj.offsetY = this.offsetY; 5625 } 5626 return obj; 5627 } 5628 }); 5629 5630 /** 5631 * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") 5632 * @static 5633 * @field 5634 * @memberOf fabric.Shadow 5635 */ 5636 fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; 5637 5638})(typeof exports !== 'undefined' ? exports : this); 5639 5640 5641(function () { 5642 5643 'use strict'; 5644 5645 if (fabric.StaticCanvas) { 5646 fabric.warn('fabric.StaticCanvas is already defined.'); 5647 return; 5648 } 5649 5650 // aliases for faster resolution 5651 var extend = fabric.util.object.extend, 5652 getElementOffset = fabric.util.getElementOffset, 5653 removeFromArray = fabric.util.removeFromArray, 5654 5655 CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); 5656 5657 /** 5658 * Static canvas class 5659 * @class fabric.StaticCanvas 5660 * @mixes fabric.Collection 5661 * @mixes fabric.Observable 5662 * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo} 5663 * @see {@link fabric.StaticCanvas#initialize} for constructor definition 5664 * @fires before:render 5665 * @fires after:render 5666 * @fires canvas:cleared 5667 * @fires object:added 5668 * @fires object:removed 5669 */ 5670 fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ { 5671 5672 /** 5673 * Constructor 5674 * @param {HTMLElement | String} el <canvas> element to initialize instance on 5675 * @param {Object} [options] Options object 5676 * @return {Object} thisArg 5677 */ 5678 initialize: function(el, options) { 5679 options || (options = { }); 5680 5681 this._initStatic(el, options); 5682 fabric.StaticCanvas.activeInstance = this; 5683 }, 5684 5685 /** 5686 * Background color of canvas instance. 5687 * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. 5688 * @type {(String|fabric.Pattern)} 5689 * @default 5690 */ 5691 backgroundColor: '', 5692 5693 /** 5694 * Background image of canvas instance. 5695 * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}. 5696 * <b>Backwards incompatibility note:</b> The "backgroundImageOpacity" 5697 * and "backgroundImageStretch" properties are deprecated since 1.3.9. 5698 * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. 5699 * @type fabric.Image 5700 * @default 5701 */ 5702 backgroundImage: null, 5703 5704 /** 5705 * Overlay color of canvas instance. 5706 * Should be set via {@link fabric.StaticCanvas#setOverlayColor} 5707 * @since 1.3.9 5708 * @type {(String|fabric.Pattern)} 5709 * @default 5710 */ 5711 overlayColor: '', 5712 5713 /** 5714 * Overlay image of canvas instance. 5715 * Should be set via {@link fabric.StaticCanvas#setOverlayImage}. 5716 * <b>Backwards incompatibility note:</b> The "overlayImageLeft" 5717 * and "overlayImageTop" properties are deprecated since 1.3.9. 5718 * Use {@link fabric.Image#left} and {@link fabric.Image#top}. 5719 * @type fabric.Image 5720 * @default 5721 */ 5722 overlayImage: null, 5723 5724 /** 5725 * Indicates whether toObject/toDatalessObject should include default values 5726 * @type Boolean 5727 * @default 5728 */ 5729 includeDefaultValues: true, 5730 5731 /** 5732 * Indicates whether objects' state should be saved 5733 * @type Boolean 5734 * @default 5735 */ 5736 stateful: true, 5737 5738 /** 5739 * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas. 5740 * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once 5741 * (followed by a manual rendering after addition/deletion) 5742 * @type Boolean 5743 * @default 5744 */ 5745 renderOnAddRemove: true, 5746 5747 /** 5748 * Function that determines clipping of entire canvas area 5749 * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ} 5750 * @type Function 5751 * @default 5752 */ 5753 clipTo: null, 5754 5755 /** 5756 * Indicates whether object controls (borders/controls) are rendered above overlay image 5757 * @type Boolean 5758 * @default 5759 */ 5760 controlsAboveOverlay: false, 5761 5762 /** 5763 * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas 5764 * @type Boolean 5765 * @default 5766 */ 5767 allowTouchScrolling: false, 5768 5769 /** 5770 * Indicates whether this canvas will use image smoothing, this is on by default in browsers 5771 * @type Boolean 5772 * @default 5773 */ 5774 imageSmoothingEnabled: true, 5775 5776 /** 5777 * Indicates whether objects should remain in current stack position when selected. When false objects are brought to top and rendered as part of the selection group 5778 * @type Boolean 5779 * @default 5780 */ 5781 preserveObjectStacking: false, 5782 5783 /** 5784 * The transformation (in the format of Canvas transform) which focuses the viewport 5785 * @type Array 5786 * @default 5787 */ 5788 viewportTransform: [1, 0, 0, 1, 0, 0], 5789 5790 /** 5791 * Callback; invoked right before object is about to be scaled/rotated 5792 */ 5793 onBeforeScaleRotate: function () { 5794 /* NOOP */ 5795 }, 5796 5797 /** 5798 * @private 5799 * @param {HTMLElement | String} el <canvas> element to initialize instance on 5800 * @param {Object} [options] Options object 5801 */ 5802 _initStatic: function(el, options) { 5803 this._objects = []; 5804 5805 this._createLowerCanvas(el); 5806 this._initOptions(options); 5807 this._setImageSmoothing(); 5808 5809 if (options.overlayImage) { 5810 this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); 5811 } 5812 if (options.backgroundImage) { 5813 this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); 5814 } 5815 if (options.backgroundColor) { 5816 this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); 5817 } 5818 if (options.overlayColor) { 5819 this.setOverlayColor(options.overlayColor, this.renderAll.bind(this)); 5820 } 5821 this.calcOffset(); 5822 }, 5823 5824 /** 5825 * Calculates canvas element offset relative to the document 5826 * This method is also attached as "resize" event handler of window 5827 * @return {fabric.Canvas} instance 5828 * @chainable 5829 */ 5830 calcOffset: function () { 5831 this._offset = getElementOffset(this.lowerCanvasEl); 5832 return this; 5833 }, 5834 5835 /** 5836 * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas 5837 * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to 5838 * @param {Function} callback callback to invoke when image is loaded and set as an overlay 5839 * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. 5840 * @return {fabric.Canvas} thisArg 5841 * @chainable 5842 * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} 5843 * @example <caption>Normal overlayImage with left/top = 0</caption> 5844 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 5845 * // Needed to position overlayImage at 0/0 5846 * originX: 'left', 5847 * originY: 'top' 5848 * }); 5849 * @example <caption>overlayImage with different properties</caption> 5850 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 5851 * opacity: 0.5, 5852 * angle: 45, 5853 * left: 400, 5854 * top: 400, 5855 * originX: 'left', 5856 * originY: 'top' 5857 * }); 5858 * @example <caption>Stretched overlayImage #1 - width/height correspond to canvas width/height</caption> 5859 * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) { 5860 * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); 5861 * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); 5862 * }); 5863 * @example <caption>Stretched overlayImage #2 - width/height correspond to canvas width/height</caption> 5864 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 5865 * width: canvas.width, 5866 * height: canvas.height, 5867 * // Needed to position overlayImage at 0/0 5868 * originX: 'left', 5869 * originY: 'top' 5870 * }); 5871 * @example <caption>overlayImage loaded from cross-origin</caption> 5872 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 5873 * opacity: 0.5, 5874 * angle: 45, 5875 * left: 400, 5876 * top: 400, 5877 * originX: 'left', 5878 * originY: 'top', 5879 * crossOrigin: 'anonymous' 5880 * }); 5881 */ 5882 setOverlayImage: function (image, callback, options) { 5883 return this.__setBgOverlayImage('overlayImage', image, callback, options); 5884 }, 5885 5886 /** 5887 * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas 5888 * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to 5889 * @param {Function} callback Callback to invoke when image is loaded and set as background 5890 * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. 5891 * @return {fabric.Canvas} thisArg 5892 * @chainable 5893 * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo} 5894 * @example <caption>Normal backgroundImage with left/top = 0</caption> 5895 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 5896 * // Needed to position backgroundImage at 0/0 5897 * originX: 'left', 5898 * originY: 'top' 5899 * }); 5900 * @example <caption>backgroundImage with different properties</caption> 5901 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 5902 * opacity: 0.5, 5903 * angle: 45, 5904 * left: 400, 5905 * top: 400, 5906 * originX: 'left', 5907 * originY: 'top' 5908 * }); 5909 * @example <caption>Stretched backgroundImage #1 - width/height correspond to canvas width/height</caption> 5910 * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) { 5911 * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); 5912 * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); 5913 * }); 5914 * @example <caption>Stretched backgroundImage #2 - width/height correspond to canvas width/height</caption> 5915 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 5916 * width: canvas.width, 5917 * height: canvas.height, 5918 * // Needed to position backgroundImage at 0/0 5919 * originX: 'left', 5920 * originY: 'top' 5921 * }); 5922 * @example <caption>backgroundImage loaded from cross-origin</caption> 5923 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 5924 * opacity: 0.5, 5925 * angle: 45, 5926 * left: 400, 5927 * top: 400, 5928 * originX: 'left', 5929 * originY: 'top', 5930 * crossOrigin: 'anonymous' 5931 * }); 5932 */ 5933 setBackgroundImage: function (image, callback, options) { 5934 return this.__setBgOverlayImage('backgroundImage', image, callback, options); 5935 }, 5936 5937 /** 5938 * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas 5939 * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to 5940 * @param {Function} callback Callback to invoke when background color is set 5941 * @return {fabric.Canvas} thisArg 5942 * @chainable 5943 * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} 5944 * @example <caption>Normal overlayColor - color value</caption> 5945 * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); 5946 * @example <caption>fabric.Pattern used as overlayColor</caption> 5947 * canvas.setOverlayColor({ 5948 * source: 'http://fabricjs.com/assets/escheresque_ste.png' 5949 * }, canvas.renderAll.bind(canvas)); 5950 * @example <caption>fabric.Pattern used as overlayColor with repeat and offset</caption> 5951 * canvas.setOverlayColor({ 5952 * source: 'http://fabricjs.com/assets/escheresque_ste.png', 5953 * repeat: 'repeat', 5954 * offsetX: 200, 5955 * offsetY: 100 5956 * }, canvas.renderAll.bind(canvas)); 5957 */ 5958 setOverlayColor: function(overlayColor, callback) { 5959 return this.__setBgOverlayColor('overlayColor', overlayColor, callback); 5960 }, 5961 5962 /** 5963 * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas 5964 * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to 5965 * @param {Function} callback Callback to invoke when background color is set 5966 * @return {fabric.Canvas} thisArg 5967 * @chainable 5968 * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} 5969 * @example <caption>Normal backgroundColor - color value</caption> 5970 * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); 5971 * @example <caption>fabric.Pattern used as backgroundColor</caption> 5972 * canvas.setBackgroundColor({ 5973 * source: 'http://fabricjs.com/assets/escheresque_ste.png' 5974 * }, canvas.renderAll.bind(canvas)); 5975 * @example <caption>fabric.Pattern used as backgroundColor with repeat and offset</caption> 5976 * canvas.setBackgroundColor({ 5977 * source: 'http://fabricjs.com/assets/escheresque_ste.png', 5978 * repeat: 'repeat', 5979 * offsetX: 200, 5980 * offsetY: 100 5981 * }, canvas.renderAll.bind(canvas)); 5982 */ 5983 setBackgroundColor: function(backgroundColor, callback) { 5984 return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); 5985 }, 5986 5987 /** 5988 * @private 5989 * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} 5990 */ 5991 _setImageSmoothing: function() { 5992 var ctx = this.getContext(); 5993 5994 ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; 5995 ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled; 5996 ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; 5997 ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; 5998 ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; 5999 }, 6000 6001 /** 6002 * @private 6003 * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} 6004 * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) 6005 * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to 6006 * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay 6007 * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. 6008 */ 6009 __setBgOverlayImage: function(property, image, callback, options) { 6010 if (typeof image === 'string') { 6011 fabric.util.loadImage(image, function(img) { 6012 this[property] = new fabric.Image(img, options); 6013 callback && callback(); 6014 }, this, options && options.crossOrigin); 6015 } 6016 else { 6017 options && image.setOptions(options); 6018 this[property] = image; 6019 callback && callback(); 6020 } 6021 6022 return this; 6023 }, 6024 6025 /** 6026 * @private 6027 * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} 6028 * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) 6029 * @param {(Object|String|null)} color Object with pattern information, color value or null 6030 * @param {Function} [callback] Callback is invoked when color is set 6031 */ 6032 __setBgOverlayColor: function(property, color, callback) { 6033 if (color && color.source) { 6034 var _this = this; 6035 fabric.util.loadImage(color.source, function(img) { 6036 _this[property] = new fabric.Pattern({ 6037 source: img, 6038 repeat: color.repeat, 6039 offsetX: color.offsetX, 6040 offsetY: color.offsetY 6041 }); 6042 callback && callback(); 6043 }); 6044 } 6045 else { 6046 this[property] = color; 6047 callback && callback(); 6048 } 6049 6050 return this; 6051 }, 6052 6053 /** 6054 * @private 6055 */ 6056 _createCanvasElement: function() { 6057 var element = fabric.document.createElement('canvas'); 6058 if (!element.style) { 6059 element.style = { }; 6060 } 6061 if (!element) { 6062 throw CANVAS_INIT_ERROR; 6063 } 6064 this._initCanvasElement(element); 6065 return element; 6066 }, 6067 6068 /** 6069 * @private 6070 * @param {HTMLElement} element 6071 */ 6072 _initCanvasElement: function(element) { 6073 fabric.util.createCanvasElement(element); 6074 6075 if (typeof element.getContext === 'undefined') { 6076 throw CANVAS_INIT_ERROR; 6077 } 6078 }, 6079 6080 /** 6081 * @private 6082 * @param {Object} [options] Options object 6083 */ 6084 _initOptions: function (options) { 6085 for (var prop in options) { 6086 this[prop] = options[prop]; 6087 } 6088 6089 this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; 6090 this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; 6091 6092 if (!this.lowerCanvasEl.style) { 6093 return; 6094 } 6095 6096 this.lowerCanvasEl.width = this.width; 6097 this.lowerCanvasEl.height = this.height; 6098 6099 this.lowerCanvasEl.style.width = this.width + 'px'; 6100 this.lowerCanvasEl.style.height = this.height + 'px'; 6101 6102 this.viewportTransform = this.viewportTransform.slice(); 6103 }, 6104 6105 /** 6106 * Creates a bottom canvas 6107 * @private 6108 * @param {HTMLElement} [canvasEl] 6109 */ 6110 _createLowerCanvas: function (canvasEl) { 6111 this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); 6112 this._initCanvasElement(this.lowerCanvasEl); 6113 6114 fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); 6115 6116 if (this.interactive) { 6117 this._applyCanvasStyle(this.lowerCanvasEl); 6118 } 6119 6120 this.contextContainer = this.lowerCanvasEl.getContext('2d'); 6121 }, 6122 6123 /** 6124 * Returns canvas width (in px) 6125 * @return {Number} 6126 */ 6127 getWidth: function () { 6128 return this.width; 6129 }, 6130 6131 /** 6132 * Returns canvas height (in px) 6133 * @return {Number} 6134 */ 6135 getHeight: function () { 6136 return this.height; 6137 }, 6138 6139 /** 6140 * Sets width of this canvas instance 6141 * @param {Number|String} value Value to set width to 6142 * @param {Object} [options] Options object 6143 * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions 6144 * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions 6145 * @return {fabric.Canvas} instance 6146 * @chainable true 6147 */ 6148 setWidth: function (value, options) { 6149 return this.setDimensions({ width: value }, options); 6150 }, 6151 6152 /** 6153 * Sets height of this canvas instance 6154 * @param {Number|String} value Value to set height to 6155 * @param {Object} [options] Options object 6156 * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions 6157 * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions 6158 * @return {fabric.Canvas} instance 6159 * @chainable true 6160 */ 6161 setHeight: function (value, options) { 6162 return this.setDimensions({ height: value }, options); 6163 }, 6164 6165 /** 6166 * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) 6167 * @param {Object} dimensions Object with width/height properties 6168 * @param {Number|String} [dimensions.width] Width of canvas element 6169 * @param {Number|String} [dimensions.height] Height of canvas element 6170 * @param {Object} [options] Options object 6171 * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions 6172 * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions 6173 * @return {fabric.Canvas} thisArg 6174 * @chainable 6175 */ 6176 setDimensions: function (dimensions, options) { 6177 var cssValue; 6178 6179 options = options || {}; 6180 6181 for (var prop in dimensions) { 6182 cssValue = dimensions[prop]; 6183 6184 if (!options.cssOnly) { 6185 this._setBackstoreDimension(prop, dimensions[prop]); 6186 cssValue += 'px'; 6187 } 6188 6189 if (!options.backstoreOnly) { 6190 this._setCssDimension(prop, cssValue); 6191 } 6192 } 6193 6194 if (!options.cssOnly) { 6195 this.renderAll(); 6196 } 6197 6198 this.calcOffset(); 6199 6200 return this; 6201 }, 6202 6203 /** 6204 * Helper for setting width/height 6205 * @private 6206 * @param {String} prop property (width|height) 6207 * @param {Number} value value to set property to 6208 * @return {fabric.Canvas} instance 6209 * @chainable true 6210 */ 6211 _setBackstoreDimension: function (prop, value) { 6212 this.lowerCanvasEl[prop] = value; 6213 6214 if (this.upperCanvasEl) { 6215 this.upperCanvasEl[prop] = value; 6216 } 6217 6218 if (this.cacheCanvasEl) { 6219 this.cacheCanvasEl[prop] = value; 6220 } 6221 6222 this[prop] = value; 6223 6224 return this; 6225 }, 6226 6227 /** 6228 * Helper for setting css width/height 6229 * @private 6230 * @param {String} prop property (width|height) 6231 * @param {String} value value to set property to 6232 * @return {fabric.Canvas} instance 6233 * @chainable true 6234 */ 6235 _setCssDimension: function (prop, value) { 6236 this.lowerCanvasEl.style[prop] = value; 6237 6238 if (this.upperCanvasEl) { 6239 this.upperCanvasEl.style[prop] = value; 6240 } 6241 6242 if (this.wrapperEl) { 6243 this.wrapperEl.style[prop] = value; 6244 } 6245 6246 return this; 6247 }, 6248 6249 /** 6250 * Returns canvas zoom level 6251 * @return {Number} 6252 */ 6253 getZoom: function () { 6254 return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); 6255 }, 6256 6257 /** 6258 * Sets viewport transform of this canvas instance 6259 * @param {Array} vpt the transform in the form of context.transform 6260 * @return {fabric.Canvas} instance 6261 * @chainable true 6262 */ 6263 setViewportTransform: function (vpt) { 6264 var activeGroup = this.getActiveGroup(); 6265 this.viewportTransform = vpt; 6266 this.renderAll(); 6267 for (var i = 0, len = this._objects.length; i < len; i++) { 6268 this._objects[i].setCoords(); 6269 } 6270 if (activeGroup) { 6271 activeGroup.setCoords(); 6272 } 6273 return this; 6274 }, 6275 6276 /** 6277 * Sets zoom level of this canvas instance, zoom centered around point 6278 * @param {fabric.Point} point to zoom with respect to 6279 * @param {Number} value to set zoom to, less than 1 zooms out 6280 * @return {fabric.Canvas} instance 6281 * @chainable true 6282 */ 6283 zoomToPoint: function (point, value) { 6284 // TODO: just change the scale, preserve other transformations 6285 var before = point; 6286 point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform)); 6287 this.viewportTransform[0] = value; 6288 this.viewportTransform[3] = value; 6289 var after = fabric.util.transformPoint(point, this.viewportTransform); 6290 this.viewportTransform[4] += before.x - after.x; 6291 this.viewportTransform[5] += before.y - after.y; 6292 this.renderAll(); 6293 for (var i = 0, len = this._objects.length; i < len; i++) { 6294 this._objects[i].setCoords(); 6295 } 6296 return this; 6297 }, 6298 6299 /** 6300 * Sets zoom level of this canvas instance 6301 * @param {Number} value to set zoom to, less than 1 zooms out 6302 * @return {fabric.Canvas} instance 6303 * @chainable true 6304 */ 6305 setZoom: function (value) { 6306 this.zoomToPoint(new fabric.Point(0, 0), value); 6307 return this; 6308 }, 6309 6310 /** 6311 * Pan viewport so as to place point at top left corner of canvas 6312 * @param {fabric.Point} point to move to 6313 * @return {fabric.Canvas} instance 6314 * @chainable true 6315 */ 6316 absolutePan: function (point) { 6317 this.viewportTransform[4] = -point.x; 6318 this.viewportTransform[5] = -point.y; 6319 this.renderAll(); 6320 for (var i = 0, len = this._objects.length; i < len; i++) { 6321 this._objects[i].setCoords(); 6322 } 6323 return this; 6324 }, 6325 6326 /** 6327 * Pans viewpoint relatively 6328 * @param {fabric.Point} point (position vector) to move by 6329 * @return {fabric.Canvas} instance 6330 * @chainable true 6331 */ 6332 relativePan: function (point) { 6333 return this.absolutePan(new fabric.Point( 6334 -point.x - this.viewportTransform[4], 6335 -point.y - this.viewportTransform[5] 6336 )); 6337 }, 6338 6339 /** 6340 * Returns <canvas> element corresponding to this instance 6341 * @return {HTMLCanvasElement} 6342 */ 6343 getElement: function () { 6344 return this.lowerCanvasEl; 6345 }, 6346 6347 /** 6348 * Returns currently selected object, if any 6349 * @return {fabric.Object} 6350 */ 6351 getActiveObject: function() { 6352 return null; 6353 }, 6354 6355 /** 6356 * Returns currently selected group of object, if any 6357 * @return {fabric.Group} 6358 */ 6359 getActiveGroup: function() { 6360 return null; 6361 }, 6362 6363 /** 6364 * Given a context, renders an object on that context 6365 * @param {CanvasRenderingContext2D} ctx Context to render object on 6366 * @param {fabric.Object} object Object to render 6367 * @private 6368 */ 6369 _draw: function (ctx, object) { 6370 if (!object) { 6371 return; 6372 } 6373 6374 ctx.save(); 6375 var v = this.viewportTransform; 6376 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 6377 if (this._shouldRenderObject(object)) { 6378 object.render(ctx); 6379 } 6380 ctx.restore(); 6381 if (!this.controlsAboveOverlay) { 6382 object._renderControls(ctx); 6383 } 6384 }, 6385 6386 _shouldRenderObject: function(object) { 6387 if (!object) { 6388 return false; 6389 } 6390 return (object !== this.getActiveGroup() || !this.preserveObjectStacking); 6391 }, 6392 6393 /** 6394 * @private 6395 * @param {fabric.Object} obj Object that was added 6396 */ 6397 _onObjectAdded: function(obj) { 6398 this.stateful && obj.setupState(); 6399 obj.canvas = this; 6400 obj.setCoords(); 6401 this.fire('object:added', { target: obj }); 6402 obj.fire('added'); 6403 }, 6404 6405 /** 6406 * @private 6407 * @param {fabric.Object} obj Object that was removed 6408 */ 6409 _onObjectRemoved: function(obj) { 6410 // removing active object should fire "selection:cleared" events 6411 if (this.getActiveObject() === obj) { 6412 this.fire('before:selection:cleared', { target: obj }); 6413 this._discardActiveObject(); 6414 this.fire('selection:cleared'); 6415 } 6416 6417 this.fire('object:removed', { target: obj }); 6418 obj.fire('removed'); 6419 }, 6420 6421 /** 6422 * Clears specified context of canvas element 6423 * @param {CanvasRenderingContext2D} ctx Context to clear 6424 * @return {fabric.Canvas} thisArg 6425 * @chainable 6426 */ 6427 clearContext: function(ctx) { 6428 ctx.clearRect(0, 0, this.width, this.height); 6429 return this; 6430 }, 6431 6432 /** 6433 * Returns context of canvas where objects are drawn 6434 * @return {CanvasRenderingContext2D} 6435 */ 6436 getContext: function () { 6437 return this.contextContainer; 6438 }, 6439 6440 /** 6441 * Clears all contexts (background, main, top) of an instance 6442 * @return {fabric.Canvas} thisArg 6443 * @chainable 6444 */ 6445 clear: function () { 6446 this._objects.length = 0; 6447 if (this.discardActiveGroup) { 6448 this.discardActiveGroup(); 6449 } 6450 if (this.discardActiveObject) { 6451 this.discardActiveObject(); 6452 } 6453 this.clearContext(this.contextContainer); 6454 if (this.contextTop) { 6455 this.clearContext(this.contextTop); 6456 } 6457 this.fire('canvas:cleared'); 6458 this.renderAll(); 6459 return this; 6460 }, 6461 6462 /** 6463 * Renders both the top canvas and the secondary container canvas. 6464 * @param {Boolean} [allOnTop] Whether we want to force all images to be rendered on the top canvas 6465 * @return {fabric.Canvas} instance 6466 * @chainable 6467 */ 6468 renderAll: function (allOnTop) { 6469 var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'], 6470 activeGroup = this.getActiveGroup(); 6471 6472 if (this.contextTop && this.selection && !this._groupSelector) { 6473 this.clearContext(this.contextTop); 6474 } 6475 6476 if (!allOnTop) { 6477 this.clearContext(canvasToDrawOn); 6478 } 6479 6480 this.fire('before:render'); 6481 6482 if (this.clipTo) { 6483 fabric.util.clipContext(this, canvasToDrawOn); 6484 } 6485 6486 this._renderBackground(canvasToDrawOn); 6487 this._renderObjects(canvasToDrawOn, activeGroup); 6488 this._renderActiveGroup(canvasToDrawOn, activeGroup); 6489 6490 if (this.clipTo) { 6491 canvasToDrawOn.restore(); 6492 } 6493 6494 this._renderOverlay(canvasToDrawOn); 6495 6496 if (this.controlsAboveOverlay && this.interactive) { 6497 this.drawControls(canvasToDrawOn); 6498 } 6499 6500 this.fire('after:render'); 6501 6502 return this; 6503 }, 6504 6505 /** 6506 * @private 6507 * @param {CanvasRenderingContext2D} ctx Context to render on 6508 * @param {fabric.Group} activeGroup 6509 */ 6510 _renderObjects: function(ctx, activeGroup) { 6511 var i, length; 6512 6513 // fast path 6514 if (!activeGroup || this.preserveObjectStacking) { 6515 for (i = 0, length = this._objects.length; i < length; ++i) { 6516 this._draw(ctx, this._objects[i]); 6517 } 6518 } 6519 else { 6520 for (i = 0, length = this._objects.length; i < length; ++i) { 6521 if (this._objects[i] && !activeGroup.contains(this._objects[i])) { 6522 this._draw(ctx, this._objects[i]); 6523 } 6524 } 6525 } 6526 }, 6527 6528 /** 6529 * @private 6530 * @param {CanvasRenderingContext2D} ctx Context to render on 6531 * @param {fabric.Group} activeGroup 6532 */ 6533 _renderActiveGroup: function(ctx, activeGroup) { 6534 6535 // delegate rendering to group selection (if one exists) 6536 if (activeGroup) { 6537 6538 //Store objects in group preserving order, then replace 6539 var sortedObjects = []; 6540 this.forEachObject(function (object) { 6541 if (activeGroup.contains(object)) { 6542 sortedObjects.push(object); 6543 } 6544 }); 6545 activeGroup._set('objects', sortedObjects); 6546 this._draw(ctx, activeGroup); 6547 } 6548 }, 6549 6550 /** 6551 * @private 6552 * @param {CanvasRenderingContext2D} ctx Context to render on 6553 */ 6554 _renderBackground: function(ctx) { 6555 if (this.backgroundColor) { 6556 ctx.fillStyle = this.backgroundColor.toLive 6557 ? this.backgroundColor.toLive(ctx) 6558 : this.backgroundColor; 6559 6560 ctx.fillRect( 6561 this.backgroundColor.offsetX || 0, 6562 this.backgroundColor.offsetY || 0, 6563 this.width, 6564 this.height); 6565 } 6566 if (this.backgroundImage) { 6567 this._draw(ctx, this.backgroundImage); 6568 } 6569 }, 6570 6571 /** 6572 * @private 6573 * @param {CanvasRenderingContext2D} ctx Context to render on 6574 */ 6575 _renderOverlay: function(ctx) { 6576 if (this.overlayColor) { 6577 ctx.fillStyle = this.overlayColor.toLive 6578 ? this.overlayColor.toLive(ctx) 6579 : this.overlayColor; 6580 6581 ctx.fillRect( 6582 this.overlayColor.offsetX || 0, 6583 this.overlayColor.offsetY || 0, 6584 this.width, 6585 this.height); 6586 } 6587 if (this.overlayImage) { 6588 this._draw(ctx, this.overlayImage); 6589 } 6590 }, 6591 6592 /** 6593 * Method to render only the top canvas. 6594 * Also used to render the group selection box. 6595 * @return {fabric.Canvas} thisArg 6596 * @chainable 6597 */ 6598 renderTop: function () { 6599 var ctx = this.contextTop || this.contextContainer; 6600 this.clearContext(ctx); 6601 6602 // we render the top context - last object 6603 if (this.selection && this._groupSelector) { 6604 this._drawSelection(); 6605 } 6606 6607 // delegate rendering to group selection if one exists 6608 // used for drawing selection borders/controls 6609 var activeGroup = this.getActiveGroup(); 6610 if (activeGroup) { 6611 activeGroup.render(ctx); 6612 } 6613 6614 this._renderOverlay(ctx); 6615 6616 this.fire('after:render'); 6617 6618 return this; 6619 }, 6620 6621 /** 6622 * Returns coordinates of a center of canvas. 6623 * Returned value is an object with top and left properties 6624 * @return {Object} object with "top" and "left" number values 6625 */ 6626 getCenter: function () { 6627 return { 6628 top: this.getHeight() / 2, 6629 left: this.getWidth() / 2 6630 }; 6631 }, 6632 6633 /** 6634 * Centers object horizontally. 6635 * You might need to call `setCoords` on an object after centering, to update controls area. 6636 * @param {fabric.Object} object Object to center horizontally 6637 * @return {fabric.Canvas} thisArg 6638 */ 6639 centerObjectH: function (object) { 6640 this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); 6641 this.renderAll(); 6642 return this; 6643 }, 6644 6645 /** 6646 * Centers object vertically. 6647 * You might need to call `setCoords` on an object after centering, to update controls area. 6648 * @param {fabric.Object} object Object to center vertically 6649 * @return {fabric.Canvas} thisArg 6650 * @chainable 6651 */ 6652 centerObjectV: function (object) { 6653 this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); 6654 this.renderAll(); 6655 return this; 6656 }, 6657 6658 /** 6659 * Centers object vertically and horizontally. 6660 * You might need to call `setCoords` on an object after centering, to update controls area. 6661 * @param {fabric.Object} object Object to center vertically and horizontally 6662 * @return {fabric.Canvas} thisArg 6663 * @chainable 6664 */ 6665 centerObject: function(object) { 6666 var center = this.getCenter(); 6667 6668 this._centerObject(object, new fabric.Point(center.left, center.top)); 6669 this.renderAll(); 6670 return this; 6671 }, 6672 6673 /** 6674 * @private 6675 * @param {fabric.Object} object Object to center 6676 * @param {fabric.Point} center Center point 6677 * @return {fabric.Canvas} thisArg 6678 * @chainable 6679 */ 6680 _centerObject: function(object, center) { 6681 object.setPositionByOrigin(center, 'center', 'center'); 6682 return this; 6683 }, 6684 6685 /** 6686 * Returs dataless JSON representation of canvas 6687 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 6688 * @return {String} json string 6689 */ 6690 toDatalessJSON: function (propertiesToInclude) { 6691 return this.toDatalessObject(propertiesToInclude); 6692 }, 6693 6694 /** 6695 * Returns object representation of canvas 6696 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 6697 * @return {Object} object representation of an instance 6698 */ 6699 toObject: function (propertiesToInclude) { 6700 return this._toObjectMethod('toObject', propertiesToInclude); 6701 }, 6702 6703 /** 6704 * Returns dataless object representation of canvas 6705 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 6706 * @return {Object} object representation of an instance 6707 */ 6708 toDatalessObject: function (propertiesToInclude) { 6709 return this._toObjectMethod('toDatalessObject', propertiesToInclude); 6710 }, 6711 6712 /** 6713 * @private 6714 */ 6715 _toObjectMethod: function (methodName, propertiesToInclude) { 6716 6717 var data = { 6718 objects: this._toObjects(methodName, propertiesToInclude) 6719 }; 6720 6721 extend(data, this.__serializeBgOverlay()); 6722 6723 fabric.util.populateWithProperties(this, data, propertiesToInclude); 6724 6725 return data; 6726 }, 6727 6728 /** 6729 * @private 6730 */ 6731 _toObjects: function(methodName, propertiesToInclude) { 6732 return this.getObjects().map(function(instance) { 6733 return this._toObject(instance, methodName, propertiesToInclude); 6734 }, this); 6735 }, 6736 6737 /** 6738 * @private 6739 */ 6740 _toObject: function(instance, methodName, propertiesToInclude) { 6741 var originalValue; 6742 6743 if (!this.includeDefaultValues) { 6744 originalValue = instance.includeDefaultValues; 6745 instance.includeDefaultValues = false; 6746 } 6747 6748 //If the object is part of the current selection group, it should 6749 //be transformed appropriately 6750 //i.e. it should be serialised as it would appear if the selection group 6751 //were to be destroyed. 6752 var originalProperties = this._realizeGroupTransformOnObject(instance), 6753 object = instance[methodName](propertiesToInclude); 6754 if (!this.includeDefaultValues) { 6755 instance.includeDefaultValues = originalValue; 6756 } 6757 6758 //Undo the damage we did by changing all of its properties 6759 this._unwindGroupTransformOnObject(instance, originalProperties); 6760 6761 return object; 6762 }, 6763 6764 /** 6765 * Realises an object's group transformation on it 6766 * @private 6767 * @param {fabric.Object} [instance] the object to transform (gets mutated) 6768 * @returns the original values of instance which were changed 6769 */ 6770 _realizeGroupTransformOnObject: function(instance) { 6771 var layoutProps = ['angle', 'flipX', 'flipY', 'height', 'left', 'scaleX', 'scaleY', 'top', 'width']; 6772 if (instance.group && instance.group === this.getActiveGroup()) { 6773 //Copy all the positionally relevant properties across now 6774 var originalValues = {}; 6775 layoutProps.forEach(function(prop) { 6776 originalValues[prop] = instance[prop]; 6777 }); 6778 this.getActiveGroup().realizeTransform(instance); 6779 return originalValues; 6780 } 6781 else { 6782 return null; 6783 } 6784 }, 6785 6786 /* 6787 * Restores the changed properties of instance 6788 * @private 6789 * @param {fabric.Object} [instance] the object to un-transform (gets mutated) 6790 * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject 6791 */ 6792 _unwindGroupTransformOnObject: function(instance, originalValues) { 6793 if (originalValues) { 6794 instance.set(originalValues); 6795 } 6796 }, 6797 6798 /** 6799 * @private 6800 */ 6801 __serializeBgOverlay: function() { 6802 var data = { 6803 background: (this.backgroundColor && this.backgroundColor.toObject) 6804 ? this.backgroundColor.toObject() 6805 : this.backgroundColor 6806 }; 6807 6808 if (this.overlayColor) { 6809 data.overlay = this.overlayColor.toObject 6810 ? this.overlayColor.toObject() 6811 : this.overlayColor; 6812 } 6813 if (this.backgroundImage) { 6814 data.backgroundImage = this.backgroundImage.toObject(); 6815 } 6816 if (this.overlayImage) { 6817 data.overlayImage = this.overlayImage.toObject(); 6818 } 6819 6820 return data; 6821 }, 6822 6823 /* _TO_SVG_START_ */ 6824 /** 6825 * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, 6826 * a zoomed canvas will then produce zoomed SVG output. 6827 * @type Boolean 6828 * @default 6829 */ 6830 svgViewportTransformation: true, 6831 6832 /** 6833 * Returns SVG representation of canvas 6834 * @function 6835 * @param {Object} [options] Options object for SVG output 6836 * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included 6837 * @param {Object} [options.viewBox] SVG viewbox object 6838 * @param {Number} [options.viewBox.x] x-cooridnate of viewbox 6839 * @param {Number} [options.viewBox.y] y-coordinate of viewbox 6840 * @param {Number} [options.viewBox.width] Width of viewbox 6841 * @param {Number} [options.viewBox.height] Height of viewbox 6842 * @param {String} [options.encoding=UTF-8] Encoding of SVG output 6843 * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. 6844 * @return {String} SVG string 6845 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} 6846 * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} 6847 * @example <caption>Normal SVG output</caption> 6848 * var svg = canvas.toSVG(); 6849 * @example <caption>SVG output without preamble (without <?xml ../>)</caption> 6850 * var svg = canvas.toSVG({suppressPreamble: true}); 6851 * @example <caption>SVG output with viewBox attribute</caption> 6852 * var svg = canvas.toSVG({ 6853 * viewBox: { 6854 * x: 100, 6855 * y: 100, 6856 * width: 200, 6857 * height: 300 6858 * } 6859 * }); 6860 * @example <caption>SVG output with different encoding (default: UTF-8)</caption> 6861 * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); 6862 * @example <caption>Modify SVG output with reviver function</caption> 6863 * var svg = canvas.toSVG(null, function(svg) { 6864 * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); 6865 * }); 6866 */ 6867 toSVG: function(options, reviver) { 6868 options || (options = { }); 6869 6870 var markup = []; 6871 6872 this._setSVGPreamble(markup, options); 6873 this._setSVGHeader(markup, options); 6874 6875 this._setSVGBgOverlayColor(markup, 'backgroundColor'); 6876 this._setSVGBgOverlayImage(markup, 'backgroundImage'); 6877 6878 this._setSVGObjects(markup, reviver); 6879 6880 this._setSVGBgOverlayColor(markup, 'overlayColor'); 6881 this._setSVGBgOverlayImage(markup, 'overlayImage'); 6882 6883 markup.push('</svg>'); 6884 6885 return markup.join(''); 6886 }, 6887 6888 /** 6889 * @private 6890 */ 6891 _setSVGPreamble: function(markup, options) { 6892 if (!options.suppressPreamble) { 6893 markup.push( 6894 '<?xml version="1.0" encoding="', (options.encoding || 'UTF-8'), '" standalone="no" ?>', 6895 '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ', 6896 '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' 6897 ); 6898 } 6899 }, 6900 6901 /** 6902 * @private 6903 */ 6904 _setSVGHeader: function(markup, options) { 6905 var width, height, vpt; 6906 6907 if (options.viewBox) { 6908 width = options.viewBox.width; 6909 height = options.viewBox.height; 6910 } 6911 else { 6912 width = this.width; 6913 height = this.height; 6914 if (!this.svgViewportTransformation) { 6915 vpt = this.viewportTransform; 6916 width /= vpt[0]; 6917 height /= vpt[3]; 6918 } 6919 } 6920 6921 markup.push( 6922 '<svg ', 6923 'xmlns="http://www.w3.org/2000/svg" ', 6924 'xmlns:xlink="http://www.w3.org/1999/xlink" ', 6925 'version="1.1" ', 6926 'width="', width, '" ', 6927 'height="', height, '" ', 6928 (this.backgroundColor && !this.backgroundColor.toLive 6929 ? 'style="background-color: ' + this.backgroundColor + '" ' 6930 : null), 6931 (options.viewBox 6932 ? 'viewBox="' + 6933 options.viewBox.x + ' ' + 6934 options.viewBox.y + ' ' + 6935 options.viewBox.width + ' ' + 6936 options.viewBox.height + '" ' 6937 : null), 6938 'xml:space="preserve">', 6939 '<desc>Created with Fabric.js ', fabric.version, '</desc>', 6940 '<defs>', 6941 fabric.createSVGFontFacesMarkup(this.getObjects()), 6942 fabric.createSVGRefElementsMarkup(this), 6943 '</defs>' 6944 ); 6945 }, 6946 6947 /** 6948 * @private 6949 */ 6950 _setSVGObjects: function(markup, reviver) { 6951 for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { 6952 var instance = objects[i], 6953 //If the object is in a selection group, simulate what would happen to that 6954 //object when the group is deselected 6955 originalProperties = this._realizeGroupTransformOnObject(instance); 6956 markup.push(instance.toSVG(reviver)); 6957 this._unwindGroupTransformOnObject(instance, originalProperties); 6958 } 6959 }, 6960 6961 /** 6962 * @private 6963 */ 6964 _setSVGBgOverlayImage: function(markup, property) { 6965 if (this[property] && this[property].toSVG) { 6966 markup.push(this[property].toSVG()); 6967 } 6968 }, 6969 6970 /** 6971 * @private 6972 */ 6973 _setSVGBgOverlayColor: function(markup, property) { 6974 if (this[property] && this[property].source) { 6975 markup.push( 6976 '<rect x="', this[property].offsetX, '" y="', this[property].offsetY, '" ', 6977 'width="', 6978 (this[property].repeat === 'repeat-y' || this[property].repeat === 'no-repeat' 6979 ? this[property].source.width 6980 : this.width), 6981 '" height="', 6982 (this[property].repeat === 'repeat-x' || this[property].repeat === 'no-repeat' 6983 ? this[property].source.height 6984 : this.height), 6985 '" fill="url(#' + property + 'Pattern)"', 6986 '></rect>' 6987 ); 6988 } 6989 else if (this[property] && property === 'overlayColor') { 6990 markup.push( 6991 '<rect x="0" y="0" ', 6992 'width="', this.width, 6993 '" height="', this.height, 6994 '" fill="', this[property], '"', 6995 '></rect>' 6996 ); 6997 } 6998 }, 6999 /* _TO_SVG_END_ */ 7000 7001 /** 7002 * Moves an object to the bottom of the stack of drawn objects 7003 * @param {fabric.Object} object Object to send to back 7004 * @return {fabric.Canvas} thisArg 7005 * @chainable 7006 */ 7007 sendToBack: function (object) { 7008 removeFromArray(this._objects, object); 7009 this._objects.unshift(object); 7010 return this.renderAll && this.renderAll(); 7011 }, 7012 7013 /** 7014 * Moves an object to the top of the stack of drawn objects 7015 * @param {fabric.Object} object Object to send 7016 * @return {fabric.Canvas} thisArg 7017 * @chainable 7018 */ 7019 bringToFront: function (object) { 7020 removeFromArray(this._objects, object); 7021 this._objects.push(object); 7022 return this.renderAll && this.renderAll(); 7023 }, 7024 7025 /** 7026 * Moves an object down in stack of drawn objects 7027 * @param {fabric.Object} object Object to send 7028 * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object 7029 * @return {fabric.Canvas} thisArg 7030 * @chainable 7031 */ 7032 sendBackwards: function (object, intersecting) { 7033 var idx = this._objects.indexOf(object); 7034 7035 // if object is not on the bottom of stack 7036 if (idx !== 0) { 7037 var newIdx = this._findNewLowerIndex(object, idx, intersecting); 7038 7039 removeFromArray(this._objects, object); 7040 this._objects.splice(newIdx, 0, object); 7041 this.renderAll && this.renderAll(); 7042 } 7043 return this; 7044 }, 7045 7046 /** 7047 * @private 7048 */ 7049 _findNewLowerIndex: function(object, idx, intersecting) { 7050 var newIdx; 7051 7052 if (intersecting) { 7053 newIdx = idx; 7054 7055 // traverse down the stack looking for the nearest intersecting object 7056 for (var i = idx - 1; i >= 0; --i) { 7057 7058 var isIntersecting = object.intersectsWithObject(this._objects[i]) || 7059 object.isContainedWithinObject(this._objects[i]) || 7060 this._objects[i].isContainedWithinObject(object); 7061 7062 if (isIntersecting) { 7063 newIdx = i; 7064 break; 7065 } 7066 } 7067 } 7068 else { 7069 newIdx = idx - 1; 7070 } 7071 7072 return newIdx; 7073 }, 7074 7075 /** 7076 * Moves an object up in stack of drawn objects 7077 * @param {fabric.Object} object Object to send 7078 * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object 7079 * @return {fabric.Canvas} thisArg 7080 * @chainable 7081 */ 7082 bringForward: function (object, intersecting) { 7083 var idx = this._objects.indexOf(object); 7084 7085 // if object is not on top of stack (last item in an array) 7086 if (idx !== this._objects.length - 1) { 7087 var newIdx = this._findNewUpperIndex(object, idx, intersecting); 7088 7089 removeFromArray(this._objects, object); 7090 this._objects.splice(newIdx, 0, object); 7091 this.renderAll && this.renderAll(); 7092 } 7093 return this; 7094 }, 7095 7096 /** 7097 * @private 7098 */ 7099 _findNewUpperIndex: function(object, idx, intersecting) { 7100 var newIdx; 7101 7102 if (intersecting) { 7103 newIdx = idx; 7104 7105 // traverse up the stack looking for the nearest intersecting object 7106 for (var i = idx + 1; i < this._objects.length; ++i) { 7107 7108 var isIntersecting = object.intersectsWithObject(this._objects[i]) || 7109 object.isContainedWithinObject(this._objects[i]) || 7110 this._objects[i].isContainedWithinObject(object); 7111 7112 if (isIntersecting) { 7113 newIdx = i; 7114 break; 7115 } 7116 } 7117 } 7118 else { 7119 newIdx = idx + 1; 7120 } 7121 7122 return newIdx; 7123 }, 7124 7125 /** 7126 * Moves an object to specified level in stack of drawn objects 7127 * @param {fabric.Object} object Object to send 7128 * @param {Number} index Position to move to 7129 * @return {fabric.Canvas} thisArg 7130 * @chainable 7131 */ 7132 moveTo: function (object, index) { 7133 removeFromArray(this._objects, object); 7134 this._objects.splice(index, 0, object); 7135 return this.renderAll && this.renderAll(); 7136 }, 7137 7138 /** 7139 * Clears a canvas element and removes all event listeners 7140 * @return {fabric.Canvas} thisArg 7141 * @chainable 7142 */ 7143 dispose: function () { 7144 this.clear(); 7145 this.interactive && this.removeListeners(); 7146 return this; 7147 }, 7148 7149 /** 7150 * Returns a string representation of an instance 7151 * @return {String} string representation of an instance 7152 */ 7153 toString: function () { 7154 return '#<fabric.Canvas (' + this.complexity() + '): ' + 7155 '{ objects: ' + this.getObjects().length + ' }>'; 7156 } 7157 }); 7158 7159 extend(fabric.StaticCanvas.prototype, fabric.Observable); 7160 extend(fabric.StaticCanvas.prototype, fabric.Collection); 7161 extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); 7162 7163 extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { 7164 7165 /** 7166 * @static 7167 * @type String 7168 * @default 7169 */ 7170 EMPTY_JSON: '{"objects": [], "background": "white"}', 7171 7172 /** 7173 * Provides a way to check support of some of the canvas methods 7174 * (either those of HTMLCanvasElement itself, or rendering context) 7175 * 7176 * @param {String} methodName Method to check support for; 7177 * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash" 7178 * @return {Boolean | null} `true` if method is supported (or at least exists), 7179 * `null` if canvas element or context can not be initialized 7180 */ 7181 supports: function (methodName) { 7182 var el = fabric.util.createCanvasElement(); 7183 7184 if (!el || !el.getContext) { 7185 return null; 7186 } 7187 7188 var ctx = el.getContext('2d'); 7189 if (!ctx) { 7190 return null; 7191 } 7192 7193 switch (methodName) { 7194 7195 case 'getImageData': 7196 return typeof ctx.getImageData !== 'undefined'; 7197 7198 case 'setLineDash': 7199 return typeof ctx.setLineDash !== 'undefined'; 7200 7201 case 'toDataURL': 7202 return typeof el.toDataURL !== 'undefined'; 7203 7204 case 'toDataURLWithQuality': 7205 try { 7206 el.toDataURL('image/jpeg', 0); 7207 return true; 7208 } 7209 catch (e) { } 7210 return false; 7211 7212 default: 7213 return null; 7214 } 7215 } 7216 }); 7217 7218 /** 7219 * Returns JSON representation of canvas 7220 * @function 7221 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 7222 * @return {String} JSON string 7223 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} 7224 * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} 7225 * @example <caption>JSON without additional properties</caption> 7226 * var json = canvas.toJSON(); 7227 * @example <caption>JSON with additional properties included</caption> 7228 * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']); 7229 * @example <caption>JSON without default values</caption> 7230 * canvas.includeDefaultValues = false; 7231 * var json = canvas.toJSON(); 7232 */ 7233 fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; 7234 7235})(); 7236 7237 7238/** 7239 * BaseBrush class 7240 * @class fabric.BaseBrush 7241 * @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo} 7242 */ 7243fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { 7244 7245 /** 7246 * Color of a brush 7247 * @type String 7248 * @default 7249 */ 7250 color: 'rgb(0, 0, 0)', 7251 7252 /** 7253 * Width of a brush 7254 * @type Number 7255 * @default 7256 */ 7257 width: 1, 7258 7259 /** 7260 * Shadow object representing shadow of this shape. 7261 * <b>Backwards incompatibility note:</b> This property replaces "shadowColor" (String), "shadowOffsetX" (Number), 7262 * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 7263 * @type fabric.Shadow 7264 * @default 7265 */ 7266 shadow: null, 7267 7268 /** 7269 * Line endings style of a brush (one of "butt", "round", "square") 7270 * @type String 7271 * @default 7272 */ 7273 strokeLineCap: 'round', 7274 7275 /** 7276 * Corner style of a brush (one of "bevil", "round", "miter") 7277 * @type String 7278 * @default 7279 */ 7280 strokeLineJoin: 'round', 7281 7282 /** 7283 * Stroke Dash Array. 7284 * @type Array 7285 * @default 7286 */ 7287 strokeDashArray: null, 7288 7289 /** 7290 * Sets shadow of an object 7291 * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") 7292 * @return {fabric.Object} thisArg 7293 * @chainable 7294 */ 7295 setShadow: function(options) { 7296 this.shadow = new fabric.Shadow(options); 7297 return this; 7298 }, 7299 7300 /** 7301 * Sets brush styles 7302 * @private 7303 */ 7304 _setBrushStyles: function() { 7305 var ctx = this.canvas.contextTop; 7306 7307 ctx.strokeStyle = this.color; 7308 ctx.lineWidth = this.width; 7309 ctx.lineCap = this.strokeLineCap; 7310 ctx.lineJoin = this.strokeLineJoin; 7311 if (this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) { 7312 ctx.setLineDash(this.strokeDashArray); 7313 } 7314 }, 7315 7316 /** 7317 * Sets brush shadow styles 7318 * @private 7319 */ 7320 _setShadow: function() { 7321 if (!this.shadow) { 7322 return; 7323 } 7324 7325 var ctx = this.canvas.contextTop; 7326 7327 ctx.shadowColor = this.shadow.color; 7328 ctx.shadowBlur = this.shadow.blur; 7329 ctx.shadowOffsetX = this.shadow.offsetX; 7330 ctx.shadowOffsetY = this.shadow.offsetY; 7331 }, 7332 7333 /** 7334 * Removes brush shadow styles 7335 * @private 7336 */ 7337 _resetShadow: function() { 7338 var ctx = this.canvas.contextTop; 7339 7340 ctx.shadowColor = ''; 7341 ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; 7342 } 7343}); 7344 7345 7346(function() { 7347 7348 /** 7349 * PencilBrush class 7350 * @class fabric.PencilBrush 7351 * @extends fabric.BaseBrush 7352 */ 7353 fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { 7354 7355 /** 7356 * Constructor 7357 * @param {fabric.Canvas} canvas 7358 * @return {fabric.PencilBrush} Instance of a pencil brush 7359 */ 7360 initialize: function(canvas) { 7361 this.canvas = canvas; 7362 this._points = [ ]; 7363 }, 7364 7365 /** 7366 * Inovoked on mouse down 7367 * @param {Object} pointer 7368 */ 7369 onMouseDown: function(pointer) { 7370 this._prepareForDrawing(pointer); 7371 // capture coordinates immediately 7372 // this allows to draw dots (when movement never occurs) 7373 this._captureDrawingPath(pointer); 7374 this._render(); 7375 }, 7376 7377 /** 7378 * Inovoked on mouse move 7379 * @param {Object} pointer 7380 */ 7381 onMouseMove: function(pointer) { 7382 this._captureDrawingPath(pointer); 7383 // redraw curve 7384 // clear top canvas 7385 this.canvas.clearContext(this.canvas.contextTop); 7386 this._render(); 7387 }, 7388 7389 /** 7390 * Invoked on mouse up 7391 */ 7392 onMouseUp: function() { 7393 this._finalizeAndAddPath(); 7394 }, 7395 7396 /** 7397 * @private 7398 * @param {Object} pointer Actual mouse position related to the canvas. 7399 */ 7400 _prepareForDrawing: function(pointer) { 7401 7402 var p = new fabric.Point(pointer.x, pointer.y); 7403 7404 this._reset(); 7405 this._addPoint(p); 7406 7407 this.canvas.contextTop.moveTo(p.x, p.y); 7408 }, 7409 7410 /** 7411 * @private 7412 * @param {fabric.Point} point Point to be added to points array 7413 */ 7414 _addPoint: function(point) { 7415 this._points.push(point); 7416 }, 7417 7418 /** 7419 * Clear points array and set contextTop canvas style. 7420 * @private 7421 */ 7422 _reset: function() { 7423 this._points.length = 0; 7424 7425 this._setBrushStyles(); 7426 this._setShadow(); 7427 }, 7428 7429 /** 7430 * @private 7431 * @param {Object} pointer Actual mouse position related to the canvas. 7432 */ 7433 _captureDrawingPath: function(pointer) { 7434 var pointerPoint = new fabric.Point(pointer.x, pointer.y); 7435 this._addPoint(pointerPoint); 7436 }, 7437 7438 /** 7439 * Draw a smooth path on the topCanvas using quadraticCurveTo 7440 * @private 7441 */ 7442 _render: function() { 7443 var ctx = this.canvas.contextTop, 7444 v = this.canvas.viewportTransform, 7445 p1 = this._points[0], 7446 p2 = this._points[1]; 7447 7448 ctx.save(); 7449 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 7450 ctx.beginPath(); 7451 7452 //if we only have 2 points in the path and they are the same 7453 //it means that the user only clicked the canvas without moving the mouse 7454 //then we should be drawing a dot. A path isn't drawn between two identical dots 7455 //that's why we set them apart a bit 7456 if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { 7457 p1.x -= 0.5; 7458 p2.x += 0.5; 7459 } 7460 ctx.moveTo(p1.x, p1.y); 7461 7462 for (var i = 1, len = this._points.length; i < len; i++) { 7463 // we pick the point between pi + 1 & pi + 2 as the 7464 // end point and p1 as our control point. 7465 var midPoint = p1.midPointFrom(p2); 7466 ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); 7467 7468 p1 = this._points[i]; 7469 p2 = this._points[i + 1]; 7470 } 7471 // Draw last line as a straight line while 7472 // we wait for the next point to be able to calculate 7473 // the bezier control point 7474 ctx.lineTo(p1.x, p1.y); 7475 ctx.stroke(); 7476 ctx.restore(); 7477 }, 7478 7479 /** 7480 * Converts points to SVG path 7481 * @param {Array} points Array of points 7482 * @param {Number} minX 7483 * @param {Number} minY 7484 * @return {String} SVG path 7485 */ 7486 convertPointsToSVGPath: function(points) { 7487 var path = [], 7488 p1 = new fabric.Point(points[0].x, points[0].y), 7489 p2 = new fabric.Point(points[1].x, points[1].y); 7490 7491 path.push('M ', points[0].x, ' ', points[0].y, ' '); 7492 for (var i = 1, len = points.length; i < len; i++) { 7493 var midPoint = p1.midPointFrom(p2); 7494 // p1 is our bezier control point 7495 // midpoint is our endpoint 7496 // start point is p(i-1) value. 7497 path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); 7498 p1 = new fabric.Point(points[i].x, points[i].y); 7499 if ((i + 1) < points.length) { 7500 p2 = new fabric.Point(points[i + 1].x, points[i + 1].y); 7501 } 7502 } 7503 path.push('L ', p1.x, ' ', p1.y, ' '); 7504 return path; 7505 }, 7506 7507 /** 7508 * Creates fabric.Path object to add on canvas 7509 * @param {String} pathData Path data 7510 * @return {fabric.Path} Path to add on canvas 7511 */ 7512 createPath: function(pathData) { 7513 var path = new fabric.Path(pathData, { 7514 fill: null, 7515 stroke: this.color, 7516 strokeWidth: this.width, 7517 strokeLineCap: this.strokeLineCap, 7518 strokeLineJoin: this.strokeLineJoin, 7519 strokeDashArray: this.strokeDashArray, 7520 originX: 'center', 7521 originY: 'center' 7522 }); 7523 7524 if (this.shadow) { 7525 this.shadow.affectStroke = true; 7526 path.setShadow(this.shadow); 7527 } 7528 7529 return path; 7530 }, 7531 7532 /** 7533 * On mouseup after drawing the path on contextTop canvas 7534 * we use the points captured to create an new fabric path object 7535 * and add it to the fabric canvas. 7536 */ 7537 _finalizeAndAddPath: function() { 7538 var ctx = this.canvas.contextTop; 7539 ctx.closePath(); 7540 7541 var pathData = this.convertPointsToSVGPath(this._points).join(''); 7542 if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { 7543 // do not create 0 width/height paths, as they are 7544 // rendered inconsistently across browsers 7545 // Firefox 4, for example, renders a dot, 7546 // whereas Chrome 10 renders nothing 7547 this.canvas.renderAll(); 7548 return; 7549 } 7550 7551 var path = this.createPath(pathData); 7552 7553 this.canvas.add(path); 7554 path.setCoords(); 7555 7556 this.canvas.clearContext(this.canvas.contextTop); 7557 this._resetShadow(); 7558 this.canvas.renderAll(); 7559 7560 // fire event 'path' created 7561 this.canvas.fire('path:created', { path: path }); 7562 } 7563 }); 7564})(); 7565 7566 7567/** 7568 * CircleBrush class 7569 * @class fabric.CircleBrush 7570 */ 7571fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { 7572 7573 /** 7574 * Width of a brush 7575 * @type Number 7576 * @default 7577 */ 7578 width: 10, 7579 7580 /** 7581 * Constructor 7582 * @param {fabric.Canvas} canvas 7583 * @return {fabric.CircleBrush} Instance of a circle brush 7584 */ 7585 initialize: function(canvas) { 7586 this.canvas = canvas; 7587 this.points = [ ]; 7588 }, 7589 /** 7590 * Invoked inside on mouse down and mouse move 7591 * @param {Object} pointer 7592 */ 7593 drawDot: function(pointer) { 7594 var point = this.addPoint(pointer), 7595 ctx = this.canvas.contextTop, 7596 v = this.canvas.viewportTransform; 7597 ctx.save(); 7598 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 7599 7600 ctx.fillStyle = point.fill; 7601 ctx.beginPath(); 7602 ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); 7603 ctx.closePath(); 7604 ctx.fill(); 7605 7606 ctx.restore(); 7607 }, 7608 7609 /** 7610 * Invoked on mouse down 7611 */ 7612 onMouseDown: function(pointer) { 7613 this.points.length = 0; 7614 this.canvas.clearContext(this.canvas.contextTop); 7615 this._setShadow(); 7616 this.drawDot(pointer); 7617 }, 7618 7619 /** 7620 * Invoked on mouse move 7621 * @param {Object} pointer 7622 */ 7623 onMouseMove: function(pointer) { 7624 this.drawDot(pointer); 7625 }, 7626 7627 /** 7628 * Invoked on mouse up 7629 */ 7630 onMouseUp: function() { 7631 var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; 7632 this.canvas.renderOnAddRemove = false; 7633 7634 var circles = [ ]; 7635 7636 for (var i = 0, len = this.points.length; i < len; i++) { 7637 var point = this.points[i], 7638 circle = new fabric.Circle({ 7639 radius: point.radius, 7640 left: point.x, 7641 top: point.y, 7642 originX: 'center', 7643 originY: 'center', 7644 fill: point.fill 7645 }); 7646 7647 this.shadow && circle.setShadow(this.shadow); 7648 7649 circles.push(circle); 7650 } 7651 var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); 7652 group.canvas = this.canvas; 7653 7654 this.canvas.add(group); 7655 this.canvas.fire('path:created', { path: group }); 7656 7657 this.canvas.clearContext(this.canvas.contextTop); 7658 this._resetShadow(); 7659 this.canvas.renderOnAddRemove = originalRenderOnAddRemove; 7660 this.canvas.renderAll(); 7661 }, 7662 7663 /** 7664 * @param {Object} pointer 7665 * @return {fabric.Point} Just added pointer point 7666 */ 7667 addPoint: function(pointer) { 7668 var pointerPoint = new fabric.Point(pointer.x, pointer.y), 7669 7670 circleRadius = fabric.util.getRandomInt( 7671 Math.max(0, this.width - 20), this.width + 20) / 2, 7672 7673 circleColor = new fabric.Color(this.color) 7674 .setAlpha(fabric.util.getRandomInt(0, 100) / 100) 7675 .toRgba(); 7676 7677 pointerPoint.radius = circleRadius; 7678 pointerPoint.fill = circleColor; 7679 7680 this.points.push(pointerPoint); 7681 7682 return pointerPoint; 7683 } 7684}); 7685 7686 7687/** 7688 * SprayBrush class 7689 * @class fabric.SprayBrush 7690 */ 7691fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { 7692 7693 /** 7694 * Width of a spray 7695 * @type Number 7696 * @default 7697 */ 7698 width: 10, 7699 7700 /** 7701 * Density of a spray (number of dots per chunk) 7702 * @type Number 7703 * @default 7704 */ 7705 density: 20, 7706 7707 /** 7708 * Width of spray dots 7709 * @type Number 7710 * @default 7711 */ 7712 dotWidth: 1, 7713 7714 /** 7715 * Width variance of spray dots 7716 * @type Number 7717 * @default 7718 */ 7719 dotWidthVariance: 1, 7720 7721 /** 7722 * Whether opacity of a dot should be random 7723 * @type Boolean 7724 * @default 7725 */ 7726 randomOpacity: false, 7727 7728 /** 7729 * Whether overlapping dots (rectangles) should be removed (for performance reasons) 7730 * @type Boolean 7731 * @default 7732 */ 7733 optimizeOverlapping: true, 7734 7735 /** 7736 * Constructor 7737 * @param {fabric.Canvas} canvas 7738 * @return {fabric.SprayBrush} Instance of a spray brush 7739 */ 7740 initialize: function(canvas) { 7741 this.canvas = canvas; 7742 this.sprayChunks = [ ]; 7743 }, 7744 7745 /** 7746 * Invoked on mouse down 7747 * @param {Object} pointer 7748 */ 7749 onMouseDown: function(pointer) { 7750 this.sprayChunks.length = 0; 7751 this.canvas.clearContext(this.canvas.contextTop); 7752 this._setShadow(); 7753 7754 this.addSprayChunk(pointer); 7755 this.render(); 7756 }, 7757 7758 /** 7759 * Invoked on mouse move 7760 * @param {Object} pointer 7761 */ 7762 onMouseMove: function(pointer) { 7763 this.addSprayChunk(pointer); 7764 this.render(); 7765 }, 7766 7767 /** 7768 * Invoked on mouse up 7769 */ 7770 onMouseUp: function() { 7771 var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; 7772 this.canvas.renderOnAddRemove = false; 7773 7774 var rects = [ ]; 7775 7776 for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { 7777 var sprayChunk = this.sprayChunks[i]; 7778 7779 for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { 7780 7781 var rect = new fabric.Rect({ 7782 width: sprayChunk[j].width, 7783 height: sprayChunk[j].width, 7784 left: sprayChunk[j].x + 1, 7785 top: sprayChunk[j].y + 1, 7786 originX: 'center', 7787 originY: 'center', 7788 fill: this.color 7789 }); 7790 7791 this.shadow && rect.setShadow(this.shadow); 7792 rects.push(rect); 7793 } 7794 } 7795 7796 if (this.optimizeOverlapping) { 7797 rects = this._getOptimizedRects(rects); 7798 } 7799 7800 var group = new fabric.Group(rects, { originX: 'center', originY: 'center' }); 7801 group.canvas = this.canvas; 7802 7803 this.canvas.add(group); 7804 this.canvas.fire('path:created', { path: group }); 7805 7806 this.canvas.clearContext(this.canvas.contextTop); 7807 this._resetShadow(); 7808 this.canvas.renderOnAddRemove = originalRenderOnAddRemove; 7809 this.canvas.renderAll(); 7810 }, 7811 7812 /** 7813 * @private 7814 * @param {Array} rects 7815 */ 7816 _getOptimizedRects: function(rects) { 7817 7818 // avoid creating duplicate rects at the same coordinates 7819 var uniqueRects = { }, key; 7820 7821 for (var i = 0, len = rects.length; i < len; i++) { 7822 key = rects[i].left + '' + rects[i].top; 7823 if (!uniqueRects[key]) { 7824 uniqueRects[key] = rects[i]; 7825 } 7826 } 7827 var uniqueRectsArray = [ ]; 7828 for (key in uniqueRects) { 7829 uniqueRectsArray.push(uniqueRects[key]); 7830 } 7831 7832 return uniqueRectsArray; 7833 }, 7834 7835 /** 7836 * Renders brush 7837 */ 7838 render: function() { 7839 var ctx = this.canvas.contextTop; 7840 ctx.fillStyle = this.color; 7841 7842 var v = this.canvas.viewportTransform; 7843 ctx.save(); 7844 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 7845 7846 for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { 7847 var point = this.sprayChunkPoints[i]; 7848 if (typeof point.opacity !== 'undefined') { 7849 ctx.globalAlpha = point.opacity; 7850 } 7851 ctx.fillRect(point.x, point.y, point.width, point.width); 7852 } 7853 ctx.restore(); 7854 }, 7855 7856 /** 7857 * @param {Object} pointer 7858 */ 7859 addSprayChunk: function(pointer) { 7860 this.sprayChunkPoints = [ ]; 7861 7862 var x, y, width, radius = this.width / 2; 7863 7864 for (var i = 0; i < this.density; i++) { 7865 7866 x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); 7867 y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); 7868 7869 if (this.dotWidthVariance) { 7870 width = fabric.util.getRandomInt( 7871 // bottom clamp width to 1 7872 Math.max(1, this.dotWidth - this.dotWidthVariance), 7873 this.dotWidth + this.dotWidthVariance); 7874 } 7875 else { 7876 width = this.dotWidth; 7877 } 7878 7879 var point = new fabric.Point(x, y); 7880 point.width = width; 7881 7882 if (this.randomOpacity) { 7883 point.opacity = fabric.util.getRandomInt(0, 100) / 100; 7884 } 7885 7886 this.sprayChunkPoints.push(point); 7887 } 7888 7889 this.sprayChunks.push(this.sprayChunkPoints); 7890 } 7891}); 7892 7893 7894/** 7895 * PatternBrush class 7896 * @class fabric.PatternBrush 7897 * @extends fabric.BaseBrush 7898 */ 7899fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { 7900 7901 getPatternSrc: function() { 7902 7903 var dotWidth = 20, 7904 dotDistance = 5, 7905 patternCanvas = fabric.document.createElement('canvas'), 7906 patternCtx = patternCanvas.getContext('2d'); 7907 7908 patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; 7909 7910 patternCtx.fillStyle = this.color; 7911 patternCtx.beginPath(); 7912 patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); 7913 patternCtx.closePath(); 7914 patternCtx.fill(); 7915 7916 return patternCanvas; 7917 }, 7918 7919 getPatternSrcFunction: function() { 7920 return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); 7921 }, 7922 7923 /** 7924 * Creates "pattern" instance property 7925 */ 7926 getPattern: function() { 7927 return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); 7928 }, 7929 7930 /** 7931 * Sets brush styles 7932 */ 7933 _setBrushStyles: function() { 7934 this.callSuper('_setBrushStyles'); 7935 this.canvas.contextTop.strokeStyle = this.getPattern(); 7936 }, 7937 7938 /** 7939 * Creates path 7940 */ 7941 createPath: function(pathData) { 7942 var path = this.callSuper('createPath', pathData); 7943 path.stroke = new fabric.Pattern({ 7944 source: this.source || this.getPatternSrcFunction() 7945 }); 7946 return path; 7947 } 7948}); 7949 7950 7951(function() { 7952 7953 var getPointer = fabric.util.getPointer, 7954 degreesToRadians = fabric.util.degreesToRadians, 7955 radiansToDegrees = fabric.util.radiansToDegrees, 7956 atan2 = Math.atan2, 7957 abs = Math.abs, 7958 7959 STROKE_OFFSET = 0.5; 7960 7961 /** 7962 * Canvas class 7963 * @class fabric.Canvas 7964 * @extends fabric.StaticCanvas 7965 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#canvas} 7966 * @see {@link fabric.Canvas#initialize} for constructor definition 7967 * 7968 * @fires object:modified 7969 * @fires object:rotating 7970 * @fires object:scaling 7971 * @fires object:moving 7972 * @fires object:selected 7973 * 7974 * @fires before:selection:cleared 7975 * @fires selection:cleared 7976 * @fires selection:created 7977 * 7978 * @fires path:created 7979 * @fires mouse:down 7980 * @fires mouse:move 7981 * @fires mouse:up 7982 * @fires mouse:over 7983 * @fires mouse:out 7984 * 7985 */ 7986 fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { 7987 7988 /** 7989 * Constructor 7990 * @param {HTMLElement | String} el <canvas> element to initialize instance on 7991 * @param {Object} [options] Options object 7992 * @return {Object} thisArg 7993 */ 7994 initialize: function(el, options) { 7995 options || (options = { }); 7996 7997 this._initStatic(el, options); 7998 this._initInteractive(); 7999 this._createCacheCanvas(); 8000 8001 fabric.Canvas.activeInstance = this; 8002 }, 8003 8004 /** 8005 * When true, objects can be transformed by one side (unproportionally) 8006 * @type Boolean 8007 * @default 8008 */ 8009 uniScaleTransform: false, 8010 8011 /** 8012 * When true, objects use center point as the origin of scale transformation. 8013 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 8014 * @since 1.3.4 8015 * @type Boolean 8016 * @default 8017 */ 8018 centeredScaling: false, 8019 8020 /** 8021 * When true, objects use center point as the origin of rotate transformation. 8022 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 8023 * @since 1.3.4 8024 * @type Boolean 8025 * @default 8026 */ 8027 centeredRotation: false, 8028 8029 /** 8030 * Indicates that canvas is interactive. This property should not be changed. 8031 * @type Boolean 8032 * @default 8033 */ 8034 interactive: true, 8035 8036 /** 8037 * Indicates whether group selection should be enabled 8038 * @type Boolean 8039 * @default 8040 */ 8041 selection: true, 8042 8043 /** 8044 * Color of selection 8045 * @type String 8046 * @default 8047 */ 8048 selectionColor: 'rgba(100, 100, 255, 0.3)', // blue 8049 8050 /** 8051 * Default dash array pattern 8052 * If not empty the selection border is dashed 8053 * @type Array 8054 */ 8055 selectionDashArray: [ ], 8056 8057 /** 8058 * Color of the border of selection (usually slightly darker than color of selection itself) 8059 * @type String 8060 * @default 8061 */ 8062 selectionBorderColor: 'rgba(255, 255, 255, 0.3)', 8063 8064 /** 8065 * Width of a line used in object/group selection 8066 * @type Number 8067 * @default 8068 */ 8069 selectionLineWidth: 1, 8070 8071 /** 8072 * Default cursor value used when hovering over an object on canvas 8073 * @type String 8074 * @default 8075 */ 8076 hoverCursor: 'move', 8077 8078 /** 8079 * Default cursor value used when moving an object on canvas 8080 * @type String 8081 * @default 8082 */ 8083 moveCursor: 'move', 8084 8085 /** 8086 * Default cursor value used for the entire canvas 8087 * @type String 8088 * @default 8089 */ 8090 defaultCursor: 'default', 8091 8092 /** 8093 * Cursor value used during free drawing 8094 * @type String 8095 * @default 8096 */ 8097 freeDrawingCursor: 'crosshair', 8098 8099 /** 8100 * Cursor value used for rotation point 8101 * @type String 8102 * @default 8103 */ 8104 rotationCursor: 'crosshair', 8105 8106 /** 8107 * Default element class that's given to wrapper (div) element of canvas 8108 * @type String 8109 * @default 8110 */ 8111 containerClass: 'canvas-container', 8112 8113 /** 8114 * When true, object detection happens on per-pixel basis rather than on per-bounding-box 8115 * @type Boolean 8116 * @default 8117 */ 8118 perPixelTargetFind: false, 8119 8120 /** 8121 * Number of pixels around target pixel to tolerate (consider active) during object detection 8122 * @type Number 8123 * @default 8124 */ 8125 targetFindTolerance: 0, 8126 8127 /** 8128 * When true, target detection is skipped when hovering over canvas. This can be used to improve performance. 8129 * @type Boolean 8130 * @default 8131 */ 8132 skipTargetFind: false, 8133 8134 /** 8135 * @private 8136 */ 8137 _initInteractive: function() { 8138 this._currentTransform = null; 8139 this._groupSelector = null; 8140 this._initWrapperElement(); 8141 this._createUpperCanvas(); 8142 this._initEventListeners(); 8143 8144 this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); 8145 8146 this.calcOffset(); 8147 }, 8148 8149 /** 8150 * Resets the current transform to its original values and chooses the type of resizing based on the event 8151 * @private 8152 * @param {Event} e Event object fired on mousemove 8153 */ 8154 _resetCurrentTransform: function(e) { 8155 var t = this._currentTransform; 8156 8157 t.target.set({ 8158 scaleX: t.original.scaleX, 8159 scaleY: t.original.scaleY, 8160 left: t.original.left, 8161 top: t.original.top 8162 }); 8163 8164 if (this._shouldCenterTransform(e, t.target)) { 8165 if (t.action === 'rotate') { 8166 this._setOriginToCenter(t.target); 8167 } 8168 else { 8169 if (t.originX !== 'center') { 8170 if (t.originX === 'right') { 8171 t.mouseXSign = -1; 8172 } 8173 else { 8174 t.mouseXSign = 1; 8175 } 8176 } 8177 if (t.originY !== 'center') { 8178 if (t.originY === 'bottom') { 8179 t.mouseYSign = -1; 8180 } 8181 else { 8182 t.mouseYSign = 1; 8183 } 8184 } 8185 8186 t.originX = 'center'; 8187 t.originY = 'center'; 8188 } 8189 } 8190 else { 8191 t.originX = t.original.originX; 8192 t.originY = t.original.originY; 8193 } 8194 }, 8195 8196 /** 8197 * Checks if point is contained within an area of given object 8198 * @param {Event} e Event object 8199 * @param {fabric.Object} target Object to test against 8200 * @return {Boolean} true if point is contained within an area of given object 8201 */ 8202 containsPoint: function (e, target) { 8203 var pointer = this.getPointer(e, true), 8204 xy = this._normalizePointer(target, pointer); 8205 8206 // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html 8207 // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html 8208 return (target.containsPoint(xy) || target._findTargetCorner(pointer)); 8209 }, 8210 8211 /** 8212 * @private 8213 */ 8214 _normalizePointer: function (object, pointer) { 8215 var activeGroup = this.getActiveGroup(), 8216 x = pointer.x, 8217 y = pointer.y, 8218 isObjectInGroup = ( 8219 activeGroup && 8220 object.type !== 'group' && 8221 activeGroup.contains(object)), 8222 lt; 8223 8224 if (isObjectInGroup) { 8225 lt = new fabric.Point(activeGroup.left, activeGroup.top); 8226 lt = fabric.util.transformPoint(lt, this.viewportTransform, true); 8227 x -= lt.x; 8228 y -= lt.y; 8229 } 8230 return { x: x, y: y }; 8231 }, 8232 8233 /** 8234 * Returns true if object is transparent at a certain location 8235 * @param {fabric.Object} target Object to check 8236 * @param {Number} x Left coordinate 8237 * @param {Number} y Top coordinate 8238 * @return {Boolean} 8239 */ 8240 isTargetTransparent: function (target, x, y) { 8241 var hasBorders = target.hasBorders, 8242 transparentCorners = target.transparentCorners; 8243 8244 target.hasBorders = target.transparentCorners = false; 8245 8246 this._draw(this.contextCache, target); 8247 8248 target.hasBorders = hasBorders; 8249 target.transparentCorners = transparentCorners; 8250 8251 var isTransparent = fabric.util.isTransparent( 8252 this.contextCache, x, y, this.targetFindTolerance); 8253 8254 this.clearContext(this.contextCache); 8255 8256 return isTransparent; 8257 }, 8258 8259 /** 8260 * @private 8261 * @param {Event} e Event object 8262 * @param {fabric.Object} target 8263 */ 8264 _shouldClearSelection: function (e, target) { 8265 var activeGroup = this.getActiveGroup(), 8266 activeObject = this.getActiveObject(); 8267 8268 return ( 8269 !target 8270 || 8271 (target && 8272 activeGroup && 8273 !activeGroup.contains(target) && 8274 activeGroup !== target && 8275 !e.shiftKey) 8276 || 8277 (target && !target.evented) 8278 || 8279 (target && 8280 !target.selectable && 8281 activeObject && 8282 activeObject !== target) 8283 ); 8284 }, 8285 8286 /** 8287 * @private 8288 * @param {Event} e Event object 8289 * @param {fabric.Object} target 8290 */ 8291 _shouldCenterTransform: function (e, target) { 8292 if (!target) { 8293 return; 8294 } 8295 8296 var t = this._currentTransform, 8297 centerTransform; 8298 8299 if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') { 8300 centerTransform = this.centeredScaling || target.centeredScaling; 8301 } 8302 else if (t.action === 'rotate') { 8303 centerTransform = this.centeredRotation || target.centeredRotation; 8304 } 8305 8306 return centerTransform ? !e.altKey : e.altKey; 8307 }, 8308 8309 /** 8310 * @private 8311 */ 8312 _getOriginFromCorner: function(target, corner) { 8313 var origin = { 8314 x: target.originX, 8315 y: target.originY 8316 }; 8317 8318 if (corner === 'ml' || corner === 'tl' || corner === 'bl') { 8319 origin.x = 'right'; 8320 } 8321 else if (corner === 'mr' || corner === 'tr' || corner === 'br') { 8322 origin.x = 'left'; 8323 } 8324 8325 if (corner === 'tl' || corner === 'mt' || corner === 'tr') { 8326 origin.y = 'bottom'; 8327 } 8328 else if (corner === 'bl' || corner === 'mb' || corner === 'br') { 8329 origin.y = 'top'; 8330 } 8331 8332 return origin; 8333 }, 8334 8335 /** 8336 * @private 8337 */ 8338 _getActionFromCorner: function(target, corner) { 8339 var action = 'drag'; 8340 if (corner) { 8341 action = (corner === 'ml' || corner === 'mr') 8342 ? 'scaleX' 8343 : (corner === 'mt' || corner === 'mb') 8344 ? 'scaleY' 8345 : corner === 'mtr' 8346 ? 'rotate' 8347 : 'scale'; 8348 } 8349 return action; 8350 }, 8351 8352 /** 8353 * @private 8354 * @param {Event} e Event object 8355 * @param {fabric.Object} target 8356 */ 8357 _setupCurrentTransform: function (e, target) { 8358 if (!target) { 8359 return; 8360 } 8361 8362 var pointer = this.getPointer(e), 8363 corner = target._findTargetCorner(this.getPointer(e, true)), 8364 action = this._getActionFromCorner(target, corner), 8365 origin = this._getOriginFromCorner(target, corner); 8366 8367 this._currentTransform = { 8368 target: target, 8369 action: action, 8370 scaleX: target.scaleX, 8371 scaleY: target.scaleY, 8372 offsetX: pointer.x - target.left, 8373 offsetY: pointer.y - target.top, 8374 originX: origin.x, 8375 originY: origin.y, 8376 ex: pointer.x, 8377 ey: pointer.y, 8378 left: target.left, 8379 top: target.top, 8380 theta: degreesToRadians(target.angle), 8381 width: target.width * target.scaleX, 8382 mouseXSign: 1, 8383 mouseYSign: 1 8384 }; 8385 8386 this._currentTransform.original = { 8387 left: target.left, 8388 top: target.top, 8389 scaleX: target.scaleX, 8390 scaleY: target.scaleY, 8391 originX: origin.x, 8392 originY: origin.y 8393 }; 8394 8395 this._resetCurrentTransform(e); 8396 }, 8397 8398 /** 8399 * Translates object by "setting" its left/top 8400 * @private 8401 * @param {Number} x pointer's x coordinate 8402 * @param {Number} y pointer's y coordinate 8403 */ 8404 _translateObject: function (x, y) { 8405 var target = this._currentTransform.target; 8406 8407 if (!target.get('lockMovementX')) { 8408 target.set('left', x - this._currentTransform.offsetX); 8409 } 8410 if (!target.get('lockMovementY')) { 8411 target.set('top', y - this._currentTransform.offsetY); 8412 } 8413 }, 8414 8415 /** 8416 * Scales object by invoking its scaleX/scaleY methods 8417 * @private 8418 * @param {Number} x pointer's x coordinate 8419 * @param {Number} y pointer's y coordinate 8420 * @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object. 8421 * When not provided, an object is scaled by both dimensions equally 8422 */ 8423 _scaleObject: function (x, y, by) { 8424 var t = this._currentTransform, 8425 target = t.target, 8426 lockScalingX = target.get('lockScalingX'), 8427 lockScalingY = target.get('lockScalingY'), 8428 lockScalingFlip = target.get('lockScalingFlip'); 8429 8430 if (lockScalingX && lockScalingY) { 8431 return; 8432 } 8433 8434 // Get the constraint point 8435 var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), 8436 localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY); 8437 8438 this._setLocalMouse(localMouse, t); 8439 8440 // Actually scale the object 8441 this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip); 8442 8443 // Make sure the constraints apply 8444 target.setPositionByOrigin(constraintPosition, t.originX, t.originY); 8445 }, 8446 8447 /** 8448 * @private 8449 */ 8450 _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip) { 8451 var target = transform.target, forbidScalingX = false, forbidScalingY = false, 8452 strokeWidth = target.stroke ? target.strokeWidth : 0; 8453 8454 transform.newScaleX = localMouse.x / (target.width + strokeWidth / 2); 8455 transform.newScaleY = localMouse.y / (target.height + strokeWidth / 2); 8456 8457 if (lockScalingFlip && transform.newScaleX <= 0 && transform.newScaleX < target.scaleX) { 8458 forbidScalingX = true; 8459 } 8460 8461 if (lockScalingFlip && transform.newScaleY <= 0 && transform.newScaleY < target.scaleY) { 8462 forbidScalingY = true; 8463 } 8464 8465 if (by === 'equally' && !lockScalingX && !lockScalingY) { 8466 forbidScalingX || forbidScalingY || this._scaleObjectEqually(localMouse, target, transform); 8467 } 8468 else if (!by) { 8469 forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX); 8470 forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY); 8471 } 8472 else if (by === 'x' && !target.get('lockUniScaling')) { 8473 forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX); 8474 } 8475 else if (by === 'y' && !target.get('lockUniScaling')) { 8476 forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY); 8477 } 8478 8479 forbidScalingX || forbidScalingY || this._flipObject(transform, by); 8480 8481 }, 8482 8483 /** 8484 * @private 8485 */ 8486 _scaleObjectEqually: function(localMouse, target, transform) { 8487 8488 var dist = localMouse.y + localMouse.x, 8489 strokeWidth = target.stroke ? target.strokeWidth : 0, 8490 lastDist = (target.height + (strokeWidth / 2)) * transform.original.scaleY + 8491 (target.width + (strokeWidth / 2)) * transform.original.scaleX; 8492 8493 // We use transform.scaleX/Y instead of target.scaleX/Y 8494 // because the object may have a min scale and we'll loose the proportions 8495 transform.newScaleX = transform.original.scaleX * dist / lastDist; 8496 transform.newScaleY = transform.original.scaleY * dist / lastDist; 8497 8498 target.set('scaleX', transform.newScaleX); 8499 target.set('scaleY', transform.newScaleY); 8500 }, 8501 8502 /** 8503 * @private 8504 */ 8505 _flipObject: function(transform, by) { 8506 if (transform.newScaleX < 0 && by !== 'y') { 8507 if (transform.originX === 'left') { 8508 transform.originX = 'right'; 8509 } 8510 else if (transform.originX === 'right') { 8511 transform.originX = 'left'; 8512 } 8513 } 8514 8515 if (transform.newScaleY < 0 && by !== 'x') { 8516 if (transform.originY === 'top') { 8517 transform.originY = 'bottom'; 8518 } 8519 else if (transform.originY === 'bottom') { 8520 transform.originY = 'top'; 8521 } 8522 } 8523 }, 8524 8525 /** 8526 * @private 8527 */ 8528 _setLocalMouse: function(localMouse, t) { 8529 var target = t.target; 8530 8531 if (t.originX === 'right') { 8532 localMouse.x *= -1; 8533 } 8534 else if (t.originX === 'center') { 8535 localMouse.x *= t.mouseXSign * 2; 8536 8537 if (localMouse.x < 0) { 8538 t.mouseXSign = -t.mouseXSign; 8539 } 8540 } 8541 8542 if (t.originY === 'bottom') { 8543 localMouse.y *= -1; 8544 } 8545 else if (t.originY === 'center') { 8546 localMouse.y *= t.mouseYSign * 2; 8547 8548 if (localMouse.y < 0) { 8549 t.mouseYSign = -t.mouseYSign; 8550 } 8551 } 8552 8553 // adjust the mouse coordinates when dealing with padding 8554 if (abs(localMouse.x) > target.padding) { 8555 if (localMouse.x < 0) { 8556 localMouse.x += target.padding; 8557 } 8558 else { 8559 localMouse.x -= target.padding; 8560 } 8561 } 8562 else { // mouse is within the padding, set to 0 8563 localMouse.x = 0; 8564 } 8565 8566 if (abs(localMouse.y) > target.padding) { 8567 if (localMouse.y < 0) { 8568 localMouse.y += target.padding; 8569 } 8570 else { 8571 localMouse.y -= target.padding; 8572 } 8573 } 8574 else { 8575 localMouse.y = 0; 8576 } 8577 }, 8578 8579 /** 8580 * Rotates object by invoking its rotate method 8581 * @private 8582 * @param {Number} x pointer's x coordinate 8583 * @param {Number} y pointer's y coordinate 8584 */ 8585 _rotateObject: function (x, y) { 8586 8587 var t = this._currentTransform; 8588 8589 if (t.target.get('lockRotation')) { 8590 return; 8591 } 8592 8593 var lastAngle = atan2(t.ey - t.top, t.ex - t.left), 8594 curAngle = atan2(y - t.top, x - t.left), 8595 angle = radiansToDegrees(curAngle - lastAngle + t.theta); 8596 8597 // normalize angle to positive value 8598 if (angle < 0) { 8599 angle = 360 + angle; 8600 } 8601 8602 t.target.angle = angle % 360; 8603 }, 8604 8605 /** 8606 * Set the cursor type of the canvas element 8607 * @param {String} value Cursor type of the canvas element. 8608 * @see http://www.w3.org/TR/css3-ui/#cursor 8609 */ 8610 setCursor: function (value) { 8611 this.upperCanvasEl.style.cursor = value; 8612 }, 8613 8614 /** 8615 * @private 8616 */ 8617 _resetObjectTransform: function (target) { 8618 target.scaleX = 1; 8619 target.scaleY = 1; 8620 target.setAngle(0); 8621 }, 8622 8623 /** 8624 * @private 8625 */ 8626 _drawSelection: function () { 8627 var ctx = this.contextTop, 8628 groupSelector = this._groupSelector, 8629 left = groupSelector.left, 8630 top = groupSelector.top, 8631 aleft = abs(left), 8632 atop = abs(top); 8633 8634 ctx.fillStyle = this.selectionColor; 8635 8636 ctx.fillRect( 8637 groupSelector.ex - ((left > 0) ? 0 : -left), 8638 groupSelector.ey - ((top > 0) ? 0 : -top), 8639 aleft, 8640 atop 8641 ); 8642 8643 ctx.lineWidth = this.selectionLineWidth; 8644 ctx.strokeStyle = this.selectionBorderColor; 8645 8646 // selection border 8647 if (this.selectionDashArray.length > 1) { 8648 8649 var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft), 8650 py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); 8651 8652 ctx.beginPath(); 8653 8654 fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray); 8655 fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray); 8656 fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray); 8657 fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray); 8658 8659 ctx.closePath(); 8660 ctx.stroke(); 8661 } 8662 else { 8663 ctx.strokeRect( 8664 groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), 8665 groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop), 8666 aleft, 8667 atop 8668 ); 8669 } 8670 }, 8671 8672 /** 8673 * @private 8674 */ 8675 _isLastRenderedObject: function(e) { 8676 return ( 8677 this.controlsAboveOverlay && 8678 this.lastRenderedObjectWithControlsAboveOverlay && 8679 this.lastRenderedObjectWithControlsAboveOverlay.visible && 8680 this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && 8681 this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true))); 8682 }, 8683 8684 /** 8685 * Method that determines what object we are clicking on 8686 * @param {Event} e mouse event 8687 * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through 8688 */ 8689 findTarget: function (e, skipGroup) { 8690 if (this.skipTargetFind) { 8691 return; 8692 } 8693 8694 if (this._isLastRenderedObject(e)) { 8695 return this.lastRenderedObjectWithControlsAboveOverlay; 8696 } 8697 8698 // first check current group (if one exists) 8699 var activeGroup = this.getActiveGroup(); 8700 if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { 8701 return activeGroup; 8702 } 8703 8704 var target = this._searchPossibleTargets(e); 8705 this._fireOverOutEvents(target); 8706 8707 return target; 8708 }, 8709 8710 /** 8711 * @private 8712 */ 8713 _fireOverOutEvents: function(target) { 8714 if (target) { 8715 if (this._hoveredTarget !== target) { 8716 this.fire('mouse:over', { target: target }); 8717 target.fire('mouseover'); 8718 if (this._hoveredTarget) { 8719 this.fire('mouse:out', { target: this._hoveredTarget }); 8720 this._hoveredTarget.fire('mouseout'); 8721 } 8722 this._hoveredTarget = target; 8723 } 8724 } 8725 else if (this._hoveredTarget) { 8726 this.fire('mouse:out', { target: this._hoveredTarget }); 8727 this._hoveredTarget.fire('mouseout'); 8728 this._hoveredTarget = null; 8729 } 8730 }, 8731 8732 /** 8733 * @private 8734 */ 8735 _checkTarget: function(e, obj, pointer) { 8736 if (obj && 8737 obj.visible && 8738 obj.evented && 8739 this.containsPoint(e, obj)){ 8740 if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { 8741 var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y); 8742 if (!isTransparent) { 8743 return true; 8744 } 8745 } 8746 else { 8747 return true; 8748 } 8749 } 8750 }, 8751 8752 /** 8753 * @private 8754 */ 8755 _searchPossibleTargets: function(e) { 8756 8757 // Cache all targets where their bounding box contains point. 8758 var target, 8759 pointer = this.getPointer(e, true), 8760 i = this._objects.length; 8761 // Do not check for currently grouped objects, since we check the parent group itself. 8762 while (i--) { 8763 if (!this._objects[i].group && this._checkTarget(e, this._objects[i], pointer)){ 8764 this.relatedTarget = this._objects[i]; 8765 target = this._objects[i]; 8766 break; 8767 } 8768 } 8769 8770 return target; 8771 }, 8772 8773 /** 8774 * Returns pointer coordinates relative to canvas. 8775 * @param {Event} e 8776 * @return {Object} object with "x" and "y" number values 8777 */ 8778 getPointer: function (e, ignoreZoom, upperCanvasEl) { 8779 if (!upperCanvasEl) { 8780 upperCanvasEl = this.upperCanvasEl; 8781 } 8782 var pointer = getPointer(e, upperCanvasEl), 8783 bounds = upperCanvasEl.getBoundingClientRect(), 8784 boundsWidth = bounds.width || 0, 8785 boundsHeight = bounds.height || 0, 8786 cssScale; 8787 8788 if (!boundsWidth || !boundsHeight ) { 8789 if ('top' in bounds && 'bottom' in bounds) { 8790 boundsHeight = Math.abs( bounds.top - bounds.bottom ); 8791 } 8792 if ('right' in bounds && 'left' in bounds) { 8793 boundsWidth = Math.abs( bounds.right - bounds.left ); 8794 } 8795 } 8796 8797 this.calcOffset(); 8798 8799 pointer.x = pointer.x - this._offset.left; 8800 pointer.y = pointer.y - this._offset.top; 8801 if (!ignoreZoom) { 8802 pointer = fabric.util.transformPoint( 8803 pointer, 8804 fabric.util.invertTransform(this.viewportTransform) 8805 ); 8806 } 8807 8808 if (boundsWidth === 0 || boundsHeight === 0) { 8809 // If bounds are not available (i.e. not visible), do not apply scale. 8810 cssScale = { width: 1, height: 1 }; 8811 } 8812 else { 8813 cssScale = { 8814 width: upperCanvasEl.width / boundsWidth, 8815 height: upperCanvasEl.height / boundsHeight 8816 }; 8817 } 8818 8819 return { 8820 x: pointer.x * cssScale.width, 8821 y: pointer.y * cssScale.height 8822 }; 8823 }, 8824 8825 /** 8826 * @private 8827 * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized 8828 */ 8829 _createUpperCanvas: function () { 8830 var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''); 8831 8832 this.upperCanvasEl = this._createCanvasElement(); 8833 fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); 8834 8835 this.wrapperEl.appendChild(this.upperCanvasEl); 8836 8837 this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl); 8838 this._applyCanvasStyle(this.upperCanvasEl); 8839 this.contextTop = this.upperCanvasEl.getContext('2d'); 8840 }, 8841 8842 /** 8843 * @private 8844 */ 8845 _createCacheCanvas: function () { 8846 this.cacheCanvasEl = this._createCanvasElement(); 8847 this.cacheCanvasEl.setAttribute('width', this.width); 8848 this.cacheCanvasEl.setAttribute('height', this.height); 8849 this.contextCache = this.cacheCanvasEl.getContext('2d'); 8850 }, 8851 8852 /** 8853 * @private 8854 */ 8855 _initWrapperElement: function () { 8856 this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', { 8857 'class': this.containerClass 8858 }); 8859 fabric.util.setStyle(this.wrapperEl, { 8860 width: this.getWidth() + 'px', 8861 height: this.getHeight() + 'px', 8862 position: 'relative' 8863 }); 8864 fabric.util.makeElementUnselectable(this.wrapperEl); 8865 }, 8866 8867 /** 8868 * @private 8869 * @param {HTMLElement} element canvas element to apply styles on 8870 */ 8871 _applyCanvasStyle: function (element) { 8872 var width = this.getWidth() || element.width, 8873 height = this.getHeight() || element.height; 8874 8875 fabric.util.setStyle(element, { 8876 position: 'absolute', 8877 width: width + 'px', 8878 height: height + 'px', 8879 left: 0, 8880 top: 0 8881 }); 8882 element.width = width; 8883 element.height = height; 8884 fabric.util.makeElementUnselectable(element); 8885 }, 8886 8887 /** 8888 * Copys the the entire inline style from one element (fromEl) to another (toEl) 8889 * @private 8890 * @param {Element} fromEl Element style is copied from 8891 * @param {Element} toEl Element copied style is applied to 8892 */ 8893 _copyCanvasStyle: function (fromEl, toEl) { 8894 toEl.style.cssText = fromEl.style.cssText; 8895 }, 8896 8897 /** 8898 * Returns context of canvas where object selection is drawn 8899 * @return {CanvasRenderingContext2D} 8900 */ 8901 getSelectionContext: function() { 8902 return this.contextTop; 8903 }, 8904 8905 /** 8906 * Returns <canvas> element on which object selection is drawn 8907 * @return {HTMLCanvasElement} 8908 */ 8909 getSelectionElement: function () { 8910 return this.upperCanvasEl; 8911 }, 8912 8913 /** 8914 * @private 8915 * @param {Object} object 8916 */ 8917 _setActiveObject: function(object) { 8918 if (this._activeObject) { 8919 this._activeObject.set('active', false); 8920 } 8921 this._activeObject = object; 8922 object.set('active', true); 8923 }, 8924 8925 /** 8926 * Sets given object as the only active object on canvas 8927 * @param {fabric.Object} object Object to set as an active one 8928 * @param {Event} [e] Event (passed along when firing "object:selected") 8929 * @return {fabric.Canvas} thisArg 8930 * @chainable 8931 */ 8932 setActiveObject: function (object, e) { 8933 this._setActiveObject(object); 8934 this.renderAll(); 8935 this.fire('object:selected', { target: object, e: e }); 8936 object.fire('selected', { e: e }); 8937 return this; 8938 }, 8939 8940 /** 8941 * Returns currently active object 8942 * @return {fabric.Object} active object 8943 */ 8944 getActiveObject: function () { 8945 return this._activeObject; 8946 }, 8947 8948 /** 8949 * @private 8950 */ 8951 _discardActiveObject: function() { 8952 if (this._activeObject) { 8953 this._activeObject.set('active', false); 8954 } 8955 this._activeObject = null; 8956 }, 8957 8958 /** 8959 * Discards currently active object 8960 * @return {fabric.Canvas} thisArg 8961 * @chainable 8962 */ 8963 discardActiveObject: function (e) { 8964 this._discardActiveObject(); 8965 this.renderAll(); 8966 this.fire('selection:cleared', { e: e }); 8967 return this; 8968 }, 8969 8970 /** 8971 * @private 8972 * @param {fabric.Group} group 8973 */ 8974 _setActiveGroup: function(group) { 8975 this._activeGroup = group; 8976 if (group) { 8977 group.set('active', true); 8978 } 8979 }, 8980 8981 /** 8982 * Sets active group to a speicified one 8983 * @param {fabric.Group} group Group to set as a current one 8984 * @return {fabric.Canvas} thisArg 8985 * @chainable 8986 */ 8987 setActiveGroup: function (group, e) { 8988 this._setActiveGroup(group); 8989 if (group) { 8990 this.fire('object:selected', { target: group, e: e }); 8991 group.fire('selected', { e: e }); 8992 } 8993 return this; 8994 }, 8995 8996 /** 8997 * Returns currently active group 8998 * @return {fabric.Group} Current group 8999 */ 9000 getActiveGroup: function () { 9001 return this._activeGroup; 9002 }, 9003 9004 /** 9005 * @private 9006 */ 9007 _discardActiveGroup: function() { 9008 var g = this.getActiveGroup(); 9009 if (g) { 9010 g.destroy(); 9011 } 9012 this.setActiveGroup(null); 9013 }, 9014 9015 /** 9016 * Discards currently active group 9017 * @return {fabric.Canvas} thisArg 9018 */ 9019 discardActiveGroup: function (e) { 9020 this._discardActiveGroup(); 9021 this.fire('selection:cleared', { e: e }); 9022 return this; 9023 }, 9024 9025 /** 9026 * Deactivates all objects on canvas, removing any active group or object 9027 * @return {fabric.Canvas} thisArg 9028 */ 9029 deactivateAll: function () { 9030 var allObjects = this.getObjects(), 9031 i = 0, 9032 len = allObjects.length; 9033 for ( ; i < len; i++) { 9034 allObjects[i].set('active', false); 9035 } 9036 this._discardActiveGroup(); 9037 this._discardActiveObject(); 9038 return this; 9039 }, 9040 9041 /** 9042 * Deactivates all objects and dispatches appropriate events 9043 * @return {fabric.Canvas} thisArg 9044 */ 9045 deactivateAllWithDispatch: function (e) { 9046 var activeObject = this.getActiveGroup() || this.getActiveObject(); 9047 if (activeObject) { 9048 this.fire('before:selection:cleared', { target: activeObject, e: e }); 9049 } 9050 this.deactivateAll(); 9051 if (activeObject) { 9052 this.fire('selection:cleared', { e: e }); 9053 } 9054 return this; 9055 }, 9056 9057 /** 9058 * Draws objects' controls (borders/controls) 9059 * @param {CanvasRenderingContext2D} ctx Context to render controls on 9060 */ 9061 drawControls: function(ctx) { 9062 var activeGroup = this.getActiveGroup(); 9063 if (activeGroup) { 9064 this._drawGroupControls(ctx, activeGroup); 9065 } 9066 else { 9067 this._drawObjectsControls(ctx); 9068 } 9069 }, 9070 9071 /** 9072 * @private 9073 */ 9074 _drawGroupControls: function(ctx, activeGroup) { 9075 activeGroup._renderControls(ctx); 9076 }, 9077 9078 /** 9079 * @private 9080 */ 9081 _drawObjectsControls: function(ctx) { 9082 for (var i = 0, len = this._objects.length; i < len; ++i) { 9083 if (!this._objects[i] || !this._objects[i].active) { 9084 continue; 9085 } 9086 this._objects[i]._renderControls(ctx); 9087 this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; 9088 } 9089 } 9090 }); 9091 9092 // copying static properties manually to work around Opera's bug, 9093 // where "prototype" property is enumerable and overrides existing prototype 9094 for (var prop in fabric.StaticCanvas) { 9095 if (prop !== 'prototype') { 9096 fabric.Canvas[prop] = fabric.StaticCanvas[prop]; 9097 } 9098 } 9099 9100 if (fabric.isTouchSupported) { 9101 /** @ignore */ 9102 fabric.Canvas.prototype._setCursorFromEvent = function() { }; 9103 } 9104 9105 /** 9106 * @class fabric.Element 9107 * @alias fabric.Canvas 9108 * @deprecated Use {@link fabric.Canvas} instead. 9109 * @constructor 9110 */ 9111 fabric.Element = fabric.Canvas; 9112})(); 9113 9114 9115(function() { 9116 9117 var cursorOffset = { 9118 mt: 0, // n 9119 tr: 1, // ne 9120 mr: 2, // e 9121 br: 3, // se 9122 mb: 4, // s 9123 bl: 5, // sw 9124 ml: 6, // w 9125 tl: 7 // nw 9126 }, 9127 addListener = fabric.util.addListener, 9128 removeListener = fabric.util.removeListener; 9129 9130 fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { 9131 9132 /** 9133 * Map of cursor style values for each of the object controls 9134 * @private 9135 */ 9136 cursorMap: [ 9137 'n-resize', 9138 'ne-resize', 9139 'e-resize', 9140 'se-resize', 9141 's-resize', 9142 'sw-resize', 9143 'w-resize', 9144 'nw-resize' 9145 ], 9146 9147 /** 9148 * Adds mouse listeners to canvas 9149 * @private 9150 */ 9151 _initEventListeners: function () { 9152 9153 this._bindEvents(); 9154 9155 addListener(fabric.window, 'resize', this._onResize); 9156 9157 // mouse events 9158 addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); 9159 addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); 9160 addListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel); 9161 9162 // touch events 9163 addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); 9164 addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); 9165 9166 if (typeof eventjs !== 'undefined' && 'add' in eventjs) { 9167 eventjs.add(this.upperCanvasEl, 'gesture', this._onGesture); 9168 eventjs.add(this.upperCanvasEl, 'drag', this._onDrag); 9169 eventjs.add(this.upperCanvasEl, 'orientation', this._onOrientationChange); 9170 eventjs.add(this.upperCanvasEl, 'shake', this._onShake); 9171 eventjs.add(this.upperCanvasEl, 'longpress', this._onLongPress); 9172 } 9173 }, 9174 9175 /** 9176 * @private 9177 */ 9178 _bindEvents: function() { 9179 this._onMouseDown = this._onMouseDown.bind(this); 9180 this._onMouseMove = this._onMouseMove.bind(this); 9181 this._onMouseUp = this._onMouseUp.bind(this); 9182 this._onResize = this._onResize.bind(this); 9183 this._onGesture = this._onGesture.bind(this); 9184 this._onDrag = this._onDrag.bind(this); 9185 this._onShake = this._onShake.bind(this); 9186 this._onLongPress = this._onLongPress.bind(this); 9187 this._onOrientationChange = this._onOrientationChange.bind(this); 9188 this._onMouseWheel = this._onMouseWheel.bind(this); 9189 }, 9190 9191 /** 9192 * Removes all event listeners 9193 */ 9194 removeListeners: function() { 9195 removeListener(fabric.window, 'resize', this._onResize); 9196 9197 removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); 9198 removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); 9199 removeListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel); 9200 9201 removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown); 9202 removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); 9203 9204 if (typeof eventjs !== 'undefined' && 'remove' in eventjs) { 9205 eventjs.remove(this.upperCanvasEl, 'gesture', this._onGesture); 9206 eventjs.remove(this.upperCanvasEl, 'drag', this._onDrag); 9207 eventjs.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange); 9208 eventjs.remove(this.upperCanvasEl, 'shake', this._onShake); 9209 eventjs.remove(this.upperCanvasEl, 'longpress', this._onLongPress); 9210 } 9211 }, 9212 9213 /** 9214 * @private 9215 * @param {Event} [e] Event object fired on Event.js gesture 9216 * @param {Event} [self] Inner Event object 9217 */ 9218 _onGesture: function(e, self) { 9219 this.__onTransformGesture && this.__onTransformGesture(e, self); 9220 }, 9221 9222 /** 9223 * @private 9224 * @param {Event} [e] Event object fired on Event.js drag 9225 * @param {Event} [self] Inner Event object 9226 */ 9227 _onDrag: function(e, self) { 9228 this.__onDrag && this.__onDrag(e, self); 9229 }, 9230 9231 /** 9232 * @private 9233 * @param {Event} [e] Event object fired on Event.js wheel event 9234 * @param {Event} [self] Inner Event object 9235 */ 9236 _onMouseWheel: function(e, self) { 9237 this.__onMouseWheel && this.__onMouseWheel(e, self); 9238 }, 9239 9240 /** 9241 * @private 9242 * @param {Event} [e] Event object fired on Event.js orientation change 9243 * @param {Event} [self] Inner Event object 9244 */ 9245 _onOrientationChange: function(e, self) { 9246 this.__onOrientationChange && this.__onOrientationChange(e, self); 9247 }, 9248 9249 /** 9250 * @private 9251 * @param {Event} [e] Event object fired on Event.js shake 9252 * @param {Event} [self] Inner Event object 9253 */ 9254 _onShake: function(e, self) { 9255 this.__onShake && this.__onShake(e, self); 9256 }, 9257 /** 9258 * @private 9259 * @param {Event} [e] Event object fired on Event.js shake 9260 * @param {Event} [self] Inner Event object 9261 */ 9262 _onLongPress: function(e, self) { 9263 this.__onLongPress && this.__onLongPress(e, self); 9264 }, 9265 9266 /** 9267 * @private 9268 * @param {Event} e Event object fired on mousedown 9269 */ 9270 _onMouseDown: function (e) { 9271 this.__onMouseDown(e); 9272 9273 addListener(fabric.document, 'touchend', this._onMouseUp); 9274 addListener(fabric.document, 'touchmove', this._onMouseMove); 9275 9276 removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); 9277 removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); 9278 9279 if (e.type === 'touchstart') { 9280 // Unbind mousedown to prevent double triggers from touch devices 9281 removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); 9282 } 9283 else { 9284 addListener(fabric.document, 'mouseup', this._onMouseUp); 9285 addListener(fabric.document, 'mousemove', this._onMouseMove); 9286 } 9287 }, 9288 9289 /** 9290 * @private 9291 * @param {Event} e Event object fired on mouseup 9292 */ 9293 _onMouseUp: function (e) { 9294 this.__onMouseUp(e); 9295 9296 removeListener(fabric.document, 'mouseup', this._onMouseUp); 9297 removeListener(fabric.document, 'touchend', this._onMouseUp); 9298 9299 removeListener(fabric.document, 'mousemove', this._onMouseMove); 9300 removeListener(fabric.document, 'touchmove', this._onMouseMove); 9301 9302 addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); 9303 addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); 9304 9305 if (e.type === 'touchend') { 9306 // Wait 400ms before rebinding mousedown to prevent double triggers 9307 // from touch devices 9308 var _this = this; 9309 setTimeout(function() { 9310 addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown); 9311 }, 400); 9312 } 9313 }, 9314 9315 /** 9316 * @private 9317 * @param {Event} e Event object fired on mousemove 9318 */ 9319 _onMouseMove: function (e) { 9320 !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); 9321 this.__onMouseMove(e); 9322 }, 9323 9324 /** 9325 * @private 9326 */ 9327 _onResize: function () { 9328 this.calcOffset(); 9329 }, 9330 9331 /** 9332 * Decides whether the canvas should be redrawn in mouseup and mousedown events. 9333 * @private 9334 * @param {Object} target 9335 * @param {Object} pointer 9336 */ 9337 _shouldRender: function(target, pointer) { 9338 var activeObject = this.getActiveGroup() || this.getActiveObject(); 9339 9340 return !!( 9341 (target && ( 9342 target.isMoving || 9343 target !== activeObject)) 9344 || 9345 (!target && !!activeObject) 9346 || 9347 (!target && !activeObject && !this._groupSelector) 9348 || 9349 (pointer && 9350 this._previousPointer && 9351 this.selection && ( 9352 pointer.x !== this._previousPointer.x || 9353 pointer.y !== this._previousPointer.y)) 9354 ); 9355 }, 9356 9357 /** 9358 * Method that defines the actions when mouse is released on canvas. 9359 * The method resets the currentTransform parameters, store the image corner 9360 * position in the image object and render the canvas on top. 9361 * @private 9362 * @param {Event} e Event object fired on mouseup 9363 */ 9364 __onMouseUp: function (e) { 9365 var target; 9366 9367 if (this.isDrawingMode && this._isCurrentlyDrawing) { 9368 this._onMouseUpInDrawingMode(e); 9369 return; 9370 } 9371 9372 if (this._currentTransform) { 9373 this._finalizeCurrentTransform(); 9374 target = this._currentTransform.target; 9375 } 9376 else { 9377 target = this.findTarget(e, true); 9378 } 9379 9380 var shouldRender = this._shouldRender(target, this.getPointer(e)); 9381 9382 this._maybeGroupObjects(e); 9383 9384 if (target) { 9385 target.isMoving = false; 9386 } 9387 9388 shouldRender && this.renderAll(); 9389 9390 this._handleCursorAndEvent(e, target); 9391 }, 9392 9393 _handleCursorAndEvent: function(e, target) { 9394 this._setCursorFromEvent(e, target); 9395 9396 // TODO: why are we doing this? 9397 var _this = this; 9398 setTimeout(function () { 9399 _this._setCursorFromEvent(e, target); 9400 }, 50); 9401 9402 this.fire('mouse:up', { target: target, e: e }); 9403 target && target.fire('mouseup', { e: e }); 9404 }, 9405 9406 /** 9407 * @private 9408 */ 9409 _finalizeCurrentTransform: function() { 9410 9411 var transform = this._currentTransform, 9412 target = transform.target; 9413 9414 if (target._scaling) { 9415 target._scaling = false; 9416 } 9417 9418 target.setCoords(); 9419 9420 // only fire :modified event if target coordinates were changed during mousedown-mouseup 9421 if (this.stateful && target.hasStateChanged()) { 9422 this.fire('object:modified', { target: target }); 9423 target.fire('modified'); 9424 } 9425 9426 this._restoreOriginXY(target); 9427 }, 9428 9429 /** 9430 * @private 9431 * @param {Object} target Object to restore 9432 */ 9433 _restoreOriginXY: function(target) { 9434 if (this._previousOriginX && this._previousOriginY) { 9435 9436 var originPoint = target.translateToOriginPoint( 9437 target.getCenterPoint(), 9438 this._previousOriginX, 9439 this._previousOriginY); 9440 9441 target.originX = this._previousOriginX; 9442 target.originY = this._previousOriginY; 9443 9444 target.left = originPoint.x; 9445 target.top = originPoint.y; 9446 9447 this._previousOriginX = null; 9448 this._previousOriginY = null; 9449 } 9450 }, 9451 9452 /** 9453 * @private 9454 * @param {Event} e Event object fired on mousedown 9455 */ 9456 _onMouseDownInDrawingMode: function(e) { 9457 this._isCurrentlyDrawing = true; 9458 this.discardActiveObject(e).renderAll(); 9459 if (this.clipTo) { 9460 fabric.util.clipContext(this, this.contextTop); 9461 } 9462 var ivt = fabric.util.invertTransform(this.viewportTransform), 9463 pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); 9464 this.freeDrawingBrush.onMouseDown(pointer); 9465 this.fire('mouse:down', { e: e }); 9466 9467 var target = this.findTarget(e); 9468 if (typeof target !== 'undefined') { 9469 target.fire('mousedown', { e: e, target: target }); 9470 } 9471 }, 9472 9473 /** 9474 * @private 9475 * @param {Event} e Event object fired on mousemove 9476 */ 9477 _onMouseMoveInDrawingMode: function(e) { 9478 if (this._isCurrentlyDrawing) { 9479 var ivt = fabric.util.invertTransform(this.viewportTransform), 9480 pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt); 9481 this.freeDrawingBrush.onMouseMove(pointer); 9482 } 9483 this.setCursor(this.freeDrawingCursor); 9484 this.fire('mouse:move', { e: e }); 9485 9486 var target = this.findTarget(e); 9487 if (typeof target !== 'undefined') { 9488 target.fire('mousemove', { e: e, target: target }); 9489 } 9490 }, 9491 9492 /** 9493 * @private 9494 * @param {Event} e Event object fired on mouseup 9495 */ 9496 _onMouseUpInDrawingMode: function(e) { 9497 this._isCurrentlyDrawing = false; 9498 if (this.clipTo) { 9499 this.contextTop.restore(); 9500 } 9501 this.freeDrawingBrush.onMouseUp(); 9502 this.fire('mouse:up', { e: e }); 9503 9504 var target = this.findTarget(e); 9505 if (typeof target !== 'undefined') { 9506 target.fire('mouseup', { e: e, target: target }); 9507 } 9508 }, 9509 9510 /** 9511 * Method that defines the actions when mouse is clic ked on canvas. 9512 * The method inits the currentTransform parameters and renders all the 9513 * canvas so the current image can be placed on the top canvas and the rest 9514 * in on the container one. 9515 * @private 9516 * @param {Event} e Event object fired on mousedown 9517 */ 9518 __onMouseDown: function (e) { 9519 9520 // accept only left clicks 9521 var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1; 9522 if (!isLeftClick && !fabric.isTouchSupported) { 9523 return; 9524 } 9525 9526 if (this.isDrawingMode) { 9527 this._onMouseDownInDrawingMode(e); 9528 return; 9529 } 9530 9531 // ignore if some object is being transformed at this moment 9532 if (this._currentTransform) { 9533 return; 9534 } 9535 9536 var target = this.findTarget(e), 9537 pointer = this.getPointer(e, true); 9538 9539 // save pointer for check in __onMouseUp event 9540 this._previousPointer = pointer; 9541 9542 var shouldRender = this._shouldRender(target, pointer), 9543 shouldGroup = this._shouldGroup(e, target); 9544 9545 if (this._shouldClearSelection(e, target)) { 9546 this._clearSelection(e, target, pointer); 9547 } 9548 else if (shouldGroup) { 9549 this._handleGrouping(e, target); 9550 target = this.getActiveGroup(); 9551 } 9552 9553 if (target && target.selectable && !shouldGroup) { 9554 this._beforeTransform(e, target); 9555 this._setupCurrentTransform(e, target); 9556 } 9557 // we must renderAll so that active image is placed on the top canvas 9558 shouldRender && this.renderAll(); 9559 9560 this.fire('mouse:down', { target: target, e: e }); 9561 target && target.fire('mousedown', { e: e }); 9562 }, 9563 9564 /** 9565 * @private 9566 */ 9567 _beforeTransform: function(e, target) { 9568 this.stateful && target.saveState(); 9569 9570 // determine if it's a drag or rotate case 9571 if (target._findTargetCorner(this.getPointer(e))) { 9572 this.onBeforeScaleRotate(target); 9573 } 9574 9575 if (target !== this.getActiveGroup() && target !== this.getActiveObject()) { 9576 this.deactivateAll(); 9577 this.setActiveObject(target, e); 9578 } 9579 }, 9580 9581 /** 9582 * @private 9583 */ 9584 _clearSelection: function(e, target, pointer) { 9585 this.deactivateAllWithDispatch(e); 9586 9587 if (target && target.selectable) { 9588 this.setActiveObject(target, e); 9589 } 9590 else if (this.selection) { 9591 this._groupSelector = { 9592 ex: pointer.x, 9593 ey: pointer.y, 9594 top: 0, 9595 left: 0 9596 }; 9597 } 9598 }, 9599 9600 /** 9601 * @private 9602 * @param {Object} target Object for that origin is set to center 9603 */ 9604 _setOriginToCenter: function(target) { 9605 this._previousOriginX = this._currentTransform.target.originX; 9606 this._previousOriginY = this._currentTransform.target.originY; 9607 9608 var center = target.getCenterPoint(); 9609 9610 target.originX = 'center'; 9611 target.originY = 'center'; 9612 9613 target.left = center.x; 9614 target.top = center.y; 9615 9616 this._currentTransform.left = target.left; 9617 this._currentTransform.top = target.top; 9618 }, 9619 9620 /** 9621 * @private 9622 * @param {Object} target Object for that center is set to origin 9623 */ 9624 _setCenterToOrigin: function(target) { 9625 var originPoint = target.translateToOriginPoint( 9626 target.getCenterPoint(), 9627 this._previousOriginX, 9628 this._previousOriginY); 9629 9630 target.originX = this._previousOriginX; 9631 target.originY = this._previousOriginY; 9632 9633 target.left = originPoint.x; 9634 target.top = originPoint.y; 9635 9636 this._previousOriginX = null; 9637 this._previousOriginY = null; 9638 }, 9639 9640 /** 9641 * Method that defines the actions when mouse is hovering the canvas. 9642 * The currentTransform parameter will definde whether the user is rotating/scaling/translating 9643 * an image or neither of them (only hovering). A group selection is also possible and would cancel 9644 * all any other type of action. 9645 * In case of an image transformation only the top canvas will be rendered. 9646 * @private 9647 * @param {Event} e Event object fired on mousemove 9648 */ 9649 __onMouseMove: function (e) { 9650 9651 var target, pointer; 9652 9653 if (this.isDrawingMode) { 9654 this._onMouseMoveInDrawingMode(e); 9655 return; 9656 } 9657 if (typeof e.touches !== 'undefined' && e.touches.length > 1) { 9658 return; 9659 } 9660 9661 var groupSelector = this._groupSelector; 9662 9663 // We initially clicked in an empty area, so we draw a box for multiple selection 9664 if (groupSelector) { 9665 pointer = this.getPointer(e, true); 9666 9667 groupSelector.left = pointer.x - groupSelector.ex; 9668 groupSelector.top = pointer.y - groupSelector.ey; 9669 9670 this.renderTop(); 9671 } 9672 else if (!this._currentTransform) { 9673 9674 target = this.findTarget(e); 9675 9676 if (!target || target && !target.selectable) { 9677 this.setCursor(this.defaultCursor); 9678 } 9679 else { 9680 this._setCursorFromEvent(e, target); 9681 } 9682 } 9683 else { 9684 this._transformObject(e); 9685 } 9686 9687 this.fire('mouse:move', { target: target, e: e }); 9688 target && target.fire('mousemove', { e: e }); 9689 }, 9690 9691 /** 9692 * @private 9693 * @param {Event} e Event fired on mousemove 9694 */ 9695 _transformObject: function(e) { 9696 var pointer = this.getPointer(e), 9697 transform = this._currentTransform; 9698 9699 transform.reset = false, 9700 transform.target.isMoving = true; 9701 9702 this._beforeScaleTransform(e, transform); 9703 this._performTransformAction(e, transform, pointer); 9704 9705 this.renderAll(); 9706 }, 9707 9708 /** 9709 * @private 9710 */ 9711 _performTransformAction: function(e, transform, pointer) { 9712 var x = pointer.x, 9713 y = pointer.y, 9714 target = transform.target, 9715 action = transform.action; 9716 9717 if (action === 'rotate') { 9718 this._rotateObject(x, y); 9719 this._fire('rotating', target, e); 9720 } 9721 else if (action === 'scale') { 9722 this._onScale(e, transform, x, y); 9723 this._fire('scaling', target, e); 9724 } 9725 else if (action === 'scaleX') { 9726 this._scaleObject(x, y, 'x'); 9727 this._fire('scaling', target, e); 9728 } 9729 else if (action === 'scaleY') { 9730 this._scaleObject(x, y, 'y'); 9731 this._fire('scaling', target, e); 9732 } 9733 else { 9734 this._translateObject(x, y); 9735 this._fire('moving', target, e); 9736 this.setCursor(this.moveCursor); 9737 } 9738 }, 9739 9740 /** 9741 * @private 9742 */ 9743 _fire: function(eventName, target, e) { 9744 this.fire('object:' + eventName, { target: target, e: e }); 9745 target.fire(eventName, { e: e }); 9746 }, 9747 9748 /** 9749 * @private 9750 */ 9751 _beforeScaleTransform: function(e, transform) { 9752 if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') { 9753 var centerTransform = this._shouldCenterTransform(e, transform.target); 9754 9755 // Switch from a normal resize to center-based 9756 if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) || 9757 // Switch from center-based resize to normal one 9758 (!centerTransform && transform.originX === 'center' && transform.originY === 'center') 9759 ) { 9760 this._resetCurrentTransform(e); 9761 transform.reset = true; 9762 } 9763 } 9764 }, 9765 9766 /** 9767 * @private 9768 */ 9769 _onScale: function(e, transform, x, y) { 9770 // rotate object only if shift key is not pressed 9771 // and if it is not a group we are transforming 9772 if ((e.shiftKey || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) { 9773 transform.currentAction = 'scale'; 9774 this._scaleObject(x, y); 9775 } 9776 else { 9777 // Switch from a normal resize to proportional 9778 if (!transform.reset && transform.currentAction === 'scale') { 9779 this._resetCurrentTransform(e, transform.target); 9780 } 9781 9782 transform.currentAction = 'scaleEqually'; 9783 this._scaleObject(x, y, 'equally'); 9784 } 9785 }, 9786 9787 /** 9788 * Sets the cursor depending on where the canvas is being hovered. 9789 * Note: very buggy in Opera 9790 * @param {Event} e Event object 9791 * @param {Object} target Object that the mouse is hovering, if so. 9792 */ 9793 _setCursorFromEvent: function (e, target) { 9794 if (!target || !target.selectable) { 9795 this.setCursor(this.defaultCursor); 9796 return false; 9797 } 9798 else { 9799 var activeGroup = this.getActiveGroup(), 9800 // only show proper corner when group selection is not active 9801 corner = target._findTargetCorner 9802 && (!activeGroup || !activeGroup.contains(target)) 9803 && target._findTargetCorner(this.getPointer(e, true)); 9804 9805 if (!corner) { 9806 this.setCursor(target.hoverCursor || this.hoverCursor); 9807 } 9808 else { 9809 this._setCornerCursor(corner, target); 9810 } 9811 } 9812 return true; 9813 }, 9814 9815 /** 9816 * @private 9817 */ 9818 _setCornerCursor: function(corner, target) { 9819 if (corner in cursorOffset) { 9820 this.setCursor(this._getRotatedCornerCursor(corner, target)); 9821 } 9822 else if (corner === 'mtr' && target.hasRotatingPoint) { 9823 this.setCursor(this.rotationCursor); 9824 } 9825 else { 9826 this.setCursor(this.defaultCursor); 9827 return false; 9828 } 9829 }, 9830 9831 /** 9832 * @private 9833 */ 9834 _getRotatedCornerCursor: function(corner, target) { 9835 var n = Math.round((target.getAngle() % 360) / 45); 9836 9837 if (n < 0) { 9838 n += 8; // full circle ahead 9839 } 9840 n += cursorOffset[corner]; 9841 // normalize n to be from 0 to 7 9842 n %= 8; 9843 9844 return this.cursorMap[n]; 9845 } 9846 }); 9847})(); 9848 9849 9850(function() { 9851 9852 var min = Math.min, 9853 max = Math.max; 9854 9855 fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { 9856 9857 /** 9858 * @private 9859 * @param {Event} e Event object 9860 * @param {fabric.Object} target 9861 * @return {Boolean} 9862 */ 9863 _shouldGroup: function(e, target) { 9864 var activeObject = this.getActiveObject(); 9865 return e.shiftKey && 9866 (this.getActiveGroup() || (activeObject && activeObject !== target)) 9867 && this.selection; 9868 }, 9869 9870 /** 9871 * @private 9872 * @param {Event} e Event object 9873 * @param {fabric.Object} target 9874 */ 9875 _handleGrouping: function (e, target) { 9876 9877 if (target === this.getActiveGroup()) { 9878 9879 // if it's a group, find target again, this time skipping group 9880 target = this.findTarget(e, true); 9881 9882 // if even object is not found, bail out 9883 if (!target || target.isType('group')) { 9884 return; 9885 } 9886 } 9887 if (this.getActiveGroup()) { 9888 this._updateActiveGroup(target, e); 9889 } 9890 else { 9891 this._createActiveGroup(target, e); 9892 } 9893 9894 if (this._activeGroup) { 9895 this._activeGroup.saveCoords(); 9896 } 9897 }, 9898 9899 /** 9900 * @private 9901 */ 9902 _updateActiveGroup: function(target, e) { 9903 var activeGroup = this.getActiveGroup(); 9904 9905 if (activeGroup.contains(target)) { 9906 9907 activeGroup.removeWithUpdate(target); 9908 this._resetObjectTransform(activeGroup); 9909 target.set('active', false); 9910 9911 if (activeGroup.size() === 1) { 9912 // remove group alltogether if after removal it only contains 1 object 9913 this.discardActiveGroup(e); 9914 // activate last remaining object 9915 this.setActiveObject(activeGroup.item(0)); 9916 return; 9917 } 9918 } 9919 else { 9920 activeGroup.addWithUpdate(target); 9921 this._resetObjectTransform(activeGroup); 9922 } 9923 this.fire('selection:created', { target: activeGroup, e: e }); 9924 activeGroup.set('active', true); 9925 }, 9926 9927 /** 9928 * @private 9929 */ 9930 _createActiveGroup: function(target, e) { 9931 9932 if (this._activeObject && target !== this._activeObject) { 9933 9934 var group = this._createGroup(target); 9935 group.addWithUpdate(); 9936 9937 this.setActiveGroup(group); 9938 this._activeObject = null; 9939 9940 this.fire('selection:created', { target: group, e: e }); 9941 } 9942 9943 target.set('active', true); 9944 }, 9945 9946 /** 9947 * @private 9948 * @param {Object} target 9949 */ 9950 _createGroup: function(target) { 9951 9952 var objects = this.getObjects(), 9953 isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), 9954 groupObjects = isActiveLower 9955 ? [ this._activeObject, target ] 9956 : [ target, this._activeObject ]; 9957 9958 return new fabric.Group(groupObjects, { 9959 canvas: this 9960 }); 9961 }, 9962 9963 /** 9964 * @private 9965 * @param {Event} e mouse event 9966 */ 9967 _groupSelectedObjects: function (e) { 9968 9969 var group = this._collectObjects(); 9970 9971 // do not create group for 1 element only 9972 if (group.length === 1) { 9973 this.setActiveObject(group[0], e); 9974 } 9975 else if (group.length > 1) { 9976 group = new fabric.Group(group.reverse(), { 9977 canvas: this 9978 }); 9979 group.addWithUpdate(); 9980 this.setActiveGroup(group, e); 9981 group.saveCoords(); 9982 this.fire('selection:created', { target: group }); 9983 this.renderAll(); 9984 } 9985 }, 9986 9987 /** 9988 * @private 9989 */ 9990 _collectObjects: function() { 9991 var group = [ ], 9992 currentObject, 9993 x1 = this._groupSelector.ex, 9994 y1 = this._groupSelector.ey, 9995 x2 = x1 + this._groupSelector.left, 9996 y2 = y1 + this._groupSelector.top, 9997 selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), 9998 selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), 9999 isClick = x1 === x2 && y1 === y2; 10000 10001 for (var i = this._objects.length; i--; ) { 10002 currentObject = this._objects[i]; 10003 10004 if (!currentObject || !currentObject.selectable || !currentObject.visible) { 10005 continue; 10006 } 10007 10008 if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || 10009 currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) || 10010 currentObject.containsPoint(selectionX1Y1) || 10011 currentObject.containsPoint(selectionX2Y2) 10012 ) { 10013 currentObject.set('active', true); 10014 group.push(currentObject); 10015 10016 // only add one object if it's a click 10017 if (isClick) { 10018 break; 10019 } 10020 } 10021 } 10022 10023 return group; 10024 }, 10025 10026 /** 10027 * @private 10028 */ 10029 _maybeGroupObjects: function(e) { 10030 if (this.selection && this._groupSelector) { 10031 this._groupSelectedObjects(e); 10032 } 10033 10034 var activeGroup = this.getActiveGroup(); 10035 if (activeGroup) { 10036 activeGroup.setObjectsCoords().setCoords(); 10037 activeGroup.isMoving = false; 10038 this.setCursor(this.defaultCursor); 10039 } 10040 10041 // clear selection and current transformation 10042 this._groupSelector = null; 10043 this._currentTransform = null; 10044 } 10045 }); 10046 10047})(); 10048 10049 10050fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { 10051 10052 /** 10053 * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately 10054 * @param {Object} [options] Options object 10055 * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" 10056 * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. 10057 * @param {Number} [options.multiplier=1] Multiplier to scale by 10058 * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 10059 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 10060 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 10061 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 10062 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format 10063 * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} 10064 * @example <caption>Generate jpeg dataURL with lower quality</caption> 10065 * var dataURL = canvas.toDataURL({ 10066 * format: 'jpeg', 10067 * quality: 0.8 10068 * }); 10069 * @example <caption>Generate cropped png dataURL (clipping of canvas)</caption> 10070 * var dataURL = canvas.toDataURL({ 10071 * format: 'png', 10072 * left: 100, 10073 * top: 100, 10074 * width: 200, 10075 * height: 200 10076 * }); 10077 * @example <caption>Generate double scaled png dataURL</caption> 10078 * var dataURL = canvas.toDataURL({ 10079 * format: 'png', 10080 * multiplier: 2 10081 * }); 10082 */ 10083 toDataURL: function (options) { 10084 options || (options = { }); 10085 10086 var format = options.format || 'png', 10087 quality = options.quality || 1, 10088 multiplier = options.multiplier || 1, 10089 cropping = { 10090 left: options.left, 10091 top: options.top, 10092 width: options.width, 10093 height: options.height 10094 }; 10095 10096 if (multiplier !== 1) { 10097 return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); 10098 } 10099 else { 10100 return this.__toDataURL(format, quality, cropping); 10101 } 10102 }, 10103 10104 /** 10105 * @private 10106 */ 10107 __toDataURL: function(format, quality, cropping) { 10108 10109 this.renderAll(true); 10110 10111 var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, 10112 croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); 10113 10114 // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 10115 if (format === 'jpg') { 10116 format = 'jpeg'; 10117 } 10118 10119 var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) 10120 ? (croppedCanvasEl || canvasEl).toDataURL('image/' + format, quality) 10121 : (croppedCanvasEl || canvasEl).toDataURL('image/' + format); 10122 10123 this.contextTop && this.clearContext(this.contextTop); 10124 this.renderAll(); 10125 10126 if (croppedCanvasEl) { 10127 croppedCanvasEl = null; 10128 } 10129 10130 return data; 10131 }, 10132 10133 /** 10134 * @private 10135 */ 10136 __getCroppedCanvas: function(canvasEl, cropping) { 10137 10138 var croppedCanvasEl, 10139 croppedCtx, 10140 shouldCrop = 'left' in cropping || 10141 'top' in cropping || 10142 'width' in cropping || 10143 'height' in cropping; 10144 10145 if (shouldCrop) { 10146 10147 croppedCanvasEl = fabric.util.createCanvasElement(); 10148 croppedCtx = croppedCanvasEl.getContext('2d'); 10149 10150 croppedCanvasEl.width = cropping.width || this.width; 10151 croppedCanvasEl.height = cropping.height || this.height; 10152 10153 croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0); 10154 } 10155 10156 return croppedCanvasEl; 10157 }, 10158 10159 /** 10160 * @private 10161 */ 10162 __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { 10163 10164 var origWidth = this.getWidth(), 10165 origHeight = this.getHeight(), 10166 scaledWidth = origWidth * multiplier, 10167 scaledHeight = origHeight * multiplier, 10168 activeObject = this.getActiveObject(), 10169 activeGroup = this.getActiveGroup(), 10170 10171 ctx = this.contextTop || this.contextContainer; 10172 10173 if (multiplier > 1) { 10174 this.setWidth(scaledWidth).setHeight(scaledHeight); 10175 } 10176 ctx.scale(multiplier, multiplier); 10177 10178 if (cropping.left) { 10179 cropping.left *= multiplier; 10180 } 10181 if (cropping.top) { 10182 cropping.top *= multiplier; 10183 } 10184 if (cropping.width) { 10185 cropping.width *= multiplier; 10186 } 10187 else if (multiplier < 1) { 10188 cropping.width = scaledWidth; 10189 } 10190 if (cropping.height) { 10191 cropping.height *= multiplier; 10192 } 10193 else if (multiplier < 1) { 10194 cropping.height = scaledHeight; 10195 } 10196 10197 if (activeGroup) { 10198 // not removing group due to complications with restoring it with correct state afterwords 10199 this._tempRemoveBordersControlsFromGroup(activeGroup); 10200 } 10201 else if (activeObject && this.deactivateAll) { 10202 this.deactivateAll(); 10203 } 10204 10205 this.renderAll(true); 10206 10207 var data = this.__toDataURL(format, quality, cropping); 10208 10209 // restoring width, height for `renderAll` to draw 10210 // background properly (while context is scaled) 10211 this.width = origWidth; 10212 this.height = origHeight; 10213 10214 ctx.scale(1 / multiplier, 1 / multiplier); 10215 this.setWidth(origWidth).setHeight(origHeight); 10216 10217 if (activeGroup) { 10218 this._restoreBordersControlsOnGroup(activeGroup); 10219 } 10220 else if (activeObject && this.setActiveObject) { 10221 this.setActiveObject(activeObject); 10222 } 10223 10224 this.contextTop && this.clearContext(this.contextTop); 10225 this.renderAll(); 10226 10227 return data; 10228 }, 10229 10230 /** 10231 * Exports canvas element to a dataurl image (allowing to change image size via multiplier). 10232 * @deprecated since 1.0.13 10233 * @param {String} format (png|jpeg) 10234 * @param {Number} multiplier 10235 * @param {Number} quality (0..1) 10236 * @return {String} 10237 */ 10238 toDataURLWithMultiplier: function (format, multiplier, quality) { 10239 return this.toDataURL({ 10240 format: format, 10241 multiplier: multiplier, 10242 quality: quality 10243 }); 10244 }, 10245 10246 /** 10247 * @private 10248 */ 10249 _tempRemoveBordersControlsFromGroup: function(group) { 10250 group.origHasControls = group.hasControls; 10251 group.origBorderColor = group.borderColor; 10252 10253 group.hasControls = true; 10254 group.borderColor = 'rgba(0,0,0,0)'; 10255 10256 group.forEachObject(function(o) { 10257 o.origBorderColor = o.borderColor; 10258 o.borderColor = 'rgba(0,0,0,0)'; 10259 }); 10260 }, 10261 10262 /** 10263 * @private 10264 */ 10265 _restoreBordersControlsOnGroup: function(group) { 10266 group.hideControls = group.origHideControls; 10267 group.borderColor = group.origBorderColor; 10268 10269 group.forEachObject(function(o) { 10270 o.borderColor = o.origBorderColor; 10271 delete o.origBorderColor; 10272 }); 10273 } 10274}); 10275 10276 10277fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { 10278 10279 /** 10280 * Populates canvas with data from the specified dataless JSON. 10281 * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} 10282 * @deprecated since 1.2.2 10283 * @param {String|Object} json JSON string or object 10284 * @param {Function} callback Callback, invoked when json is parsed 10285 * and corresponding objects (e.g: {@link fabric.Image}) 10286 * are initialized 10287 * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. 10288 * @return {fabric.Canvas} instance 10289 * @chainable 10290 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} 10291 */ 10292 loadFromDatalessJSON: function (json, callback, reviver) { 10293 return this.loadFromJSON(json, callback, reviver); 10294 }, 10295 10296 /** 10297 * Populates canvas with data from the specified JSON. 10298 * JSON format must conform to the one of {@link fabric.Canvas#toJSON} 10299 * @param {String|Object} json JSON string or object 10300 * @param {Function} callback Callback, invoked when json is parsed 10301 * and corresponding objects (e.g: {@link fabric.Image}) 10302 * are initialized 10303 * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. 10304 * @return {fabric.Canvas} instance 10305 * @chainable 10306 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} 10307 * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} 10308 * @example <caption>loadFromJSON</caption> 10309 * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); 10310 * @example <caption>loadFromJSON with reviver</caption> 10311 * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { 10312 * // `o` = json object 10313 * // `object` = fabric.Object instance 10314 * // ... do some stuff ... 10315 * }); 10316 */ 10317 loadFromJSON: function (json, callback, reviver) { 10318 if (!json) { 10319 return; 10320 } 10321 10322 // serialize if it wasn't already 10323 var serialized = (typeof json === 'string') 10324 ? JSON.parse(json) 10325 : json; 10326 10327 this.clear(); 10328 10329 var _this = this; 10330 this._enlivenObjects(serialized.objects, function () { 10331 _this._setBgOverlay(serialized, callback); 10332 }, reviver); 10333 10334 return this; 10335 }, 10336 10337 /** 10338 * @private 10339 * @param {Object} serialized Object with background and overlay information 10340 * @param {Function} callback Invoked after all background and overlay images/patterns loaded 10341 */ 10342 _setBgOverlay: function(serialized, callback) { 10343 var _this = this, 10344 loaded = { 10345 backgroundColor: false, 10346 overlayColor: false, 10347 backgroundImage: false, 10348 overlayImage: false 10349 }; 10350 10351 if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { 10352 callback && callback(); 10353 return; 10354 } 10355 10356 var cbIfLoaded = function () { 10357 if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { 10358 _this.renderAll(); 10359 callback && callback(); 10360 } 10361 }; 10362 10363 this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); 10364 this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); 10365 this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); 10366 this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); 10367 10368 cbIfLoaded(); 10369 }, 10370 10371 /** 10372 * @private 10373 * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) 10374 * @param {(Object|String)} value Value to set 10375 * @param {Object} loaded Set loaded property to true if property is set 10376 * @param {Object} callback Callback function to invoke after property is set 10377 */ 10378 __setBgOverlay: function(property, value, loaded, callback) { 10379 var _this = this; 10380 10381 if (!value) { 10382 loaded[property] = true; 10383 return; 10384 } 10385 10386 if (property === 'backgroundImage' || property === 'overlayImage') { 10387 fabric.Image.fromObject(value, function(img) { 10388 _this[property] = img; 10389 loaded[property] = true; 10390 callback && callback(); 10391 }); 10392 } 10393 else { 10394 this['set' + fabric.util.string.capitalize(property, true)](value, function() { 10395 loaded[property] = true; 10396 callback && callback(); 10397 }); 10398 } 10399 }, 10400 10401 /** 10402 * @private 10403 * @param {Array} objects 10404 * @param {Function} callback 10405 * @param {Function} [reviver] 10406 */ 10407 _enlivenObjects: function (objects, callback, reviver) { 10408 var _this = this; 10409 10410 if (!objects || objects.length === 0) { 10411 callback && callback(); 10412 return; 10413 } 10414 10415 var renderOnAddRemove = this.renderOnAddRemove; 10416 this.renderOnAddRemove = false; 10417 10418 fabric.util.enlivenObjects(objects, function(enlivenedObjects) { 10419 enlivenedObjects.forEach(function(obj, index) { 10420 _this.insertAt(obj, index, true); 10421 }); 10422 10423 _this.renderOnAddRemove = renderOnAddRemove; 10424 callback && callback(); 10425 }, null, reviver); 10426 }, 10427 10428 /** 10429 * @private 10430 * @param {String} format 10431 * @param {Function} callback 10432 */ 10433 _toDataURL: function (format, callback) { 10434 this.clone(function (clone) { 10435 callback(clone.toDataURL(format)); 10436 }); 10437 }, 10438 10439 /** 10440 * @private 10441 * @param {String} format 10442 * @param {Number} multiplier 10443 * @param {Function} callback 10444 */ 10445 _toDataURLWithMultiplier: function (format, multiplier, callback) { 10446 this.clone(function (clone) { 10447 callback(clone.toDataURLWithMultiplier(format, multiplier)); 10448 }); 10449 }, 10450 10451 /** 10452 * Clones canvas instance 10453 * @param {Object} [callback] Receives cloned instance as a first argument 10454 * @param {Array} [properties] Array of properties to include in the cloned canvas and children 10455 */ 10456 clone: function (callback, properties) { 10457 var data = JSON.stringify(this.toJSON(properties)); 10458 this.cloneWithoutData(function(clone) { 10459 clone.loadFromJSON(data, function() { 10460 callback && callback(clone); 10461 }); 10462 }); 10463 }, 10464 10465 /** 10466 * Clones canvas instance without cloning existing data. 10467 * This essentially copies canvas dimensions, clipping properties, etc. 10468 * but leaves data empty (so that you can populate it with your own) 10469 * @param {Object} [callback] Receives cloned instance as a first argument 10470 */ 10471 cloneWithoutData: function(callback) { 10472 var el = fabric.document.createElement('canvas'); 10473 10474 el.width = this.getWidth(); 10475 el.height = this.getHeight(); 10476 10477 var clone = new fabric.Canvas(el); 10478 clone.clipTo = this.clipTo; 10479 if (this.backgroundImage) { 10480 clone.setBackgroundImage(this.backgroundImage.src, function() { 10481 clone.renderAll(); 10482 callback && callback(clone); 10483 }); 10484 clone.backgroundImageOpacity = this.backgroundImageOpacity; 10485 clone.backgroundImageStretch = this.backgroundImageStretch; 10486 } 10487 else { 10488 callback && callback(clone); 10489 } 10490 } 10491}); 10492 10493 10494(function(global) { 10495 10496 'use strict'; 10497 10498 var fabric = global.fabric || (global.fabric = { }), 10499 extend = fabric.util.object.extend, 10500 toFixed = fabric.util.toFixed, 10501 capitalize = fabric.util.string.capitalize, 10502 degreesToRadians = fabric.util.degreesToRadians, 10503 supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); 10504 10505 if (fabric.Object) { 10506 return; 10507 } 10508 10509 /** 10510 * Root object class from which all 2d shape classes inherit from 10511 * @class fabric.Object 10512 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects} 10513 * @see {@link fabric.Object#initialize} for constructor definition 10514 * 10515 * @fires added 10516 * @fires removed 10517 * 10518 * @fires selected 10519 * @fires modified 10520 * @fires rotating 10521 * @fires scaling 10522 * @fires moving 10523 * 10524 * @fires mousedown 10525 * @fires mouseup 10526 */ 10527 fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ { 10528 10529 /** 10530 * Retrieves object's {@link fabric.Object#clipTo|clipping function} 10531 * @method getClipTo 10532 * @memberOf fabric.Object.prototype 10533 * @return {Function} 10534 */ 10535 10536 /** 10537 * Sets object's {@link fabric.Object#clipTo|clipping function} 10538 * @method setClipTo 10539 * @memberOf fabric.Object.prototype 10540 * @param {Function} clipTo Clipping function 10541 * @return {fabric.Object} thisArg 10542 * @chainable 10543 */ 10544 10545 /** 10546 * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix} 10547 * @method getTransformMatrix 10548 * @memberOf fabric.Object.prototype 10549 * @return {Array} transformMatrix 10550 */ 10551 10552 /** 10553 * Sets object's {@link fabric.Object#transformMatrix|transformMatrix} 10554 * @method setTransformMatrix 10555 * @memberOf fabric.Object.prototype 10556 * @param {Array} transformMatrix 10557 * @return {fabric.Object} thisArg 10558 * @chainable 10559 */ 10560 10561 /** 10562 * Retrieves object's {@link fabric.Object#visible|visible} state 10563 * @method getVisible 10564 * @memberOf fabric.Object.prototype 10565 * @return {Boolean} True if visible 10566 */ 10567 10568 /** 10569 * Sets object's {@link fabric.Object#visible|visible} state 10570 * @method setVisible 10571 * @memberOf fabric.Object.prototype 10572 * @param {Boolean} value visible value 10573 * @return {fabric.Object} thisArg 10574 * @chainable 10575 */ 10576 10577 /** 10578 * Retrieves object's {@link fabric.Object#shadow|shadow} 10579 * @method getShadow 10580 * @memberOf fabric.Object.prototype 10581 * @return {Object} Shadow instance 10582 */ 10583 10584 /** 10585 * Retrieves object's {@link fabric.Object#stroke|stroke} 10586 * @method getStroke 10587 * @memberOf fabric.Object.prototype 10588 * @return {String} stroke value 10589 */ 10590 10591 /** 10592 * Sets object's {@link fabric.Object#stroke|stroke} 10593 * @method setStroke 10594 * @memberOf fabric.Object.prototype 10595 * @param {String} value stroke value 10596 * @return {fabric.Object} thisArg 10597 * @chainable 10598 */ 10599 10600 /** 10601 * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth} 10602 * @method getStrokeWidth 10603 * @memberOf fabric.Object.prototype 10604 * @return {Number} strokeWidth value 10605 */ 10606 10607 /** 10608 * Sets object's {@link fabric.Object#strokeWidth|strokeWidth} 10609 * @method setStrokeWidth 10610 * @memberOf fabric.Object.prototype 10611 * @param {Number} value strokeWidth value 10612 * @return {fabric.Object} thisArg 10613 * @chainable 10614 */ 10615 10616 /** 10617 * Retrieves object's {@link fabric.Object#originX|originX} 10618 * @method getOriginX 10619 * @memberOf fabric.Object.prototype 10620 * @return {String} originX value 10621 */ 10622 10623 /** 10624 * Sets object's {@link fabric.Object#originX|originX} 10625 * @method setOriginX 10626 * @memberOf fabric.Object.prototype 10627 * @param {String} value originX value 10628 * @return {fabric.Object} thisArg 10629 * @chainable 10630 */ 10631 10632 /** 10633 * Retrieves object's {@link fabric.Object#originY|originY} 10634 * @method getOriginY 10635 * @memberOf fabric.Object.prototype 10636 * @return {String} originY value 10637 */ 10638 10639 /** 10640 * Sets object's {@link fabric.Object#originY|originY} 10641 * @method setOriginY 10642 * @memberOf fabric.Object.prototype 10643 * @param {String} value originY value 10644 * @return {fabric.Object} thisArg 10645 * @chainable 10646 */ 10647 10648 /** 10649 * Retrieves object's {@link fabric.Object#fill|fill} 10650 * @method getFill 10651 * @memberOf fabric.Object.prototype 10652 * @return {String} Fill value 10653 */ 10654 10655 /** 10656 * Sets object's {@link fabric.Object#fill|fill} 10657 * @method setFill 10658 * @memberOf fabric.Object.prototype 10659 * @param {String} value Fill value 10660 * @return {fabric.Object} thisArg 10661 * @chainable 10662 */ 10663 10664 /** 10665 * Retrieves object's {@link fabric.Object#opacity|opacity} 10666 * @method getOpacity 10667 * @memberOf fabric.Object.prototype 10668 * @return {Number} Opacity value (0-1) 10669 */ 10670 10671 /** 10672 * Sets object's {@link fabric.Object#opacity|opacity} 10673 * @method setOpacity 10674 * @memberOf fabric.Object.prototype 10675 * @param {Number} value Opacity value (0-1) 10676 * @return {fabric.Object} thisArg 10677 * @chainable 10678 */ 10679 10680 /** 10681 * Retrieves object's {@link fabric.Object#angle|angle} (in degrees) 10682 * @method getAngle 10683 * @memberOf fabric.Object.prototype 10684 * @return {Number} 10685 */ 10686 10687 /** 10688 * Retrieves object's {@link fabric.Object#top|top position} 10689 * @method getTop 10690 * @memberOf fabric.Object.prototype 10691 * @return {Number} Top value (in pixels) 10692 */ 10693 10694 /** 10695 * Sets object's {@link fabric.Object#top|top position} 10696 * @method setTop 10697 * @memberOf fabric.Object.prototype 10698 * @param {Number} value Top value (in pixels) 10699 * @return {fabric.Object} thisArg 10700 * @chainable 10701 */ 10702 10703 /** 10704 * Retrieves object's {@link fabric.Object#left|left position} 10705 * @method getLeft 10706 * @memberOf fabric.Object.prototype 10707 * @return {Number} Left value (in pixels) 10708 */ 10709 10710 /** 10711 * Sets object's {@link fabric.Object#left|left position} 10712 * @method setLeft 10713 * @memberOf fabric.Object.prototype 10714 * @param {Number} value Left value (in pixels) 10715 * @return {fabric.Object} thisArg 10716 * @chainable 10717 */ 10718 10719 /** 10720 * Retrieves object's {@link fabric.Object#scaleX|scaleX} value 10721 * @method getScaleX 10722 * @memberOf fabric.Object.prototype 10723 * @return {Number} scaleX value 10724 */ 10725 10726 /** 10727 * Sets object's {@link fabric.Object#scaleX|scaleX} value 10728 * @method setScaleX 10729 * @memberOf fabric.Object.prototype 10730 * @param {Number} value scaleX value 10731 * @return {fabric.Object} thisArg 10732 * @chainable 10733 */ 10734 10735 /** 10736 * Retrieves object's {@link fabric.Object#scaleY|scaleY} value 10737 * @method getScaleY 10738 * @memberOf fabric.Object.prototype 10739 * @return {Number} scaleY value 10740 */ 10741 10742 /** 10743 * Sets object's {@link fabric.Object#scaleY|scaleY} value 10744 * @method setScaleY 10745 * @memberOf fabric.Object.prototype 10746 * @param {Number} value scaleY value 10747 * @return {fabric.Object} thisArg 10748 * @chainable 10749 */ 10750 10751 /** 10752 * Retrieves object's {@link fabric.Object#flipX|flipX} value 10753 * @method getFlipX 10754 * @memberOf fabric.Object.prototype 10755 * @return {Boolean} flipX value 10756 */ 10757 10758 /** 10759 * Sets object's {@link fabric.Object#flipX|flipX} value 10760 * @method setFlipX 10761 * @memberOf fabric.Object.prototype 10762 * @param {Boolean} value flipX value 10763 * @return {fabric.Object} thisArg 10764 * @chainable 10765 */ 10766 10767 /** 10768 * Retrieves object's {@link fabric.Object#flipY|flipY} value 10769 * @method getFlipY 10770 * @memberOf fabric.Object.prototype 10771 * @return {Boolean} flipY value 10772 */ 10773 10774 /** 10775 * Sets object's {@link fabric.Object#flipY|flipY} value 10776 * @method setFlipY 10777 * @memberOf fabric.Object.prototype 10778 * @param {Boolean} value flipY value 10779 * @return {fabric.Object} thisArg 10780 * @chainable 10781 */ 10782 10783 /** 10784 * Type of an object (rect, circle, path, etc.). 10785 * Note that this property is meant to be read-only and not meant to be modified. 10786 * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly. 10787 * @type String 10788 * @default 10789 */ 10790 type: 'object', 10791 10792 /** 10793 * Horizontal origin of transformation of an object (one of "left", "right", "center") 10794 * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups 10795 * @type String 10796 * @default 10797 */ 10798 originX: 'left', 10799 10800 /** 10801 * Vertical origin of transformation of an object (one of "top", "bottom", "center") 10802 * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups 10803 * @type String 10804 * @default 10805 */ 10806 originY: 'top', 10807 10808 /** 10809 * Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom} 10810 * @type Number 10811 * @default 10812 */ 10813 top: 0, 10814 10815 /** 10816 * Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right} 10817 * @type Number 10818 * @default 10819 */ 10820 left: 0, 10821 10822 /** 10823 * Object width 10824 * @type Number 10825 * @default 10826 */ 10827 width: 0, 10828 10829 /** 10830 * Object height 10831 * @type Number 10832 * @default 10833 */ 10834 height: 0, 10835 10836 /** 10837 * Object scale factor (horizontal) 10838 * @type Number 10839 * @default 10840 */ 10841 scaleX: 1, 10842 10843 /** 10844 * Object scale factor (vertical) 10845 * @type Number 10846 * @default 10847 */ 10848 scaleY: 1, 10849 10850 /** 10851 * When true, an object is rendered as flipped horizontally 10852 * @type Boolean 10853 * @default 10854 */ 10855 flipX: false, 10856 10857 /** 10858 * When true, an object is rendered as flipped vertically 10859 * @type Boolean 10860 * @default 10861 */ 10862 flipY: false, 10863 10864 /** 10865 * Opacity of an object 10866 * @type Number 10867 * @default 10868 */ 10869 opacity: 1, 10870 10871 /** 10872 * Angle of rotation of an object (in degrees) 10873 * @type Number 10874 * @default 10875 */ 10876 angle: 0, 10877 10878 /** 10879 * Size of object's controlling corners (in pixels) 10880 * @type Number 10881 * @default 10882 */ 10883 cornerSize: 12, 10884 10885 /** 10886 * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) 10887 * @type Boolean 10888 * @default 10889 */ 10890 transparentCorners: true, 10891 10892 /** 10893 * Default cursor value used when hovering over this object on canvas 10894 * @type String 10895 * @default 10896 */ 10897 hoverCursor: null, 10898 10899 /** 10900 * Padding between object and its controlling borders (in pixels) 10901 * @type Number 10902 * @default 10903 */ 10904 padding: 0, 10905 10906 /** 10907 * Color of controlling borders of an object (when it's active) 10908 * @type String 10909 * @default 10910 */ 10911 borderColor: 'rgba(102,153,255,0.75)', 10912 10913 /** 10914 * Color of controlling corners of an object (when it's active) 10915 * @type String 10916 * @default 10917 */ 10918 cornerColor: 'rgba(102,153,255,0.5)', 10919 10920 /** 10921 * When true, this object will use center point as the origin of transformation 10922 * when being scaled via the controls. 10923 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 10924 * @since 1.3.4 10925 * @type Boolean 10926 * @default 10927 */ 10928 centeredScaling: false, 10929 10930 /** 10931 * When true, this object will use center point as the origin of transformation 10932 * when being rotated via the controls. 10933 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 10934 * @since 1.3.4 10935 * @type Boolean 10936 * @default 10937 */ 10938 centeredRotation: true, 10939 10940 /** 10941 * Color of object's fill 10942 * @type String 10943 * @default 10944 */ 10945 fill: 'rgb(0,0,0)', 10946 10947 /** 10948 * Fill rule used to fill an object 10949 * accepted values are nonzero, evenodd 10950 * <b>Backwards incompatibility note:</b> This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead) 10951 * @type String 10952 * @default 10953 */ 10954 fillRule: 'nonzero', 10955 10956 /** 10957 * Composite rule used for canvas globalCompositeOperation 10958 * @type String 10959 * @default 10960 */ 10961 globalCompositeOperation: 'source-over', 10962 10963 /** 10964 * Background color of an object. Only works with text objects at the moment. 10965 * @type String 10966 * @default 10967 */ 10968 backgroundColor: '', 10969 10970 /** 10971 * When defined, an object is rendered via stroke and this property specifies its color 10972 * @type String 10973 * @default 10974 */ 10975 stroke: null, 10976 10977 /** 10978 * Width of a stroke used to render this object 10979 * @type Number 10980 * @default 10981 */ 10982 strokeWidth: 1, 10983 10984 /** 10985 * Array specifying dash pattern of an object's stroke (stroke must be defined) 10986 * @type Array 10987 */ 10988 strokeDashArray: null, 10989 10990 /** 10991 * Line endings style of an object's stroke (one of "butt", "round", "square") 10992 * @type String 10993 * @default 10994 */ 10995 strokeLineCap: 'butt', 10996 10997 /** 10998 * Corner style of an object's stroke (one of "bevil", "round", "miter") 10999 * @type String 11000 * @default 11001 */ 11002 strokeLineJoin: 'miter', 11003 11004 /** 11005 * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke 11006 * @type Number 11007 * @default 11008 */ 11009 strokeMiterLimit: 10, 11010 11011 /** 11012 * Shadow object representing shadow of this shape 11013 * @type fabric.Shadow 11014 * @default 11015 */ 11016 shadow: null, 11017 11018 /** 11019 * Opacity of object's controlling borders when object is active and moving 11020 * @type Number 11021 * @default 11022 */ 11023 borderOpacityWhenMoving: 0.4, 11024 11025 /** 11026 * Scale factor of object's controlling borders 11027 * @type Number 11028 * @default 11029 */ 11030 borderScaleFactor: 1, 11031 11032 /** 11033 * Transform matrix (similar to SVG's transform matrix) 11034 * @type Array 11035 */ 11036 transformMatrix: null, 11037 11038 /** 11039 * Minimum allowed scale value of an object 11040 * @type Number 11041 * @default 11042 */ 11043 minScaleLimit: 0.01, 11044 11045 /** 11046 * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). 11047 * But events still fire on it. 11048 * @type Boolean 11049 * @default 11050 */ 11051 selectable: true, 11052 11053 /** 11054 * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 11055 * @type Boolean 11056 * @default 11057 */ 11058 evented: true, 11059 11060 /** 11061 * When set to `false`, an object is not rendered on canvas 11062 * @type Boolean 11063 * @default 11064 */ 11065 visible: true, 11066 11067 /** 11068 * When set to `false`, object's controls are not displayed and can not be used to manipulate object 11069 * @type Boolean 11070 * @default 11071 */ 11072 hasControls: true, 11073 11074 /** 11075 * When set to `false`, object's controlling borders are not rendered 11076 * @type Boolean 11077 * @default 11078 */ 11079 hasBorders: true, 11080 11081 /** 11082 * When set to `false`, object's controlling rotating point will not be visible or selectable 11083 * @type Boolean 11084 * @default 11085 */ 11086 hasRotatingPoint: true, 11087 11088 /** 11089 * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`) 11090 * @type Number 11091 * @default 11092 */ 11093 rotatingPointOffset: 40, 11094 11095 /** 11096 * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box 11097 * @type Boolean 11098 * @default 11099 */ 11100 perPixelTargetFind: false, 11101 11102 /** 11103 * When `false`, default object's values are not included in its serialization 11104 * @type Boolean 11105 * @default 11106 */ 11107 includeDefaultValues: true, 11108 11109 /** 11110 * Function that determines clipping of an object (context is passed as a first argument) 11111 * Note that context origin is at the object's center point (not left/top corner) 11112 * @type Function 11113 */ 11114 clipTo: null, 11115 11116 /** 11117 * When `true`, object horizontal movement is locked 11118 * @type Boolean 11119 * @default 11120 */ 11121 lockMovementX: false, 11122 11123 /** 11124 * When `true`, object vertical movement is locked 11125 * @type Boolean 11126 * @default 11127 */ 11128 lockMovementY: false, 11129 11130 /** 11131 * When `true`, object rotation is locked 11132 * @type Boolean 11133 * @default 11134 */ 11135 lockRotation: false, 11136 11137 /** 11138 * When `true`, object horizontal scaling is locked 11139 * @type Boolean 11140 * @default 11141 */ 11142 lockScalingX: false, 11143 11144 /** 11145 * When `true`, object vertical scaling is locked 11146 * @type Boolean 11147 * @default 11148 */ 11149 lockScalingY: false, 11150 11151 /** 11152 * When `true`, object non-uniform scaling is locked 11153 * @type Boolean 11154 * @default 11155 */ 11156 lockUniScaling: false, 11157 11158 /** 11159 * When `true`, object cannot be flipped by scaling into negative values 11160 * @type Boolean 11161 * @default 11162 */ 11163 11164 lockScalingFlip: false, 11165 /** 11166 * List of properties to consider when checking if state 11167 * of an object is changed (fabric.Object#hasStateChanged) 11168 * as well as for history (undo/redo) purposes 11169 * @type Array 11170 */ 11171 stateProperties: ( 11172 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + 11173 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' + 11174 'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor' 11175 ).split(' '), 11176 11177 /** 11178 * Constructor 11179 * @param {Object} [options] Options object 11180 */ 11181 initialize: function(options) { 11182 if (options) { 11183 this.setOptions(options); 11184 } 11185 }, 11186 11187 /** 11188 * @private 11189 * @param {Object} [options] Options object 11190 */ 11191 _initGradient: function(options) { 11192 if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { 11193 this.set('fill', new fabric.Gradient(options.fill)); 11194 } 11195 }, 11196 11197 /** 11198 * @private 11199 * @param {Object} [options] Options object 11200 */ 11201 _initPattern: function(options) { 11202 if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) { 11203 this.set('fill', new fabric.Pattern(options.fill)); 11204 } 11205 if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) { 11206 this.set('stroke', new fabric.Pattern(options.stroke)); 11207 } 11208 }, 11209 11210 /** 11211 * @private 11212 * @param {Object} [options] Options object 11213 */ 11214 _initClipping: function(options) { 11215 if (!options.clipTo || typeof options.clipTo !== 'string') { 11216 return; 11217 } 11218 11219 var functionBody = fabric.util.getFunctionBody(options.clipTo); 11220 if (typeof functionBody !== 'undefined') { 11221 this.clipTo = new Function('ctx', functionBody); 11222 } 11223 }, 11224 11225 /** 11226 * Sets object's properties from options 11227 * @param {Object} [options] Options object 11228 */ 11229 setOptions: function(options) { 11230 for (var prop in options) { 11231 this.set(prop, options[prop]); 11232 } 11233 this._initGradient(options); 11234 this._initPattern(options); 11235 this._initClipping(options); 11236 }, 11237 11238 /** 11239 * Transforms context when rendering an object 11240 * @param {CanvasRenderingContext2D} ctx Context 11241 * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node 11242 */ 11243 transform: function(ctx, fromLeft) { 11244 var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); 11245 ctx.translate(center.x, center.y); 11246 ctx.rotate(degreesToRadians(this.angle)); 11247 ctx.scale( 11248 this.scaleX * (this.flipX ? -1 : 1), 11249 this.scaleY * (this.flipY ? -1 : 1) 11250 ); 11251 }, 11252 11253 /** 11254 * Returns an object representation of an instance 11255 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 11256 * @return {Object} Object representation of an instance 11257 */ 11258 toObject: function(propertiesToInclude) { 11259 var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, 11260 11261 object = { 11262 type: this.type, 11263 originX: this.originX, 11264 originY: this.originY, 11265 left: toFixed(this.left, NUM_FRACTION_DIGITS), 11266 top: toFixed(this.top, NUM_FRACTION_DIGITS), 11267 width: toFixed(this.width, NUM_FRACTION_DIGITS), 11268 height: toFixed(this.height, NUM_FRACTION_DIGITS), 11269 fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, 11270 stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, 11271 strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), 11272 strokeDashArray: this.strokeDashArray, 11273 strokeLineCap: this.strokeLineCap, 11274 strokeLineJoin: this.strokeLineJoin, 11275 strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), 11276 scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), 11277 scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), 11278 angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), 11279 flipX: this.flipX, 11280 flipY: this.flipY, 11281 opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), 11282 shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, 11283 visible: this.visible, 11284 clipTo: this.clipTo && String(this.clipTo), 11285 backgroundColor: this.backgroundColor, 11286 fillRule: this.fillRule, 11287 globalCompositeOperation: this.globalCompositeOperation 11288 }; 11289 11290 if (!this.includeDefaultValues) { 11291 object = this._removeDefaultValues(object); 11292 } 11293 11294 fabric.util.populateWithProperties(this, object, propertiesToInclude); 11295 11296 return object; 11297 }, 11298 11299 /** 11300 * Returns (dataless) object representation of an instance 11301 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 11302 * @return {Object} Object representation of an instance 11303 */ 11304 toDatalessObject: function(propertiesToInclude) { 11305 // will be overwritten by subclasses 11306 return this.toObject(propertiesToInclude); 11307 }, 11308 11309 /** 11310 * @private 11311 * @param {Object} object 11312 */ 11313 _removeDefaultValues: function(object) { 11314 var prototype = fabric.util.getKlass(object.type).prototype, 11315 stateProperties = prototype.stateProperties; 11316 11317 stateProperties.forEach(function(prop) { 11318 if (object[prop] === prototype[prop]) { 11319 delete object[prop]; 11320 } 11321 }); 11322 11323 return object; 11324 }, 11325 11326 /** 11327 * Returns a string representation of an instance 11328 * @return {String} 11329 */ 11330 toString: function() { 11331 return '#<fabric.' + capitalize(this.type) + '>'; 11332 }, 11333 11334 /** 11335 * Basic getter 11336 * @param {String} property Property name 11337 * @return {Any} value of a property 11338 */ 11339 get: function(property) { 11340 return this[property]; 11341 }, 11342 11343 /** 11344 * @private 11345 */ 11346 _setObject: function(obj) { 11347 for (var prop in obj) { 11348 this._set(prop, obj[prop]); 11349 } 11350 }, 11351 11352 /** 11353 * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. 11354 * @param {String|Object} key Property name or object (if object, iterate over the object properties) 11355 * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) 11356 * @return {fabric.Object} thisArg 11357 * @chainable 11358 */ 11359 set: function(key, value) { 11360 if (typeof key === 'object') { 11361 this._setObject(key); 11362 } 11363 else { 11364 if (typeof value === 'function' && key !== 'clipTo') { 11365 this._set(key, value(this.get(key))); 11366 } 11367 else { 11368 this._set(key, value); 11369 } 11370 } 11371 return this; 11372 }, 11373 11374 /** 11375 * @private 11376 * @param {String} key 11377 * @param {Any} value 11378 * @return {fabric.Object} thisArg 11379 */ 11380 _set: function(key, value) { 11381 var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'); 11382 11383 if (shouldConstrainValue) { 11384 value = this._constrainScale(value); 11385 } 11386 if (key === 'scaleX' && value < 0) { 11387 this.flipX = !this.flipX; 11388 value *= -1; 11389 } 11390 else if (key === 'scaleY' && value < 0) { 11391 this.flipY = !this.flipY; 11392 value *= -1; 11393 } 11394 else if (key === 'width' || key === 'height') { 11395 this.minScaleLimit = toFixed(Math.min(0.1, 1/Math.max(this.width, this.height)), 2); 11396 } 11397 else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { 11398 value = new fabric.Shadow(value); 11399 } 11400 11401 this[key] = value; 11402 11403 return this; 11404 }, 11405 11406 /** 11407 * Toggles specified property from `true` to `false` or from `false` to `true` 11408 * @param {String} property Property to toggle 11409 * @return {fabric.Object} thisArg 11410 * @chainable 11411 */ 11412 toggle: function(property) { 11413 var value = this.get(property); 11414 if (typeof value === 'boolean') { 11415 this.set(property, !value); 11416 } 11417 return this; 11418 }, 11419 11420 /** 11421 * Sets sourcePath of an object 11422 * @param {String} value Value to set sourcePath to 11423 * @return {fabric.Object} thisArg 11424 * @chainable 11425 */ 11426 setSourcePath: function(value) { 11427 this.sourcePath = value; 11428 return this; 11429 }, 11430 11431 /** 11432 * Retrieves viewportTransform from Object's canvas if possible 11433 * @method getViewportTransform 11434 * @memberOf fabric.Object.prototype 11435 * @return {Boolean} flipY value // TODO 11436 */ 11437 getViewportTransform: function() { 11438 if (this.canvas && this.canvas.viewportTransform) { 11439 return this.canvas.viewportTransform; 11440 } 11441 return [1, 0, 0, 1, 0, 0]; 11442 }, 11443 11444 /** 11445 * Renders an object on a specified context 11446 * @param {CanvasRenderingContext2D} ctx Context to render on 11447 * @param {Boolean} [noTransform] When true, context is not transformed 11448 */ 11449 render: function(ctx, noTransform) { 11450 // do not render if width/height are zeros or object is not visible 11451 if ((this.width === 0 && this.height === 0) || !this.visible) { 11452 return; 11453 } 11454 11455 ctx.save(); 11456 11457 //setup fill rule for current object 11458 this._setupCompositeOperation(ctx); 11459 if (!noTransform) { 11460 this.transform(ctx); 11461 } 11462 this._setStrokeStyles(ctx); 11463 this._setFillStyles(ctx); 11464 if (this.transformMatrix) { 11465 ctx.transform.apply(ctx, this.transformMatrix); 11466 } 11467 this._setOpacity(ctx); 11468 this._setShadow(ctx); 11469 this.clipTo && fabric.util.clipContext(this, ctx); 11470 this._render(ctx, noTransform); 11471 this.clipTo && ctx.restore(); 11472 this._removeShadow(ctx); 11473 this._restoreCompositeOperation(ctx); 11474 11475 ctx.restore(); 11476 }, 11477 11478 /* @private 11479 * @param {CanvasRenderingContext2D} ctx Context to render on 11480 */ 11481 _setOpacity: function(ctx) { 11482 if (this.group) { 11483 this.group._setOpacity(ctx); 11484 } 11485 ctx.globalAlpha *= this.opacity; 11486 }, 11487 11488 _setStrokeStyles: function(ctx) { 11489 if (this.stroke) { 11490 ctx.lineWidth = this.strokeWidth; 11491 ctx.lineCap = this.strokeLineCap; 11492 ctx.lineJoin = this.strokeLineJoin; 11493 ctx.miterLimit = this.strokeMiterLimit; 11494 ctx.strokeStyle = this.stroke.toLive 11495 ? this.stroke.toLive(ctx, this) 11496 : this.stroke; 11497 } 11498 }, 11499 11500 _setFillStyles: function(ctx) { 11501 if (this.fill) { 11502 ctx.fillStyle = this.fill.toLive 11503 ? this.fill.toLive(ctx, this) 11504 : this.fill; 11505 } 11506 }, 11507 11508 /** 11509 * Renders controls and borders for the object 11510 * @param {CanvasRenderingContext2D} ctx Context to render on 11511 * @param {Boolean} [noTransform] When true, context is not transformed 11512 */ 11513 _renderControls: function(ctx, noTransform) { 11514 if (!this.active || noTransform) { 11515 return; 11516 } 11517 var vpt = this.getViewportTransform(); 11518 ctx.save(); 11519 var center; 11520 if (this.group) { 11521 center = fabric.util.transformPoint(this.group.getCenterPoint(), vpt); 11522 ctx.translate(center.x, center.y); 11523 ctx.rotate(degreesToRadians(this.group.angle)); 11524 } 11525 center = fabric.util.transformPoint(this.getCenterPoint(), vpt, null != this.group); 11526 if (this.group) { 11527 center.x *= this.group.scaleX; 11528 center.y *= this.group.scaleY; 11529 } 11530 ctx.translate(center.x, center.y); 11531 ctx.rotate(degreesToRadians(this.angle)); 11532 this.drawBorders(ctx); 11533 this.drawControls(ctx); 11534 ctx.restore(); 11535 }, 11536 11537 /** 11538 * @private 11539 * @param {CanvasRenderingContext2D} ctx Context to render on 11540 */ 11541 _setShadow: function(ctx) { 11542 if (!this.shadow) { 11543 return; 11544 } 11545 11546 var multX = (this.canvas && this.canvas.viewportTransform[0]) || 1, 11547 multY = (this.canvas && this.canvas.viewportTransform[3]) || 1; 11548 11549 ctx.shadowColor = this.shadow.color; 11550 ctx.shadowBlur = this.shadow.blur * (multX + multY) * (this.scaleX + this.scaleY) / 4; 11551 ctx.shadowOffsetX = this.shadow.offsetX * multX * this.scaleX; 11552 ctx.shadowOffsetY = this.shadow.offsetY * multY * this.scaleY; 11553 }, 11554 11555 /** 11556 * @private 11557 * @param {CanvasRenderingContext2D} ctx Context to render on 11558 */ 11559 _removeShadow: function(ctx) { 11560 if (!this.shadow) { 11561 return; 11562 } 11563 11564 ctx.shadowColor = ''; 11565 ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; 11566 }, 11567 11568 /** 11569 * @private 11570 * @param {CanvasRenderingContext2D} ctx Context to render on 11571 */ 11572 _renderFill: function(ctx) { 11573 if (!this.fill) { 11574 return; 11575 } 11576 11577 ctx.save(); 11578 if (this.fill.gradientTransform) { 11579 var g = this.fill.gradientTransform; 11580 ctx.transform.apply(ctx, g); 11581 } 11582 if (this.fill.toLive) { 11583 ctx.translate( 11584 -this.width / 2 + this.fill.offsetX || 0, 11585 -this.height / 2 + this.fill.offsetY || 0); 11586 } 11587 if (this.fillRule === 'evenodd') { 11588 ctx.fill('evenodd'); 11589 } 11590 else { 11591 ctx.fill(); 11592 } 11593 ctx.restore(); 11594 if (this.shadow && !this.shadow.affectStroke) { 11595 this._removeShadow(ctx); 11596 } 11597 }, 11598 11599 /** 11600 * @private 11601 * @param {CanvasRenderingContext2D} ctx Context to render on 11602 */ 11603 _renderStroke: function(ctx) { 11604 if (!this.stroke || this.strokeWidth === 0) { 11605 return; 11606 } 11607 11608 ctx.save(); 11609 if (this.strokeDashArray) { 11610 // Spec requires the concatenation of two copies the dash list when the number of elements is odd 11611 if (1 & this.strokeDashArray.length) { 11612 this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); 11613 } 11614 if (supportsLineDash) { 11615 ctx.setLineDash(this.strokeDashArray); 11616 this._stroke && this._stroke(ctx); 11617 } 11618 else { 11619 this._renderDashedStroke && this._renderDashedStroke(ctx); 11620 } 11621 ctx.stroke(); 11622 } 11623 else { 11624 if (this.stroke.gradientTransform) { 11625 var g = this.stroke.gradientTransform; 11626 ctx.transform.apply(ctx, g); 11627 } 11628 this._stroke ? this._stroke(ctx) : ctx.stroke(); 11629 } 11630 this._removeShadow(ctx); 11631 ctx.restore(); 11632 }, 11633 11634 /** 11635 * Clones an instance 11636 * @param {Function} callback Callback is invoked with a clone as a first argument 11637 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 11638 * @return {fabric.Object} clone of an instance 11639 */ 11640 clone: function(callback, propertiesToInclude) { 11641 if (this.constructor.fromObject) { 11642 return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); 11643 } 11644 return new fabric.Object(this.toObject(propertiesToInclude)); 11645 }, 11646 11647 /** 11648 * Creates an instance of fabric.Image out of an object 11649 * @param {Function} callback callback, invoked with an instance as a first argument 11650 * @return {fabric.Object} thisArg 11651 */ 11652 cloneAsImage: function(callback) { 11653 var dataUrl = this.toDataURL(); 11654 fabric.util.loadImage(dataUrl, function(img) { 11655 if (callback) { 11656 callback(new fabric.Image(img)); 11657 } 11658 }); 11659 return this; 11660 }, 11661 11662 /** 11663 * Converts an object into a data-url-like string 11664 * @param {Object} options Options object 11665 * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" 11666 * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. 11667 * @param {Number} [options.multiplier=1] Multiplier to scale by 11668 * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 11669 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 11670 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 11671 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 11672 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format 11673 */ 11674 toDataURL: function(options) { 11675 options || (options = { }); 11676 11677 var el = fabric.util.createCanvasElement(), 11678 boundingRect = this.getBoundingRect(); 11679 11680 el.width = boundingRect.width; 11681 el.height = boundingRect.height; 11682 11683 fabric.util.wrapElement(el, 'div'); 11684 var canvas = new fabric.StaticCanvas(el); 11685 11686 // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 11687 if (options.format === 'jpg') { 11688 options.format = 'jpeg'; 11689 } 11690 11691 if (options.format === 'jpeg') { 11692 canvas.backgroundColor = '#fff'; 11693 } 11694 11695 var origParams = { 11696 active: this.get('active'), 11697 left: this.getLeft(), 11698 top: this.getTop() 11699 }; 11700 11701 this.set('active', false); 11702 this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), 'center', 'center'); 11703 11704 var originalCanvas = this.canvas; 11705 canvas.add(this); 11706 var data = canvas.toDataURL(options); 11707 11708 this.set(origParams).setCoords(); 11709 this.canvas = originalCanvas; 11710 11711 canvas.dispose(); 11712 canvas = null; 11713 11714 return data; 11715 }, 11716 11717 /** 11718 * Returns true if specified type is identical to the type of an instance 11719 * @param {String} type Type to check against 11720 * @return {Boolean} 11721 */ 11722 isType: function(type) { 11723 return this.type === type; 11724 }, 11725 11726 /** 11727 * Returns complexity of an instance 11728 * @return {Number} complexity of this instance 11729 */ 11730 complexity: function() { 11731 return 0; 11732 }, 11733 11734 /** 11735 * Returns a JSON representation of an instance 11736 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 11737 * @return {Object} JSON 11738 */ 11739 toJSON: function(propertiesToInclude) { 11740 // delegate, not alias 11741 return this.toObject(propertiesToInclude); 11742 }, 11743 11744 /** 11745 * Sets gradient (fill or stroke) of an object 11746 * <b>Backwards incompatibility note:</b> This method was named "setGradientFill" until v1.1.0 11747 * @param {String} property Property name 'stroke' or 'fill' 11748 * @param {Object} [options] Options object 11749 * @param {String} [options.type] Type of gradient 'radial' or 'linear' 11750 * @param {Number} [options.x1=0] x-coordinate of start point 11751 * @param {Number} [options.y1=0] y-coordinate of start point 11752 * @param {Number} [options.x2=0] x-coordinate of end point 11753 * @param {Number} [options.y2=0] y-coordinate of end point 11754 * @param {Number} [options.r1=0] Radius of start point (only for radial gradients) 11755 * @param {Number} [options.r2=0] Radius of end point (only for radial gradients) 11756 * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'} 11757 * @return {fabric.Object} thisArg 11758 * @chainable 11759 * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo} 11760 * @example <caption>Set linear gradient</caption> 11761 * object.setGradient('fill', { 11762 * type: 'linear', 11763 * x1: -object.width / 2, 11764 * y1: 0, 11765 * x2: object.width / 2, 11766 * y2: 0, 11767 * colorStops: { 11768 * 0: 'red', 11769 * 0.5: '#005555', 11770 * 1: 'rgba(0,0,255,0.5)' 11771 * } 11772 * }); 11773 * canvas.renderAll(); 11774 * @example <caption>Set radial gradient</caption> 11775 * object.setGradient('fill', { 11776 * type: 'radial', 11777 * x1: 0, 11778 * y1: 0, 11779 * x2: 0, 11780 * y2: 0, 11781 * r1: object.width / 2, 11782 * r2: 10, 11783 * colorStops: { 11784 * 0: 'red', 11785 * 0.5: '#005555', 11786 * 1: 'rgba(0,0,255,0.5)' 11787 * } 11788 * }); 11789 * canvas.renderAll(); 11790 */ 11791 setGradient: function(property, options) { 11792 options || (options = { }); 11793 11794 var gradient = { colorStops: [] }; 11795 11796 gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); 11797 gradient.coords = { 11798 x1: options.x1, 11799 y1: options.y1, 11800 x2: options.x2, 11801 y2: options.y2 11802 }; 11803 11804 if (options.r1 || options.r2) { 11805 gradient.coords.r1 = options.r1; 11806 gradient.coords.r2 = options.r2; 11807 } 11808 11809 for (var position in options.colorStops) { 11810 var color = new fabric.Color(options.colorStops[position]); 11811 gradient.colorStops.push({ 11812 offset: position, 11813 color: color.toRgb(), 11814 opacity: color.getAlpha() 11815 }); 11816 } 11817 11818 return this.set(property, fabric.Gradient.forObject(this, gradient)); 11819 }, 11820 11821 /** 11822 * Sets pattern fill of an object 11823 * @param {Object} options Options object 11824 * @param {(String|HTMLImageElement)} options.source Pattern source 11825 * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) 11826 * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner 11827 * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner 11828 * @return {fabric.Object} thisArg 11829 * @chainable 11830 * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo} 11831 * @example <caption>Set pattern</caption> 11832 * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) { 11833 * object.setPatternFill({ 11834 * source: img, 11835 * repeat: 'repeat' 11836 * }); 11837 * canvas.renderAll(); 11838 * }); 11839 */ 11840 setPatternFill: function(options) { 11841 return this.set('fill', new fabric.Pattern(options)); 11842 }, 11843 11844 /** 11845 * Sets {@link fabric.Object#shadow|shadow} of an object 11846 * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") 11847 * @param {String} [options.color=rgb(0,0,0)] Shadow color 11848 * @param {Number} [options.blur=0] Shadow blur 11849 * @param {Number} [options.offsetX=0] Shadow horizontal offset 11850 * @param {Number} [options.offsetY=0] Shadow vertical offset 11851 * @return {fabric.Object} thisArg 11852 * @chainable 11853 * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo} 11854 * @example <caption>Set shadow with string notation</caption> 11855 * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)'); 11856 * canvas.renderAll(); 11857 * @example <caption>Set shadow with object notation</caption> 11858 * object.setShadow({ 11859 * color: 'red', 11860 * blur: 10, 11861 * offsetX: 20, 11862 * offsetY: 20 11863 * }); 11864 * canvas.renderAll(); 11865 */ 11866 setShadow: function(options) { 11867 return this.set('shadow', options ? new fabric.Shadow(options) : null); 11868 }, 11869 11870 /** 11871 * Sets "color" of an instance (alias of `set('fill', …)`) 11872 * @param {String} color Color value 11873 * @return {fabric.Object} thisArg 11874 * @chainable 11875 */ 11876 setColor: function(color) { 11877 this.set('fill', color); 11878 return this; 11879 }, 11880 11881 /** 11882 * Sets "angle" of an instance 11883 * @param {Number} angle Angle value (in degrees) 11884 * @return {fabric.Object} thisArg 11885 * @chainable 11886 */ 11887 setAngle: function(angle) { 11888 var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; 11889 11890 if (shouldCenterOrigin) { 11891 this._setOriginToCenter(); 11892 } 11893 11894 this.set('angle', angle); 11895 11896 if (shouldCenterOrigin) { 11897 this._resetOrigin(); 11898 } 11899 11900 return this; 11901 }, 11902 11903 /** 11904 * Centers object horizontally on canvas to which it was added last. 11905 * You might need to call `setCoords` on an object after centering, to update controls area. 11906 * @return {fabric.Object} thisArg 11907 * @chainable 11908 */ 11909 centerH: function () { 11910 this.canvas.centerObjectH(this); 11911 return this; 11912 }, 11913 11914 /** 11915 * Centers object vertically on canvas to which it was added last. 11916 * You might need to call `setCoords` on an object after centering, to update controls area. 11917 * @return {fabric.Object} thisArg 11918 * @chainable 11919 */ 11920 centerV: function () { 11921 this.canvas.centerObjectV(this); 11922 return this; 11923 }, 11924 11925 /** 11926 * Centers object vertically and horizontally on canvas to which is was added last 11927 * You might need to call `setCoords` on an object after centering, to update controls area. 11928 * @return {fabric.Object} thisArg 11929 * @chainable 11930 */ 11931 center: function () { 11932 this.canvas.centerObject(this); 11933 return this; 11934 }, 11935 11936 /** 11937 * Removes object from canvas to which it was added last 11938 * @return {fabric.Object} thisArg 11939 * @chainable 11940 */ 11941 remove: function() { 11942 this.canvas.remove(this); 11943 return this; 11944 }, 11945 11946 /** 11947 * Returns coordinates of a pointer relative to an object 11948 * @param {Event} e Event to operate upon 11949 * @param {Object} [pointer] Pointer to operate upon (instead of event) 11950 * @return {Object} Coordinates of a pointer (x, y) 11951 */ 11952 getLocalPointer: function(e, pointer) { 11953 pointer = pointer || this.canvas.getPointer(e); 11954 var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); 11955 return { 11956 x: pointer.x - objectLeftTop.x, 11957 y: pointer.y - objectLeftTop.y 11958 }; 11959 }, 11960 11961 /** 11962 * Sets canvas globalCompositeOperation for specific object 11963 * custom composition operation for the particular object can be specifed using globalCompositeOperation property 11964 * @param {CanvasRenderingContext2D} ctx Rendering canvas context 11965 */ 11966 _setupCompositeOperation: function (ctx) { 11967 if (this.globalCompositeOperation) { 11968 this._prevGlobalCompositeOperation = ctx.globalCompositeOperation; 11969 ctx.globalCompositeOperation = this.globalCompositeOperation; 11970 } 11971 }, 11972 11973 /** 11974 * Restores previously saved canvas globalCompositeOperation after obeject rendering 11975 * @param {CanvasRenderingContext2D} ctx Rendering canvas context 11976 */ 11977 _restoreCompositeOperation: function (ctx) { 11978 if (this.globalCompositeOperation && this._prevGlobalCompositeOperation) { 11979 ctx.globalCompositeOperation = this._prevGlobalCompositeOperation; 11980 } 11981 } 11982 }); 11983 11984 fabric.util.createAccessors(fabric.Object); 11985 11986 /** 11987 * Alias for {@link fabric.Object.prototype.setAngle} 11988 * @alias rotate -> setAngle 11989 * @memberof fabric.Object 11990 */ 11991 fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; 11992 11993 extend(fabric.Object.prototype, fabric.Observable); 11994 11995 /** 11996 * Defines the number of fraction digits to use when serializing object values. 11997 * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. 11998 * @static 11999 * @memberof fabric.Object 12000 * @constant 12001 * @type Number 12002 */ 12003 fabric.Object.NUM_FRACTION_DIGITS = 2; 12004 12005 /** 12006 * Unique id used internally when creating SVG elements 12007 * @static 12008 * @memberof fabric.Object 12009 * @type Number 12010 */ 12011 fabric.Object.__uid = 0; 12012 12013})(typeof exports !== 'undefined' ? exports : this); 12014 12015 12016(function() { 12017 12018 var degreesToRadians = fabric.util.degreesToRadians; 12019 12020 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 12021 12022 /** 12023 * Translates the coordinates from origin to center coordinates (based on the object's dimensions) 12024 * @param {fabric.Point} point The point which corresponds to the originX and originY params 12025 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 12026 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 12027 * @return {fabric.Point} 12028 */ 12029 translateToCenterPoint: function(point, originX, originY) { 12030 var cx = point.x, 12031 cy = point.y, 12032 strokeWidth = this.stroke ? this.strokeWidth : 0; 12033 12034 if (originX === 'left') { 12035 cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; 12036 } 12037 else if (originX === 'right') { 12038 cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; 12039 } 12040 12041 if (originY === 'top') { 12042 cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; 12043 } 12044 else if (originY === 'bottom') { 12045 cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; 12046 } 12047 12048 // Apply the reverse rotation to the point (it's already scaled properly) 12049 return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle)); 12050 }, 12051 12052 /** 12053 * Translates the coordinates from center to origin coordinates (based on the object's dimensions) 12054 * @param {fabric.Point} center The point which corresponds to center of the object 12055 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 12056 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 12057 * @return {fabric.Point} 12058 */ 12059 translateToOriginPoint: function(center, originX, originY) { 12060 var x = center.x, 12061 y = center.y, 12062 strokeWidth = this.stroke ? this.strokeWidth : 0; 12063 12064 // Get the point coordinates 12065 if (originX === 'left') { 12066 x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; 12067 } 12068 else if (originX === 'right') { 12069 x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; 12070 } 12071 if (originY === 'top') { 12072 y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; 12073 } 12074 else if (originY === 'bottom') { 12075 y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; 12076 } 12077 12078 // Apply the rotation to the point (it's already scaled properly) 12079 return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle)); 12080 }, 12081 12082 /** 12083 * Returns the real center coordinates of the object 12084 * @return {fabric.Point} 12085 */ 12086 getCenterPoint: function() { 12087 var leftTop = new fabric.Point(this.left, this.top); 12088 return this.translateToCenterPoint(leftTop, this.originX, this.originY); 12089 }, 12090 12091 /** 12092 * Returns the coordinates of the object based on center coordinates 12093 * @param {fabric.Point} point The point which corresponds to the originX and originY params 12094 * @return {fabric.Point} 12095 */ 12096 // getOriginPoint: function(center) { 12097 // return this.translateToOriginPoint(center, this.originX, this.originY); 12098 // }, 12099 12100 /** 12101 * Returns the coordinates of the object as if it has a different origin 12102 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 12103 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 12104 * @return {fabric.Point} 12105 */ 12106 getPointByOrigin: function(originX, originY) { 12107 var center = this.getCenterPoint(); 12108 return this.translateToOriginPoint(center, originX, originY); 12109 }, 12110 12111 /** 12112 * Returns the point in local coordinates 12113 * @param {fabric.Point} point The point relative to the global coordinate system 12114 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 12115 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 12116 * @return {fabric.Point} 12117 */ 12118 toLocalPoint: function(point, originX, originY) { 12119 var center = this.getCenterPoint(), 12120 strokeWidth = this.stroke ? this.strokeWidth : 0, 12121 x, y; 12122 12123 if (originX && originY) { 12124 if (originX === 'left') { 12125 x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; 12126 } 12127 else if (originX === 'right') { 12128 x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; 12129 } 12130 else { 12131 x = center.x; 12132 } 12133 12134 if (originY === 'top') { 12135 y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; 12136 } 12137 else if (originY === 'bottom') { 12138 y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; 12139 } 12140 else { 12141 y = center.y; 12142 } 12143 } 12144 else { 12145 x = this.left; 12146 y = this.top; 12147 } 12148 12149 return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)) 12150 .subtractEquals(new fabric.Point(x, y)); 12151 }, 12152 12153 /** 12154 * Returns the point in global coordinates 12155 * @param {fabric.Point} The point relative to the local coordinate system 12156 * @return {fabric.Point} 12157 */ 12158 // toGlobalPoint: function(point) { 12159 // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); 12160 // }, 12161 12162 /** 12163 * Sets the position of the object taking into consideration the object's origin 12164 * @param {fabric.Point} pos The new position of the object 12165 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 12166 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 12167 * @return {void} 12168 */ 12169 setPositionByOrigin: function(pos, originX, originY) { 12170 var center = this.translateToCenterPoint(pos, originX, originY), 12171 position = this.translateToOriginPoint(center, this.originX, this.originY); 12172 12173 this.set('left', position.x); 12174 this.set('top', position.y); 12175 }, 12176 12177 /** 12178 * @param {String} to One of 'left', 'center', 'right' 12179 */ 12180 adjustPosition: function(to) { 12181 var angle = degreesToRadians(this.angle), 12182 hypotHalf = this.getWidth() / 2, 12183 xHalf = Math.cos(angle) * hypotHalf, 12184 yHalf = Math.sin(angle) * hypotHalf, 12185 hypotFull = this.getWidth(), 12186 xFull = Math.cos(angle) * hypotFull, 12187 yFull = Math.sin(angle) * hypotFull; 12188 12189 if (this.originX === 'center' && to === 'left' || 12190 this.originX === 'right' && to === 'center') { 12191 // move half left 12192 this.left -= xHalf; 12193 this.top -= yHalf; 12194 } 12195 else if (this.originX === 'left' && to === 'center' || 12196 this.originX === 'center' && to === 'right') { 12197 // move half right 12198 this.left += xHalf; 12199 this.top += yHalf; 12200 } 12201 else if (this.originX === 'left' && to === 'right') { 12202 // move full right 12203 this.left += xFull; 12204 this.top += yFull; 12205 } 12206 else if (this.originX === 'right' && to === 'left') { 12207 // move full left 12208 this.left -= xFull; 12209 this.top -= yFull; 12210 } 12211 12212 this.setCoords(); 12213 this.originX = to; 12214 }, 12215 12216 /** 12217 * Sets the origin/position of the object to it's center point 12218 * @private 12219 * @return {void} 12220 */ 12221 _setOriginToCenter: function() { 12222 this._originalOriginX = this.originX; 12223 this._originalOriginY = this.originY; 12224 12225 var center = this.getCenterPoint(); 12226 12227 this.originX = 'center'; 12228 this.originY = 'center'; 12229 12230 this.left = center.x; 12231 this.top = center.y; 12232 }, 12233 12234 /** 12235 * Resets the origin/position of the object to it's original origin 12236 * @private 12237 * @return {void} 12238 */ 12239 _resetOrigin: function() { 12240 var originPoint = this.translateToOriginPoint( 12241 this.getCenterPoint(), 12242 this._originalOriginX, 12243 this._originalOriginY); 12244 12245 this.originX = this._originalOriginX; 12246 this.originY = this._originalOriginY; 12247 12248 this.left = originPoint.x; 12249 this.top = originPoint.y; 12250 12251 this._originalOriginX = null; 12252 this._originalOriginY = null; 12253 }, 12254 12255 /** 12256 * @private 12257 */ 12258 _getLeftTopCoords: function() { 12259 return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'center'); 12260 } 12261 }); 12262 12263})(); 12264 12265 12266(function() { 12267 12268 var degreesToRadians = fabric.util.degreesToRadians; 12269 12270 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 12271 12272 /** 12273 * Object containing coordinates of object's controls 12274 * @type Object 12275 * @default 12276 */ 12277 oCoords: null, 12278 12279 /** 12280 * Checks if object intersects with an area formed by 2 points 12281 * @param {Object} pointTL top-left point of area 12282 * @param {Object} pointBR bottom-right point of area 12283 * @return {Boolean} true if object intersects with an area formed by 2 points 12284 */ 12285 intersectsWithRect: function(pointTL, pointBR) { 12286 var oCoords = this.oCoords, 12287 tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), 12288 tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), 12289 bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), 12290 br = new fabric.Point(oCoords.br.x, oCoords.br.y), 12291 intersection = fabric.Intersection.intersectPolygonRectangle( 12292 [tl, tr, br, bl], 12293 pointTL, 12294 pointBR 12295 ); 12296 return intersection.status === 'Intersection'; 12297 }, 12298 12299 /** 12300 * Checks if object intersects with another object 12301 * @param {Object} other Object to test 12302 * @return {Boolean} true if object intersects with another object 12303 */ 12304 intersectsWithObject: function(other) { 12305 // extracts coords 12306 function getCoords(oCoords) { 12307 return { 12308 tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y), 12309 tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y), 12310 bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y), 12311 br: new fabric.Point(oCoords.br.x, oCoords.br.y) 12312 }; 12313 } 12314 var thisCoords = getCoords(this.oCoords), 12315 otherCoords = getCoords(other.oCoords), 12316 intersection = fabric.Intersection.intersectPolygonPolygon( 12317 [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], 12318 [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] 12319 ); 12320 12321 return intersection.status === 'Intersection'; 12322 }, 12323 12324 /** 12325 * Checks if object is fully contained within area of another object 12326 * @param {Object} other Object to test 12327 * @return {Boolean} true if object is fully contained within area of another object 12328 */ 12329 isContainedWithinObject: function(other) { 12330 var boundingRect = other.getBoundingRect(), 12331 point1 = new fabric.Point(boundingRect.left, boundingRect.top), 12332 point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); 12333 12334 return this.isContainedWithinRect(point1, point2); 12335 }, 12336 12337 /** 12338 * Checks if object is fully contained within area formed by 2 points 12339 * @param {Object} pointTL top-left point of area 12340 * @param {Object} pointBR bottom-right point of area 12341 * @return {Boolean} true if object is fully contained within area formed by 2 points 12342 */ 12343 isContainedWithinRect: function(pointTL, pointBR) { 12344 var boundingRect = this.getBoundingRect(); 12345 12346 return ( 12347 boundingRect.left >= pointTL.x && 12348 boundingRect.left + boundingRect.width <= pointBR.x && 12349 boundingRect.top >= pointTL.y && 12350 boundingRect.top + boundingRect.height <= pointBR.y 12351 ); 12352 }, 12353 12354 /** 12355 * Checks if point is inside the object 12356 * @param {fabric.Point} point Point to check against 12357 * @return {Boolean} true if point is inside the object 12358 */ 12359 containsPoint: function(point) { 12360 var lines = this._getImageLines(this.oCoords), 12361 xPoints = this._findCrossPoints(point, lines); 12362 12363 // if xPoints is odd then point is inside the object 12364 return (xPoints !== 0 && xPoints % 2 === 1); 12365 }, 12366 12367 /** 12368 * Method that returns an object with the object edges in it, given the coordinates of the corners 12369 * @private 12370 * @param {Object} oCoords Coordinates of the object corners 12371 */ 12372 _getImageLines: function(oCoords) { 12373 return { 12374 topline: { 12375 o: oCoords.tl, 12376 d: oCoords.tr 12377 }, 12378 rightline: { 12379 o: oCoords.tr, 12380 d: oCoords.br 12381 }, 12382 bottomline: { 12383 o: oCoords.br, 12384 d: oCoords.bl 12385 }, 12386 leftline: { 12387 o: oCoords.bl, 12388 d: oCoords.tl 12389 } 12390 }; 12391 }, 12392 12393 /** 12394 * Helper method to determine how many cross points are between the 4 object edges 12395 * and the horizontal line determined by a point on canvas 12396 * @private 12397 * @param {fabric.Point} point Point to check 12398 * @param {Object} oCoords Coordinates of the object being evaluated 12399 */ 12400 _findCrossPoints: function(point, oCoords) { 12401 var b1, b2, a1, a2, xi, yi, 12402 xcount = 0, 12403 iLine; 12404 12405 for (var lineKey in oCoords) { 12406 iLine = oCoords[lineKey]; 12407 // optimisation 1: line below point. no cross 12408 if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { 12409 continue; 12410 } 12411 // optimisation 2: line above point. no cross 12412 if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { 12413 continue; 12414 } 12415 // optimisation 3: vertical line case 12416 if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { 12417 xi = iLine.o.x; 12418 yi = point.y; 12419 } 12420 // calculate the intersection point 12421 else { 12422 b1 = 0; 12423 b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); 12424 a1 = point.y - b1 * point.x; 12425 a2 = iLine.o.y - b2 * iLine.o.x; 12426 12427 xi = - (a1 - a2) / (b1 - b2); 12428 yi = a1 + b1 * xi; 12429 } 12430 // dont count xi < point.x cases 12431 if (xi >= point.x) { 12432 xcount += 1; 12433 } 12434 // optimisation 4: specific for square images 12435 if (xcount === 2) { 12436 break; 12437 } 12438 } 12439 return xcount; 12440 }, 12441 12442 /** 12443 * Returns width of an object's bounding rectangle 12444 * @deprecated since 1.0.4 12445 * @return {Number} width value 12446 */ 12447 getBoundingRectWidth: function() { 12448 return this.getBoundingRect().width; 12449 }, 12450 12451 /** 12452 * Returns height of an object's bounding rectangle 12453 * @deprecated since 1.0.4 12454 * @return {Number} height value 12455 */ 12456 getBoundingRectHeight: function() { 12457 return this.getBoundingRect().height; 12458 }, 12459 12460 /** 12461 * Returns coordinates of object's bounding rectangle (left, top, width, height) 12462 * @return {Object} Object with left, top, width, height properties 12463 */ 12464 getBoundingRect: function() { 12465 this.oCoords || this.setCoords(); 12466 12467 var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], 12468 minX = fabric.util.array.min(xCoords), 12469 maxX = fabric.util.array.max(xCoords), 12470 width = Math.abs(minX - maxX), 12471 12472 yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], 12473 minY = fabric.util.array.min(yCoords), 12474 maxY = fabric.util.array.max(yCoords), 12475 height = Math.abs(minY - maxY); 12476 12477 return { 12478 left: minX, 12479 top: minY, 12480 width: width, 12481 height: height 12482 }; 12483 }, 12484 12485 /** 12486 * Returns width of an object 12487 * @return {Number} width value 12488 */ 12489 getWidth: function() { 12490 return this.width * this.scaleX; 12491 }, 12492 12493 /** 12494 * Returns height of an object 12495 * @return {Number} height value 12496 */ 12497 getHeight: function() { 12498 return this.height * this.scaleY; 12499 }, 12500 12501 /** 12502 * Makes sure the scale is valid and modifies it if necessary 12503 * @private 12504 * @param {Number} value 12505 * @return {Number} 12506 */ 12507 _constrainScale: function(value) { 12508 if (Math.abs(value) < this.minScaleLimit) { 12509 if (value < 0) { 12510 return -this.minScaleLimit; 12511 } 12512 else { 12513 return this.minScaleLimit; 12514 } 12515 } 12516 return value; 12517 }, 12518 12519 /** 12520 * Scales an object (equally by x and y) 12521 * @param {Number} value Scale factor 12522 * @return {fabric.Object} thisArg 12523 * @chainable 12524 */ 12525 scale: function(value) { 12526 value = this._constrainScale(value); 12527 12528 if (value < 0) { 12529 this.flipX = !this.flipX; 12530 this.flipY = !this.flipY; 12531 value *= -1; 12532 } 12533 12534 this.scaleX = value; 12535 this.scaleY = value; 12536 this.setCoords(); 12537 return this; 12538 }, 12539 12540 /** 12541 * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) 12542 * @param {Number} value New width value 12543 * @return {fabric.Object} thisArg 12544 * @chainable 12545 */ 12546 scaleToWidth: function(value) { 12547 // adjust to bounding rect factor so that rotated shapes would fit as well 12548 var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth(); 12549 return this.scale(value / this.width / boundingRectFactor); 12550 }, 12551 12552 /** 12553 * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) 12554 * @param {Number} value New height value 12555 * @return {fabric.Object} thisArg 12556 * @chainable 12557 */ 12558 scaleToHeight: function(value) { 12559 // adjust to bounding rect factor so that rotated shapes would fit as well 12560 var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight(); 12561 return this.scale(value / this.height / boundingRectFactor); 12562 }, 12563 12564 /** 12565 * Sets corner position coordinates based on current angle, width and height 12566 * See https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords 12567 * @return {fabric.Object} thisArg 12568 * @chainable 12569 */ 12570 setCoords: function() { 12571 var theta = degreesToRadians(this.angle), 12572 vpt = this.getViewportTransform(), 12573 f = function (p) { 12574 return fabric.util.transformPoint(p, vpt); 12575 }, 12576 p = this._calculateCurrentDimensions(false), 12577 currentWidth = p.x, currentHeight = p.y; 12578 12579 // If width is negative, make postive. Fixes path selection issue 12580 if (currentWidth < 0) { 12581 currentWidth = Math.abs(currentWidth); 12582 } 12583 12584 var _hypotenuse = Math.sqrt( 12585 Math.pow(currentWidth / 2, 2) + 12586 Math.pow(currentHeight / 2, 2)), 12587 12588 _angle = Math.atan( 12589 isFinite(currentHeight / currentWidth) 12590 ? currentHeight / currentWidth 12591 : 0), 12592 12593 // offset added for rotate and scale actions 12594 offsetX = Math.cos(_angle + theta) * _hypotenuse, 12595 offsetY = Math.sin(_angle + theta) * _hypotenuse, 12596 sinTh = Math.sin(theta), 12597 cosTh = Math.cos(theta), 12598 coords = this.getCenterPoint(), 12599 wh = new fabric.Point(currentWidth, currentHeight), 12600 _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), 12601 _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), 12602 bl = f(new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh))), 12603 br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), 12604 tl = f(_tl), 12605 tr = f(_tr), 12606 ml = new fabric.Point((tl.x + bl.x)/2, (tl.y + bl.y)/2), 12607 mt = new fabric.Point((tr.x + tl.x)/2, (tr.y + tl.y)/2), 12608 mr = new fabric.Point((br.x + tr.x)/2, (br.y + tr.y)/2), 12609 mb = new fabric.Point((br.x + bl.x)/2, (br.y + bl.y)/2), 12610 mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset); 12611 // debugging 12612 12613 /* setTimeout(function() { 12614 canvas.contextTop.fillStyle = 'green'; 12615 canvas.contextTop.fillRect(mb.x, mb.y, 3, 3); 12616 canvas.contextTop.fillRect(bl.x, bl.y, 3, 3); 12617 canvas.contextTop.fillRect(br.x, br.y, 3, 3); 12618 canvas.contextTop.fillRect(tl.x, tl.y, 3, 3); 12619 canvas.contextTop.fillRect(tr.x, tr.y, 3, 3); 12620 canvas.contextTop.fillRect(ml.x, ml.y, 3, 3); 12621 canvas.contextTop.fillRect(mr.x, mr.y, 3, 3); 12622 canvas.contextTop.fillRect(mt.x, mt.y, 3, 3); 12623 canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3); 12624 }, 50); */ 12625 12626 this.oCoords = { 12627 // corners 12628 tl: tl, tr: tr, br: br, bl: bl, 12629 // middle 12630 ml: ml, mt: mt, mr: mr, mb: mb, 12631 // rotating point 12632 mtr: mtr 12633 }; 12634 12635 // set coordinates of the draggable boxes in the corners used to scale/rotate the image 12636 this._setCornerCoords && this._setCornerCoords(); 12637 12638 return this; 12639 } 12640 }); 12641})(); 12642 12643 12644fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 12645 12646 /** 12647 * Moves an object to the bottom of the stack of drawn objects 12648 * @return {fabric.Object} thisArg 12649 * @chainable 12650 */ 12651 sendToBack: function() { 12652 if (this.group) { 12653 fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); 12654 } 12655 else { 12656 this.canvas.sendToBack(this); 12657 } 12658 return this; 12659 }, 12660 12661 /** 12662 * Moves an object to the top of the stack of drawn objects 12663 * @return {fabric.Object} thisArg 12664 * @chainable 12665 */ 12666 bringToFront: function() { 12667 if (this.group) { 12668 fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); 12669 } 12670 else { 12671 this.canvas.bringToFront(this); 12672 } 12673 return this; 12674 }, 12675 12676 /** 12677 * Moves an object down in stack of drawn objects 12678 * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object 12679 * @return {fabric.Object} thisArg 12680 * @chainable 12681 */ 12682 sendBackwards: function(intersecting) { 12683 if (this.group) { 12684 fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); 12685 } 12686 else { 12687 this.canvas.sendBackwards(this, intersecting); 12688 } 12689 return this; 12690 }, 12691 12692 /** 12693 * Moves an object up in stack of drawn objects 12694 * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object 12695 * @return {fabric.Object} thisArg 12696 * @chainable 12697 */ 12698 bringForward: function(intersecting) { 12699 if (this.group) { 12700 fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); 12701 } 12702 else { 12703 this.canvas.bringForward(this, intersecting); 12704 } 12705 return this; 12706 }, 12707 12708 /** 12709 * Moves an object to specified level in stack of drawn objects 12710 * @param {Number} index New position of object 12711 * @return {fabric.Object} thisArg 12712 * @chainable 12713 */ 12714 moveTo: function(index) { 12715 if (this.group) { 12716 fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); 12717 } 12718 else { 12719 this.canvas.moveTo(this, index); 12720 } 12721 return this; 12722 } 12723}); 12724 12725 12726/* _TO_SVG_START_ */ 12727fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 12728 12729 /** 12730 * Returns styles-string for svg-export 12731 * @return {String} 12732 */ 12733 getSvgStyles: function() { 12734 12735 var fill = this.fill 12736 ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) 12737 : 'none', 12738 fillRule = this.fillRule, 12739 stroke = this.stroke 12740 ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) 12741 : 'none', 12742 12743 strokeWidth = this.strokeWidth ? this.strokeWidth : '0', 12744 strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '', 12745 strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', 12746 strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', 12747 strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', 12748 opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', 12749 12750 visibility = this.visible ? '' : ' visibility: hidden;', 12751 filter = this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; 12752 12753 return [ 12754 'stroke: ', stroke, '; ', 12755 'stroke-width: ', strokeWidth, '; ', 12756 'stroke-dasharray: ', strokeDashArray, '; ', 12757 'stroke-linecap: ', strokeLineCap, '; ', 12758 'stroke-linejoin: ', strokeLineJoin, '; ', 12759 'stroke-miterlimit: ', strokeMiterLimit, '; ', 12760 'fill: ', fill, '; ', 12761 'fill-rule: ', fillRule, '; ', 12762 'opacity: ', opacity, ';', 12763 filter, 12764 visibility 12765 ].join(''); 12766 }, 12767 12768 /** 12769 * Returns transform-string for svg-export 12770 * @return {String} 12771 */ 12772 getSvgTransform: function() { 12773 if (this.group && this.group.type === 'path-group') { 12774 return ''; 12775 } 12776 var toFixed = fabric.util.toFixed, 12777 angle = this.getAngle(), 12778 vpt = !this.canvas || this.canvas.svgViewportTransformation ? this.getViewportTransform() : [1, 0, 0, 1, 0, 0], 12779 center = fabric.util.transformPoint(this.getCenterPoint(), vpt), 12780 12781 NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, 12782 12783 translatePart = this.type === 'path-group' ? '' : 'translate(' + 12784 toFixed(center.x, NUM_FRACTION_DIGITS) + 12785 ' ' + 12786 toFixed(center.y, NUM_FRACTION_DIGITS) + 12787 ')', 12788 12789 anglePart = angle !== 0 12790 ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') 12791 : '', 12792 12793 scalePart = (this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1) 12794 ? '' : 12795 (' scale(' + 12796 toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) + 12797 ' ' + 12798 toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) + 12799 ')'), 12800 12801 addTranslateX = this.type === 'path-group' ? this.width * vpt[0] : 0, 12802 12803 flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '', 12804 12805 addTranslateY = this.type === 'path-group' ? this.height * vpt[3] : 0, 12806 12807 flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : ''; 12808 12809 return [ 12810 translatePart, anglePart, scalePart, flipXPart, flipYPart 12811 ].join(''); 12812 }, 12813 12814 /** 12815 * Returns transform-string for svg-export from the transform matrix of single elements 12816 * @return {String} 12817 */ 12818 getSvgTransformMatrix: function() { 12819 return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : ''; 12820 }, 12821 12822 /** 12823 * @private 12824 */ 12825 _createBaseSVGMarkup: function() { 12826 var markup = [ ]; 12827 12828 if (this.fill && this.fill.toLive) { 12829 markup.push(this.fill.toSVG(this, false)); 12830 } 12831 if (this.stroke && this.stroke.toLive) { 12832 markup.push(this.stroke.toSVG(this, false)); 12833 } 12834 if (this.shadow) { 12835 markup.push(this.shadow.toSVG(this)); 12836 } 12837 return markup; 12838 } 12839}); 12840/* _TO_SVG_END_ */ 12841 12842 12843/* 12844 Depends on `stateProperties` 12845*/ 12846fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 12847 12848 /** 12849 * Returns true if object state (one of its state properties) was changed 12850 * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called 12851 */ 12852 hasStateChanged: function() { 12853 return this.stateProperties.some(function(prop) { 12854 return this.get(prop) !== this.originalState[prop]; 12855 }, this); 12856 }, 12857 12858 /** 12859 * Saves state of an object 12860 * @param {Object} [options] Object with additional `stateProperties` array to include when saving state 12861 * @return {fabric.Object} thisArg 12862 */ 12863 saveState: function(options) { 12864 this.stateProperties.forEach(function(prop) { 12865 this.originalState[prop] = this.get(prop); 12866 }, this); 12867 12868 if (options && options.stateProperties) { 12869 options.stateProperties.forEach(function(prop) { 12870 this.originalState[prop] = this.get(prop); 12871 }, this); 12872 } 12873 12874 return this; 12875 }, 12876 12877 /** 12878 * Setups state of an object 12879 * @return {fabric.Object} thisArg 12880 */ 12881 setupState: function() { 12882 this.originalState = { }; 12883 this.saveState(); 12884 12885 return this; 12886 } 12887}); 12888 12889 12890(function() { 12891 12892 var degreesToRadians = fabric.util.degreesToRadians, 12893 //jscs:disable requireCamelCaseOrUpperCaseIdentifiers 12894 isVML = function() { return typeof G_vmlCanvasManager !== 'undefined'; }; 12895 //jscs:enable requireCamelCaseOrUpperCaseIdentifiers 12896 12897 fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 12898 12899 /** 12900 * The object interactivity controls. 12901 * @private 12902 */ 12903 _controlsVisibility: null, 12904 12905 /** 12906 * Determines which corner has been clicked 12907 * @private 12908 * @param {Object} pointer The pointer indicating the mouse position 12909 * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found 12910 */ 12911 _findTargetCorner: function(pointer) { 12912 if (!this.hasControls || !this.active) { 12913 return false; 12914 } 12915 12916 var ex = pointer.x, 12917 ey = pointer.y, 12918 xPoints, 12919 lines; 12920 12921 for (var i in this.oCoords) { 12922 12923 if (!this.isControlVisible(i)) { 12924 continue; 12925 } 12926 12927 if (i === 'mtr' && !this.hasRotatingPoint) { 12928 continue; 12929 } 12930 12931 if (this.get('lockUniScaling') && 12932 (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { 12933 continue; 12934 } 12935 12936 lines = this._getImageLines(this.oCoords[i].corner); 12937 12938 // debugging 12939 12940 // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); 12941 // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); 12942 12943 // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); 12944 // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); 12945 12946 // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); 12947 // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); 12948 12949 // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); 12950 // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); 12951 12952 xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); 12953 if (xPoints !== 0 && xPoints % 2 === 1) { 12954 this.__corner = i; 12955 return i; 12956 } 12957 } 12958 return false; 12959 }, 12960 12961 /** 12962 * Sets the coordinates of the draggable boxes in the corners of 12963 * the image used to scale/rotate it. 12964 * @private 12965 */ 12966 _setCornerCoords: function() { 12967 var coords = this.oCoords, 12968 newTheta = degreesToRadians(45 - this.angle), 12969 cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, 12970 cosHalfOffset = cornerHypotenuse * Math.cos(newTheta), 12971 sinHalfOffset = cornerHypotenuse * Math.sin(newTheta), 12972 x, y; 12973 12974 for (var point in coords) { 12975 x = coords[point].x; 12976 y = coords[point].y; 12977 coords[point].corner = { 12978 tl: { 12979 x: x - sinHalfOffset, 12980 y: y - cosHalfOffset 12981 }, 12982 tr: { 12983 x: x + cosHalfOffset, 12984 y: y - sinHalfOffset 12985 }, 12986 bl: { 12987 x: x - cosHalfOffset, 12988 y: y + sinHalfOffset 12989 }, 12990 br: { 12991 x: x + sinHalfOffset, 12992 y: y + cosHalfOffset 12993 } 12994 }; 12995 } 12996 }, 12997 12998 _calculateCurrentDimensions: function(shouldTransform) { 12999 var vpt = this.getViewportTransform(), 13000 strokeWidth = this.strokeWidth, 13001 w = this.width, 13002 h = this.height, 13003 capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square', 13004 vLine = this.type === 'line' && this.width === 0, 13005 hLine = this.type === 'line' && this.height === 0, 13006 sLine = vLine || hLine, 13007 strokeW = (capped && hLine) || !sLine, 13008 strokeH = (capped && vLine) || !sLine; 13009 13010 if (vLine) { 13011 w = strokeWidth; 13012 } 13013 else if (hLine) { 13014 h = strokeWidth; 13015 } 13016 if (strokeW) { 13017 w += (w < 0 ? -strokeWidth : strokeWidth); 13018 } 13019 if (strokeH) { 13020 h += (h < 0 ? -strokeWidth : strokeWidth); 13021 } 13022 13023 w = w * this.scaleX + 2 * this.padding; 13024 h = h * this.scaleY + 2 * this.padding; 13025 13026 if (shouldTransform) { 13027 return fabric.util.transformPoint(new fabric.Point(w, h), vpt, true); 13028 } 13029 return { x: w, y: h }; 13030 }, 13031 13032 /** 13033 * Draws borders of an object's bounding box. 13034 * Requires public properties: width, height 13035 * Requires public options: padding, borderColor 13036 * @param {CanvasRenderingContext2D} ctx Context to draw on 13037 * @return {fabric.Object} thisArg 13038 * @chainable 13039 */ 13040 drawBorders: function(ctx) { 13041 if (!this.hasBorders) { 13042 return this; 13043 } 13044 13045 ctx.save(); 13046 13047 ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; 13048 ctx.strokeStyle = this.borderColor; 13049 ctx.lineWidth = 1 / this.borderScaleFactor; 13050 13051 var wh = this._calculateCurrentDimensions(true), 13052 width = wh.x, 13053 height = wh.y; 13054 if (this.group) { 13055 width = width * this.group.scaleX; 13056 height = height * this.group.scaleY; 13057 } 13058 13059 ctx.strokeRect( 13060 ~~(-(width / 2)) - 0.5, // offset needed to make lines look sharper 13061 ~~(-(height / 2)) - 0.5, 13062 ~~(width) + 1, // double offset needed to make lines look sharper 13063 ~~(height) + 1 13064 ); 13065 13066 if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) { 13067 13068 var rotateHeight = -height / 2; 13069 13070 ctx.beginPath(); 13071 ctx.moveTo(0, rotateHeight); 13072 ctx.lineTo(0, rotateHeight - this.rotatingPointOffset); 13073 ctx.closePath(); 13074 ctx.stroke(); 13075 } 13076 13077 ctx.restore(); 13078 return this; 13079 }, 13080 13081 /** 13082 * Draws corners of an object's bounding box. 13083 * Requires public properties: width, height 13084 * Requires public options: cornerSize, padding 13085 * @param {CanvasRenderingContext2D} ctx Context to draw on 13086 * @return {fabric.Object} thisArg 13087 * @chainable 13088 */ 13089 drawControls: function(ctx) { 13090 if (!this.hasControls) { 13091 return this; 13092 } 13093 13094 var wh = this._calculateCurrentDimensions(true), 13095 width = wh.x, 13096 height = wh.y, 13097 left = -(width / 2), 13098 top = -(height / 2), 13099 scaleOffset = this.cornerSize / 2, 13100 methodName = this.transparentCorners ? 'strokeRect' : 'fillRect'; 13101 13102 ctx.save(); 13103 13104 ctx.lineWidth = 1; 13105 13106 ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; 13107 ctx.strokeStyle = ctx.fillStyle = this.cornerColor; 13108 13109 // top-left 13110 this._drawControl('tl', ctx, methodName, 13111 left - scaleOffset, 13112 top - scaleOffset); 13113 13114 // top-right 13115 this._drawControl('tr', ctx, methodName, 13116 left + width - scaleOffset, 13117 top - scaleOffset); 13118 13119 // bottom-left 13120 this._drawControl('bl', ctx, methodName, 13121 left - scaleOffset, 13122 top + height - scaleOffset); 13123 13124 // bottom-right 13125 this._drawControl('br', ctx, methodName, 13126 left + width - scaleOffset, 13127 top + height - scaleOffset); 13128 13129 if (!this.get('lockUniScaling')) { 13130 13131 // middle-top 13132 this._drawControl('mt', ctx, methodName, 13133 left + width/2 - scaleOffset, 13134 top - scaleOffset); 13135 13136 // middle-bottom 13137 this._drawControl('mb', ctx, methodName, 13138 left + width/2 - scaleOffset, 13139 top + height - scaleOffset); 13140 13141 // middle-right 13142 this._drawControl('mr', ctx, methodName, 13143 left + width - scaleOffset, 13144 top + height/2 - scaleOffset); 13145 13146 // middle-left 13147 this._drawControl('ml', ctx, methodName, 13148 left - scaleOffset, 13149 top + height/2 - scaleOffset); 13150 } 13151 13152 // middle-top-rotate 13153 if (this.hasRotatingPoint) { 13154 this._drawControl('mtr', ctx, methodName, 13155 left + width/2 - scaleOffset, 13156 top - this.rotatingPointOffset - scaleOffset); 13157 } 13158 13159 ctx.restore(); 13160 13161 return this; 13162 }, 13163 13164 /** 13165 * @private 13166 */ 13167 _drawControl: function(control, ctx, methodName, left, top) { 13168 if (!this.isControlVisible(control)) { 13169 return; 13170 } 13171 var size = this.cornerSize; 13172 isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size); 13173 ctx[methodName](left, top, size, size); 13174 }, 13175 13176 /** 13177 * Returns true if the specified control is visible, false otherwise. 13178 * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. 13179 * @returns {Boolean} true if the specified control is visible, false otherwise 13180 */ 13181 isControlVisible: function(controlName) { 13182 return this._getControlsVisibility()[controlName]; 13183 }, 13184 13185 /** 13186 * Sets the visibility of the specified control. 13187 * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. 13188 * @param {Boolean} visible true to set the specified control visible, false otherwise 13189 * @return {fabric.Object} thisArg 13190 * @chainable 13191 */ 13192 setControlVisible: function(controlName, visible) { 13193 this._getControlsVisibility()[controlName] = visible; 13194 return this; 13195 }, 13196 13197 /** 13198 * Sets the visibility state of object controls. 13199 * @param {Object} [options] Options object 13200 * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it 13201 * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it 13202 * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it 13203 * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it 13204 * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it 13205 * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it 13206 * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it 13207 * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it 13208 * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it 13209 * @return {fabric.Object} thisArg 13210 * @chainable 13211 */ 13212 setControlsVisibility: function(options) { 13213 options || (options = { }); 13214 13215 for (var p in options) { 13216 this.setControlVisible(p, options[p]); 13217 } 13218 return this; 13219 }, 13220 13221 /** 13222 * Returns the instance of the control visibility set for this object. 13223 * @private 13224 * @returns {Object} 13225 */ 13226 _getControlsVisibility: function() { 13227 if (!this._controlsVisibility) { 13228 this._controlsVisibility = { 13229 tl: true, 13230 tr: true, 13231 br: true, 13232 bl: true, 13233 ml: true, 13234 mt: true, 13235 mr: true, 13236 mb: true, 13237 mtr: true 13238 }; 13239 } 13240 return this._controlsVisibility; 13241 } 13242 }); 13243})(); 13244 13245 13246fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { 13247 13248 /** 13249 * Animation duration (in ms) for fx* methods 13250 * @type Number 13251 * @default 13252 */ 13253 FX_DURATION: 500, 13254 13255 /** 13256 * Centers object horizontally with animation. 13257 * @param {fabric.Object} object Object to center 13258 * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties 13259 * @param {Function} [callbacks.onComplete] Invoked on completion 13260 * @param {Function} [callbacks.onChange] Invoked on every step of animation 13261 * @return {fabric.Canvas} thisArg 13262 * @chainable 13263 */ 13264 fxCenterObjectH: function (object, callbacks) { 13265 callbacks = callbacks || { }; 13266 13267 var empty = function() { }, 13268 onComplete = callbacks.onComplete || empty, 13269 onChange = callbacks.onChange || empty, 13270 _this = this; 13271 13272 fabric.util.animate({ 13273 startValue: object.get('left'), 13274 endValue: this.getCenter().left, 13275 duration: this.FX_DURATION, 13276 onChange: function(value) { 13277 object.set('left', value); 13278 _this.renderAll(); 13279 onChange(); 13280 }, 13281 onComplete: function() { 13282 object.setCoords(); 13283 onComplete(); 13284 } 13285 }); 13286 13287 return this; 13288 }, 13289 13290 /** 13291 * Centers object vertically with animation. 13292 * @param {fabric.Object} object Object to center 13293 * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties 13294 * @param {Function} [callbacks.onComplete] Invoked on completion 13295 * @param {Function} [callbacks.onChange] Invoked on every step of animation 13296 * @return {fabric.Canvas} thisArg 13297 * @chainable 13298 */ 13299 fxCenterObjectV: function (object, callbacks) { 13300 callbacks = callbacks || { }; 13301 13302 var empty = function() { }, 13303 onComplete = callbacks.onComplete || empty, 13304 onChange = callbacks.onChange || empty, 13305 _this = this; 13306 13307 fabric.util.animate({ 13308 startValue: object.get('top'), 13309 endValue: this.getCenter().top, 13310 duration: this.FX_DURATION, 13311 onChange: function(value) { 13312 object.set('top', value); 13313 _this.renderAll(); 13314 onChange(); 13315 }, 13316 onComplete: function() { 13317 object.setCoords(); 13318 onComplete(); 13319 } 13320 }); 13321 13322 return this; 13323 }, 13324 13325 /** 13326 * Same as `fabric.Canvas#remove` but animated 13327 * @param {fabric.Object} object Object to remove 13328 * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties 13329 * @param {Function} [callbacks.onComplete] Invoked on completion 13330 * @param {Function} [callbacks.onChange] Invoked on every step of animation 13331 * @return {fabric.Canvas} thisArg 13332 * @chainable 13333 */ 13334 fxRemove: function (object, callbacks) { 13335 callbacks = callbacks || { }; 13336 13337 var empty = function() { }, 13338 onComplete = callbacks.onComplete || empty, 13339 onChange = callbacks.onChange || empty, 13340 _this = this; 13341 13342 fabric.util.animate({ 13343 startValue: object.get('opacity'), 13344 endValue: 0, 13345 duration: this.FX_DURATION, 13346 onStart: function() { 13347 object.set('active', false); 13348 }, 13349 onChange: function(value) { 13350 object.set('opacity', value); 13351 _this.renderAll(); 13352 onChange(); 13353 }, 13354 onComplete: function () { 13355 _this.remove(object); 13356 onComplete(); 13357 } 13358 }); 13359 13360 return this; 13361 } 13362}); 13363 13364fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 13365 /** 13366 * Animates object's properties 13367 * @param {String|Object} property Property to animate (if string) or properties to animate (if object) 13368 * @param {Number|Object} value Value to animate property to (if string was given first) or options object 13369 * @return {fabric.Object} thisArg 13370 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#animation} 13371 * @chainable 13372 * 13373 * As object — multiple properties 13374 * 13375 * object.animate({ left: ..., top: ... }); 13376 * object.animate({ left: ..., top: ... }, { duration: ... }); 13377 * 13378 * As string — one property 13379 * 13380 * object.animate('left', ...); 13381 * object.animate('left', { duration: ... }); 13382 * 13383 */ 13384 animate: function() { 13385 if (arguments[0] && typeof arguments[0] === 'object') { 13386 var propsToAnimate = [ ], prop, skipCallbacks; 13387 for (prop in arguments[0]) { 13388 propsToAnimate.push(prop); 13389 } 13390 for (var i = 0, len = propsToAnimate.length; i < len; i++) { 13391 prop = propsToAnimate[i]; 13392 skipCallbacks = i !== len - 1; 13393 this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); 13394 } 13395 } 13396 else { 13397 this._animate.apply(this, arguments); 13398 } 13399 return this; 13400 }, 13401 13402 /** 13403 * @private 13404 * @param {String} property Property to animate 13405 * @param {String} to Value to animate to 13406 * @param {Object} [options] Options object 13407 * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked 13408 */ 13409 _animate: function(property, to, options, skipCallbacks) { 13410 var _this = this, propPair; 13411 13412 to = to.toString(); 13413 13414 if (!options) { 13415 options = { }; 13416 } 13417 else { 13418 options = fabric.util.object.clone(options); 13419 } 13420 13421 if (~property.indexOf('.')) { 13422 propPair = property.split('.'); 13423 } 13424 13425 var currentValue = propPair 13426 ? this.get(propPair[0])[propPair[1]] 13427 : this.get(property); 13428 13429 if (!('from' in options)) { 13430 options.from = currentValue; 13431 } 13432 13433 if (~to.indexOf('=')) { 13434 to = currentValue + parseFloat(to.replace('=', '')); 13435 } 13436 else { 13437 to = parseFloat(to); 13438 } 13439 13440 fabric.util.animate({ 13441 startValue: options.from, 13442 endValue: to, 13443 byValue: options.by, 13444 easing: options.easing, 13445 duration: options.duration, 13446 abort: options.abort && function() { 13447 return options.abort.call(_this); 13448 }, 13449 onChange: function(value) { 13450 if (propPair) { 13451 _this[propPair[0]][propPair[1]] = value; 13452 } 13453 else { 13454 _this.set(property, value); 13455 } 13456 if (skipCallbacks) { 13457 return; 13458 } 13459 options.onChange && options.onChange(); 13460 }, 13461 onComplete: function() { 13462 if (skipCallbacks) { 13463 return; 13464 } 13465 13466 _this.setCoords(); 13467 options.onComplete && options.onComplete(); 13468 } 13469 }); 13470 } 13471}); 13472 13473 13474(function(global) { 13475 13476 'use strict'; 13477 13478 var fabric = global.fabric || (global.fabric = { }), 13479 extend = fabric.util.object.extend, 13480 coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, 13481 supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); 13482 13483 if (fabric.Line) { 13484 fabric.warn('fabric.Line is already defined'); 13485 return; 13486 } 13487 13488 /** 13489 * Line class 13490 * @class fabric.Line 13491 * @extends fabric.Object 13492 * @see {@link fabric.Line#initialize} for constructor definition 13493 */ 13494 fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { 13495 13496 /** 13497 * Type of an object 13498 * @type String 13499 * @default 13500 */ 13501 type: 'line', 13502 13503 /** 13504 * x value or first line edge 13505 * @type Number 13506 * @default 13507 */ 13508 x1: 0, 13509 13510 /** 13511 * y value or first line edge 13512 * @type Number 13513 * @default 13514 */ 13515 y1: 0, 13516 13517 /** 13518 * x value or second line edge 13519 * @type Number 13520 * @default 13521 */ 13522 x2: 0, 13523 13524 /** 13525 * y value or second line edge 13526 * @type Number 13527 * @default 13528 */ 13529 y2: 0, 13530 13531 /** 13532 * Constructor 13533 * @param {Array} [points] Array of points 13534 * @param {Object} [options] Options object 13535 * @return {fabric.Line} thisArg 13536 */ 13537 initialize: function(points, options) { 13538 options = options || { }; 13539 13540 if (!points) { 13541 points = [0, 0, 0, 0]; 13542 } 13543 13544 this.callSuper('initialize', options); 13545 13546 this.set('x1', points[0]); 13547 this.set('y1', points[1]); 13548 this.set('x2', points[2]); 13549 this.set('y2', points[3]); 13550 13551 this._setWidthHeight(options); 13552 }, 13553 13554 /** 13555 * @private 13556 * @param {Object} [options] Options 13557 */ 13558 _setWidthHeight: function(options) { 13559 options || (options = { }); 13560 13561 this.width = Math.abs(this.x2 - this.x1); 13562 this.height = Math.abs(this.y2 - this.y1); 13563 13564 this.left = 'left' in options 13565 ? options.left 13566 : this._getLeftToOriginX(); 13567 13568 this.top = 'top' in options 13569 ? options.top 13570 : this._getTopToOriginY(); 13571 }, 13572 13573 /** 13574 * @private 13575 * @param {String} key 13576 * @param {Any} value 13577 */ 13578 _set: function(key, value) { 13579 this.callSuper('_set', key, value); 13580 if (typeof coordProps[key] !== 'undefined') { 13581 this._setWidthHeight(); 13582 } 13583 return this; 13584 }, 13585 13586 /** 13587 * @private 13588 * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. 13589 */ 13590 _getLeftToOriginX: makeEdgeToOriginGetter( 13591 { // property names 13592 origin: 'originX', 13593 axis1: 'x1', 13594 axis2: 'x2', 13595 dimension: 'width' 13596 }, 13597 { // possible values of origin 13598 nearest: 'left', 13599 center: 'center', 13600 farthest: 'right' 13601 } 13602 ), 13603 13604 /** 13605 * @private 13606 * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. 13607 */ 13608 _getTopToOriginY: makeEdgeToOriginGetter( 13609 { // property names 13610 origin: 'originY', 13611 axis1: 'y1', 13612 axis2: 'y2', 13613 dimension: 'height' 13614 }, 13615 { // possible values of origin 13616 nearest: 'top', 13617 center: 'center', 13618 farthest: 'bottom' 13619 } 13620 ), 13621 13622 /** 13623 * @private 13624 * @param {CanvasRenderingContext2D} ctx Context to render on 13625 */ 13626 _render: function(ctx, noTransform) { 13627 ctx.beginPath(); 13628 13629 if (noTransform) { 13630 // Line coords are distances from left-top of canvas to origin of line. 13631 // To render line in a path-group, we need to translate them to 13632 // distances from center of path-group to center of line. 13633 var cp = this.getCenterPoint(); 13634 ctx.translate( 13635 cp.x - this.strokeWidth / 2, 13636 cp.y - this.strokeWidth / 2 13637 ); 13638 } 13639 13640 if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { 13641 // move from center (of virtual box) to its left/top corner 13642 // we can't assume x1, y1 is top left and x2, y2 is bottom right 13643 var p = this.calcLinePoints(); 13644 ctx.moveTo(p.x1, p.y1); 13645 ctx.lineTo(p.x2, p.y2); 13646 } 13647 13648 ctx.lineWidth = this.strokeWidth; 13649 13650 // TODO: test this 13651 // make sure setting "fill" changes color of a line 13652 // (by copying fillStyle to strokeStyle, since line is stroked, not filled) 13653 var origStrokeStyle = ctx.strokeStyle; 13654 ctx.strokeStyle = this.stroke || ctx.fillStyle; 13655 this.stroke && this._renderStroke(ctx); 13656 ctx.strokeStyle = origStrokeStyle; 13657 }, 13658 13659 /** 13660 * @private 13661 * @param {CanvasRenderingContext2D} ctx Context to render on 13662 */ 13663 _renderDashedStroke: function(ctx) { 13664 var p = this.calcLinePoints(); 13665 13666 ctx.beginPath(); 13667 fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray); 13668 ctx.closePath(); 13669 }, 13670 13671 /** 13672 * Returns object representation of an instance 13673 * @methd toObject 13674 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 13675 * @return {Object} object representation of an instance 13676 */ 13677 toObject: function(propertiesToInclude) { 13678 return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints()); 13679 }, 13680 13681 /** 13682 * Recalculates line points given width and height 13683 * @private 13684 */ 13685 calcLinePoints: function() { 13686 var xMult = this.x1 <= this.x2 ? -1 : 1, 13687 yMult = this.y1 <= this.y2 ? -1 : 1, 13688 x1 = (xMult * this.width * 0.5), 13689 y1 = (yMult * this.height * 0.5), 13690 x2 = (xMult * this.width * -0.5), 13691 y2 = (yMult * this.height * -0.5); 13692 13693 return { 13694 x1: x1, 13695 x2: x2, 13696 y1: y1, 13697 y2: y2 13698 }; 13699 }, 13700 13701 /* _TO_SVG_START_ */ 13702 /** 13703 * Returns SVG representation of an instance 13704 * @param {Function} [reviver] Method for further parsing of svg representation. 13705 * @return {String} svg representation of an instance 13706 */ 13707 toSVG: function(reviver) { 13708 var markup = this._createBaseSVGMarkup(), 13709 p = { x1: this.x1, x2: this.x2, y1: this.y1, y2: this.y2 }; 13710 13711 if (!(this.group && this.group.type === 'path-group')) { 13712 p = this.calcLinePoints(); 13713 } 13714 markup.push( 13715 '<line ', 13716 'x1="', p.x1, 13717 '" y1="', p.y1, 13718 '" x2="', p.x2, 13719 '" y2="', p.y2, 13720 '" style="', this.getSvgStyles(), 13721 '" transform="', this.getSvgTransform(), 13722 this.getSvgTransformMatrix(), 13723 '"/>\n' 13724 ); 13725 13726 return reviver ? reviver(markup.join('')) : markup.join(''); 13727 }, 13728 /* _TO_SVG_END_ */ 13729 13730 /** 13731 * Returns complexity of an instance 13732 * @return {Number} complexity 13733 */ 13734 complexity: function() { 13735 return 1; 13736 } 13737 }); 13738 13739 /* _FROM_SVG_START_ */ 13740 /** 13741 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) 13742 * @static 13743 * @memberOf fabric.Line 13744 * @see http://www.w3.org/TR/SVG/shapes.html#LineElement 13745 */ 13746 fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); 13747 13748 /** 13749 * Returns fabric.Line instance from an SVG element 13750 * @static 13751 * @memberOf fabric.Line 13752 * @param {SVGElement} element Element to parse 13753 * @param {Object} [options] Options object 13754 * @return {fabric.Line} instance of fabric.Line 13755 */ 13756 fabric.Line.fromElement = function(element, options) { 13757 var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), 13758 points = [ 13759 parsedAttributes.x1 || 0, 13760 parsedAttributes.y1 || 0, 13761 parsedAttributes.x2 || 0, 13762 parsedAttributes.y2 || 0 13763 ]; 13764 return new fabric.Line(points, extend(parsedAttributes, options)); 13765 }; 13766 /* _FROM_SVG_END_ */ 13767 13768 /** 13769 * Returns fabric.Line instance from an object representation 13770 * @static 13771 * @memberOf fabric.Line 13772 * @param {Object} object Object to create an instance from 13773 * @return {fabric.Line} instance of fabric.Line 13774 */ 13775 fabric.Line.fromObject = function(object) { 13776 var points = [object.x1, object.y1, object.x2, object.y2]; 13777 return new fabric.Line(points, object); 13778 }; 13779 13780 /** 13781 * Produces a function that calculates distance from canvas edge to Line origin. 13782 */ 13783 function makeEdgeToOriginGetter(propertyNames, originValues) { 13784 var origin = propertyNames.origin, 13785 axis1 = propertyNames.axis1, 13786 axis2 = propertyNames.axis2, 13787 dimension = propertyNames.dimension, 13788 nearest = originValues.nearest, 13789 center = originValues.center, 13790 farthest = originValues.farthest; 13791 13792 return function() { 13793 switch (this.get(origin)) { 13794 case nearest: 13795 return Math.min(this.get(axis1), this.get(axis2)); 13796 case center: 13797 return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); 13798 case farthest: 13799 return Math.max(this.get(axis1), this.get(axis2)); 13800 } 13801 }; 13802 13803 } 13804 13805})(typeof exports !== 'undefined' ? exports : this); 13806 13807 13808(function(global) { 13809 13810 'use strict'; 13811 13812 var fabric = global.fabric || (global.fabric = { }), 13813 pi = Math.PI, 13814 extend = fabric.util.object.extend; 13815 13816 if (fabric.Circle) { 13817 fabric.warn('fabric.Circle is already defined.'); 13818 return; 13819 } 13820 13821 /** 13822 * Circle class 13823 * @class fabric.Circle 13824 * @extends fabric.Object 13825 * @see {@link fabric.Circle#initialize} for constructor definition 13826 */ 13827 fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { 13828 13829 /** 13830 * Type of an object 13831 * @type String 13832 * @default 13833 */ 13834 type: 'circle', 13835 13836 /** 13837 * Radius of this circle 13838 * @type Number 13839 * @default 13840 */ 13841 radius: 0, 13842 13843 /** 13844 * Start angle of the circle, moving clockwise 13845 * @type Number 13846 * @default 0 13847 */ 13848 startAngle: 0, 13849 13850 /** 13851 * End angle of the circle 13852 * @type Number 13853 * @default 2Pi 13854 */ 13855 endAngle: pi * 2, 13856 13857 /** 13858 * Constructor 13859 * @param {Object} [options] Options object 13860 * @return {fabric.Circle} thisArg 13861 */ 13862 initialize: function(options) { 13863 options = options || { }; 13864 13865 this.callSuper('initialize', options); 13866 this.set('radius', options.radius || 0); 13867 this.startAngle = options.startAngle || this.startAngle; 13868 this.endAngle = options.endAngle || this.endAngle; 13869 }, 13870 13871 /** 13872 * @private 13873 * @param {String} key 13874 * @param {Any} value 13875 * @return {fabric.Circle} thisArg 13876 */ 13877 _set: function(key, value) { 13878 this.callSuper('_set', key, value); 13879 13880 if (key === 'radius') { 13881 this.setRadius(value); 13882 } 13883 13884 return this; 13885 }, 13886 13887 /** 13888 * Returns object representation of an instance 13889 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 13890 * @return {Object} object representation of an instance 13891 */ 13892 toObject: function(propertiesToInclude) { 13893 return extend(this.callSuper('toObject', propertiesToInclude), { 13894 radius: this.get('radius'), 13895 startAngle: this.startAngle, 13896 endAngle: this.endAngle 13897 }); 13898 }, 13899 13900 /* _TO_SVG_START_ */ 13901 /** 13902 * Returns svg representation of an instance 13903 * @param {Function} [reviver] Method for further parsing of svg representation. 13904 * @return {String} svg representation of an instance 13905 */ 13906 toSVG: function(reviver) { 13907 var markup = this._createBaseSVGMarkup(), x = 0, y = 0, 13908 angle = (this.endAngle - this.startAngle) % ( 2 * pi); 13909 13910 if (angle === 0) { 13911 if (this.group && this.group.type === 'path-group') { 13912 x = this.left + this.radius; 13913 y = this.top + this.radius; 13914 } 13915 markup.push( 13916 '<circle ', 13917 'cx="' + x + '" cy="' + y + '" ', 13918 'r="', this.radius, 13919 '" style="', this.getSvgStyles(), 13920 '" transform="', this.getSvgTransform(), 13921 ' ', this.getSvgTransformMatrix(), 13922 '"/>\n' 13923 ); 13924 } 13925 else { 13926 var startX = Math.cos(this.startAngle) * this.radius, 13927 startY = Math.sin(this.startAngle) * this.radius, 13928 endX = Math.cos(this.endAngle) * this.radius, 13929 endY = Math.sin(this.endAngle) * this.radius, 13930 largeFlag = angle > pi ? '1' : '0'; 13931 13932 markup.push( 13933 '<path d="M ' + startX + ' ' + startY, 13934 ' A ' + this.radius + ' ' + this.radius, 13935 ' 0 ', + largeFlag + ' 1', ' ' + endX + ' ' + endY, 13936 '" style="', this.getSvgStyles(), 13937 '" transform="', this.getSvgTransform(), 13938 ' ', this.getSvgTransformMatrix(), 13939 '"/>\n' 13940 ); 13941 } 13942 13943 return reviver ? reviver(markup.join('')) : markup.join(''); 13944 }, 13945 /* _TO_SVG_END_ */ 13946 13947 /** 13948 * @private 13949 * @param {CanvasRenderingContext2D} ctx context to render on 13950 * @param {Boolean} [noTransform] When true, context is not transformed 13951 */ 13952 _render: function(ctx, noTransform) { 13953 ctx.beginPath(); 13954 ctx.arc(noTransform ? this.left + this.radius : 0, 13955 noTransform ? this.top + this.radius : 0, 13956 this.radius, 13957 this.startAngle, 13958 this.endAngle, false); 13959 this._renderFill(ctx); 13960 this._renderStroke(ctx); 13961 }, 13962 13963 /** 13964 * Returns horizontal radius of an object (according to how an object is scaled) 13965 * @return {Number} 13966 */ 13967 getRadiusX: function() { 13968 return this.get('radius') * this.get('scaleX'); 13969 }, 13970 13971 /** 13972 * Returns vertical radius of an object (according to how an object is scaled) 13973 * @return {Number} 13974 */ 13975 getRadiusY: function() { 13976 return this.get('radius') * this.get('scaleY'); 13977 }, 13978 13979 /** 13980 * Sets radius of an object (and updates width accordingly) 13981 * @return {Number} 13982 */ 13983 setRadius: function(value) { 13984 this.radius = value; 13985 this.set('width', value * 2).set('height', value * 2); 13986 }, 13987 13988 /** 13989 * Returns complexity of an instance 13990 * @return {Number} complexity of this instance 13991 */ 13992 complexity: function() { 13993 return 1; 13994 } 13995 }); 13996 13997 /* _FROM_SVG_START_ */ 13998 /** 13999 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) 14000 * @static 14001 * @memberOf fabric.Circle 14002 * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement 14003 */ 14004 fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); 14005 14006 /** 14007 * Returns {@link fabric.Circle} instance from an SVG element 14008 * @static 14009 * @memberOf fabric.Circle 14010 * @param {SVGElement} element Element to parse 14011 * @param {Object} [options] Options object 14012 * @throws {Error} If value of `r` attribute is missing or invalid 14013 * @return {fabric.Circle} Instance of fabric.Circle 14014 */ 14015 fabric.Circle.fromElement = function(element, options) { 14016 options || (options = { }); 14017 14018 var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); 14019 14020 if (!isValidRadius(parsedAttributes)) { 14021 throw new Error('value of `r` attribute is required and can not be negative'); 14022 } 14023 14024 parsedAttributes.left = parsedAttributes.left || 0; 14025 parsedAttributes.top = parsedAttributes.top || 0; 14026 14027 var obj = new fabric.Circle(extend(parsedAttributes, options)); 14028 14029 obj.left -= obj.radius; 14030 obj.top -= obj.radius; 14031 return obj; 14032 }; 14033 14034 /** 14035 * @private 14036 */ 14037 function isValidRadius(attributes) { 14038 return (('radius' in attributes) && (attributes.radius >= 0)); 14039 } 14040 /* _FROM_SVG_END_ */ 14041 14042 /** 14043 * Returns {@link fabric.Circle} instance from an object representation 14044 * @static 14045 * @memberOf fabric.Circle 14046 * @param {Object} object Object to create an instance from 14047 * @return {Object} Instance of fabric.Circle 14048 */ 14049 fabric.Circle.fromObject = function(object) { 14050 return new fabric.Circle(object); 14051 }; 14052 14053})(typeof exports !== 'undefined' ? exports : this); 14054 14055 14056(function(global) { 14057 14058 'use strict'; 14059 14060 var fabric = global.fabric || (global.fabric = { }); 14061 14062 if (fabric.Triangle) { 14063 fabric.warn('fabric.Triangle is already defined'); 14064 return; 14065 } 14066 14067 /** 14068 * Triangle class 14069 * @class fabric.Triangle 14070 * @extends fabric.Object 14071 * @return {fabric.Triangle} thisArg 14072 * @see {@link fabric.Triangle#initialize} for constructor definition 14073 */ 14074 fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { 14075 14076 /** 14077 * Type of an object 14078 * @type String 14079 * @default 14080 */ 14081 type: 'triangle', 14082 14083 /** 14084 * Constructor 14085 * @param {Object} [options] Options object 14086 * @return {Object} thisArg 14087 */ 14088 initialize: function(options) { 14089 options = options || { }; 14090 14091 this.callSuper('initialize', options); 14092 14093 this.set('width', options.width || 100) 14094 .set('height', options.height || 100); 14095 }, 14096 14097 /** 14098 * @private 14099 * @param {CanvasRenderingContext2D} ctx Context to render on 14100 */ 14101 _render: function(ctx) { 14102 var widthBy2 = this.width / 2, 14103 heightBy2 = this.height / 2; 14104 14105 ctx.beginPath(); 14106 ctx.moveTo(-widthBy2, heightBy2); 14107 ctx.lineTo(0, -heightBy2); 14108 ctx.lineTo(widthBy2, heightBy2); 14109 ctx.closePath(); 14110 14111 this._renderFill(ctx); 14112 this._renderStroke(ctx); 14113 }, 14114 14115 /** 14116 * @private 14117 * @param {CanvasRenderingContext2D} ctx Context to render on 14118 */ 14119 _renderDashedStroke: function(ctx) { 14120 var widthBy2 = this.width / 2, 14121 heightBy2 = this.height / 2; 14122 14123 ctx.beginPath(); 14124 fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray); 14125 fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray); 14126 fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray); 14127 ctx.closePath(); 14128 }, 14129 14130 /* _TO_SVG_START_ */ 14131 /** 14132 * Returns SVG representation of an instance 14133 * @param {Function} [reviver] Method for further parsing of svg representation. 14134 * @return {String} svg representation of an instance 14135 */ 14136 toSVG: function(reviver) { 14137 var markup = this._createBaseSVGMarkup(), 14138 widthBy2 = this.width / 2, 14139 heightBy2 = this.height / 2, 14140 points = [ 14141 -widthBy2 + ' ' + heightBy2, 14142 '0 ' + -heightBy2, 14143 widthBy2 + ' ' + heightBy2 14144 ] 14145 .join(','); 14146 14147 markup.push( 14148 '<polygon ', 14149 'points="', points, 14150 '" style="', this.getSvgStyles(), 14151 '" transform="', this.getSvgTransform(), 14152 '"/>' 14153 ); 14154 14155 return reviver ? reviver(markup.join('')) : markup.join(''); 14156 }, 14157 /* _TO_SVG_END_ */ 14158 14159 /** 14160 * Returns complexity of an instance 14161 * @return {Number} complexity of this instance 14162 */ 14163 complexity: function() { 14164 return 1; 14165 } 14166 }); 14167 14168 /** 14169 * Returns fabric.Triangle instance from an object representation 14170 * @static 14171 * @memberOf fabric.Triangle 14172 * @param {Object} object Object to create an instance from 14173 * @return {Object} instance of Canvas.Triangle 14174 */ 14175 fabric.Triangle.fromObject = function(object) { 14176 return new fabric.Triangle(object); 14177 }; 14178 14179})(typeof exports !== 'undefined' ? exports : this); 14180 14181 14182(function(global) { 14183 14184 'use strict'; 14185 14186 var fabric = global.fabric || (global.fabric = { }), 14187 piBy2 = Math.PI * 2, 14188 extend = fabric.util.object.extend; 14189 14190 if (fabric.Ellipse) { 14191 fabric.warn('fabric.Ellipse is already defined.'); 14192 return; 14193 } 14194 14195 /** 14196 * Ellipse class 14197 * @class fabric.Ellipse 14198 * @extends fabric.Object 14199 * @return {fabric.Ellipse} thisArg 14200 * @see {@link fabric.Ellipse#initialize} for constructor definition 14201 */ 14202 fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { 14203 14204 /** 14205 * Type of an object 14206 * @type String 14207 * @default 14208 */ 14209 type: 'ellipse', 14210 14211 /** 14212 * Horizontal radius 14213 * @type Number 14214 * @default 14215 */ 14216 rx: 0, 14217 14218 /** 14219 * Vertical radius 14220 * @type Number 14221 * @default 14222 */ 14223 ry: 0, 14224 14225 /** 14226 * Constructor 14227 * @param {Object} [options] Options object 14228 * @return {fabric.Ellipse} thisArg 14229 */ 14230 initialize: function(options) { 14231 options = options || { }; 14232 14233 this.callSuper('initialize', options); 14234 14235 this.set('rx', options.rx || 0); 14236 this.set('ry', options.ry || 0); 14237 }, 14238 14239 /** 14240 * @private 14241 * @param {String} key 14242 * @param {Any} value 14243 * @return {fabric.Ellipse} thisArg 14244 */ 14245 _set: function(key, value) { 14246 this.callSuper('_set', key, value); 14247 switch (key) { 14248 14249 case 'rx': 14250 this.rx = value; 14251 this.set('width', value * 2); 14252 break; 14253 14254 case 'ry': 14255 this.ry = value; 14256 this.set('height', value * 2); 14257 break; 14258 14259 } 14260 return this; 14261 }, 14262 14263 /** 14264 * Returns horizontal radius of an object (according to how an object is scaled) 14265 * @return {Number} 14266 */ 14267 getRx: function() { 14268 return this.get('rx') * this.get('scaleX'); 14269 }, 14270 14271 /** 14272 * Returns Vertical radius of an object (according to how an object is scaled) 14273 * @return {Number} 14274 */ 14275 getRy: function() { 14276 return this.get('ry') * this.get('scaleY'); 14277 }, 14278 14279 /** 14280 * Returns object representation of an instance 14281 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 14282 * @return {Object} object representation of an instance 14283 */ 14284 toObject: function(propertiesToInclude) { 14285 return extend(this.callSuper('toObject', propertiesToInclude), { 14286 rx: this.get('rx'), 14287 ry: this.get('ry') 14288 }); 14289 }, 14290 14291 /* _TO_SVG_START_ */ 14292 /** 14293 * Returns svg representation of an instance 14294 * @param {Function} [reviver] Method for further parsing of svg representation. 14295 * @return {String} svg representation of an instance 14296 */ 14297 toSVG: function(reviver) { 14298 var markup = this._createBaseSVGMarkup(), x = 0, y = 0; 14299 if (this.group && this.group.type === 'path-group') { 14300 x = this.left + this.rx; 14301 y = this.top + this.ry; 14302 } 14303 markup.push( 14304 '<ellipse ', 14305 'cx="', x, '" cy="', y, '" ', 14306 'rx="', this.rx, 14307 '" ry="', this.ry, 14308 '" style="', this.getSvgStyles(), 14309 '" transform="', this.getSvgTransform(), 14310 this.getSvgTransformMatrix(), 14311 '"/>\n' 14312 ); 14313 14314 return reviver ? reviver(markup.join('')) : markup.join(''); 14315 }, 14316 /* _TO_SVG_END_ */ 14317 14318 /** 14319 * @private 14320 * @param {CanvasRenderingContext2D} ctx context to render on 14321 * @param {Boolean} [noTransform] When true, context is not transformed 14322 */ 14323 _render: function(ctx, noTransform) { 14324 ctx.beginPath(); 14325 ctx.save(); 14326 ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0); 14327 ctx.arc( 14328 noTransform ? this.left + this.rx : 0, 14329 noTransform ? (this.top + this.ry) * this.rx/this.ry : 0, 14330 this.rx, 14331 0, 14332 piBy2, 14333 false); 14334 ctx.restore(); 14335 this._renderFill(ctx); 14336 this._renderStroke(ctx); 14337 }, 14338 14339 /** 14340 * Returns complexity of an instance 14341 * @return {Number} complexity 14342 */ 14343 complexity: function() { 14344 return 1; 14345 } 14346 }); 14347 14348 /* _FROM_SVG_START_ */ 14349 /** 14350 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) 14351 * @static 14352 * @memberOf fabric.Ellipse 14353 * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement 14354 */ 14355 fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); 14356 14357 /** 14358 * Returns {@link fabric.Ellipse} instance from an SVG element 14359 * @static 14360 * @memberOf fabric.Ellipse 14361 * @param {SVGElement} element Element to parse 14362 * @param {Object} [options] Options object 14363 * @return {fabric.Ellipse} 14364 */ 14365 fabric.Ellipse.fromElement = function(element, options) { 14366 options || (options = { }); 14367 14368 var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); 14369 14370 parsedAttributes.left = parsedAttributes.left || 0; 14371 parsedAttributes.top = parsedAttributes.top || 0; 14372 14373 var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); 14374 14375 ellipse.top -= ellipse.ry; 14376 ellipse.left -= ellipse.rx; 14377 return ellipse; 14378 }; 14379 /* _FROM_SVG_END_ */ 14380 14381 /** 14382 * Returns {@link fabric.Ellipse} instance from an object representation 14383 * @static 14384 * @memberOf fabric.Ellipse 14385 * @param {Object} object Object to create an instance from 14386 * @return {fabric.Ellipse} 14387 */ 14388 fabric.Ellipse.fromObject = function(object) { 14389 return new fabric.Ellipse(object); 14390 }; 14391 14392})(typeof exports !== 'undefined' ? exports : this); 14393 14394 14395(function(global) { 14396 14397 'use strict'; 14398 14399 var fabric = global.fabric || (global.fabric = { }), 14400 extend = fabric.util.object.extend; 14401 14402 if (fabric.Rect) { 14403 console.warn('fabric.Rect is already defined'); 14404 return; 14405 } 14406 14407 var stateProperties = fabric.Object.prototype.stateProperties.concat(); 14408 stateProperties.push('rx', 'ry', 'x', 'y'); 14409 14410 /** 14411 * Rectangle class 14412 * @class fabric.Rect 14413 * @extends fabric.Object 14414 * @return {fabric.Rect} thisArg 14415 * @see {@link fabric.Rect#initialize} for constructor definition 14416 */ 14417 fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { 14418 14419 /** 14420 * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) 14421 * as well as for history (undo/redo) purposes 14422 * @type Array 14423 */ 14424 stateProperties: stateProperties, 14425 14426 /** 14427 * Type of an object 14428 * @type String 14429 * @default 14430 */ 14431 type: 'rect', 14432 14433 /** 14434 * Horizontal border radius 14435 * @type Number 14436 * @default 14437 */ 14438 rx: 0, 14439 14440 /** 14441 * Vertical border radius 14442 * @type Number 14443 * @default 14444 */ 14445 ry: 0, 14446 14447 /** 14448 * Used to specify dash pattern for stroke on this object 14449 * @type Array 14450 */ 14451 strokeDashArray: null, 14452 14453 /** 14454 * Constructor 14455 * @param {Object} [options] Options object 14456 * @return {Object} thisArg 14457 */ 14458 initialize: function(options) { 14459 options = options || { }; 14460 14461 this.callSuper('initialize', options); 14462 this._initRxRy(); 14463 14464 }, 14465 14466 /** 14467 * Initializes rx/ry attributes 14468 * @private 14469 */ 14470 _initRxRy: function() { 14471 if (this.rx && !this.ry) { 14472 this.ry = this.rx; 14473 } 14474 else if (this.ry && !this.rx) { 14475 this.rx = this.ry; 14476 } 14477 }, 14478 14479 /** 14480 * @private 14481 * @param {CanvasRenderingContext2D} ctx Context to render on 14482 */ 14483 _render: function(ctx, noTransform) { 14484 14485 // optimize 1x1 case (used in spray brush) 14486 if (this.width === 1 && this.height === 1) { 14487 ctx.fillRect(0, 0, 1, 1); 14488 return; 14489 } 14490 14491 var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, 14492 ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, 14493 w = this.width, 14494 h = this.height, 14495 x = noTransform ? this.left : -this.width / 2, 14496 y = noTransform ? this.top : -this.height / 2, 14497 isRounded = rx !== 0 || ry !== 0, 14498 k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */; 14499 14500 ctx.beginPath(); 14501 14502 ctx.moveTo(x + rx, y); 14503 14504 ctx.lineTo(x + w - rx, y); 14505 isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); 14506 14507 ctx.lineTo(x + w, y + h - ry); 14508 isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); 14509 14510 ctx.lineTo(x + rx, y + h); 14511 isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); 14512 14513 ctx.lineTo(x, y + ry); 14514 isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); 14515 14516 ctx.closePath(); 14517 14518 this._renderFill(ctx); 14519 this._renderStroke(ctx); 14520 }, 14521 14522 /** 14523 * @private 14524 * @param {CanvasRenderingContext2D} ctx Context to render on 14525 */ 14526 _renderDashedStroke: function(ctx) { 14527 var x = -this.width / 2, 14528 y = -this.height / 2, 14529 w = this.width, 14530 h = this.height; 14531 14532 ctx.beginPath(); 14533 fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); 14534 fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); 14535 fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); 14536 fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); 14537 ctx.closePath(); 14538 }, 14539 14540 /** 14541 * Returns object representation of an instance 14542 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 14543 * @return {Object} object representation of an instance 14544 */ 14545 toObject: function(propertiesToInclude) { 14546 var object = extend(this.callSuper('toObject', propertiesToInclude), { 14547 rx: this.get('rx') || 0, 14548 ry: this.get('ry') || 0 14549 }); 14550 if (!this.includeDefaultValues) { 14551 this._removeDefaultValues(object); 14552 } 14553 return object; 14554 }, 14555 14556 /* _TO_SVG_START_ */ 14557 /** 14558 * Returns svg representation of an instance 14559 * @param {Function} [reviver] Method for further parsing of svg representation. 14560 * @return {String} svg representation of an instance 14561 */ 14562 toSVG: function(reviver) { 14563 var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top; 14564 if (!(this.group && this.group.type === 'path-group')) { 14565 x = -this.width / 2; 14566 y = -this.height / 2; 14567 } 14568 markup.push( 14569 '<rect ', 14570 'x="', x, '" y="', y, 14571 '" rx="', this.get('rx'), '" ry="', this.get('ry'), 14572 '" width="', this.width, '" height="', this.height, 14573 '" style="', this.getSvgStyles(), 14574 '" transform="', this.getSvgTransform(), 14575 this.getSvgTransformMatrix(), 14576 '"/>\n'); 14577 14578 return reviver ? reviver(markup.join('')) : markup.join(''); 14579 }, 14580 /* _TO_SVG_END_ */ 14581 14582 /** 14583 * Returns complexity of an instance 14584 * @return {Number} complexity 14585 */ 14586 complexity: function() { 14587 return 1; 14588 } 14589 }); 14590 14591 /* _FROM_SVG_START_ */ 14592 /** 14593 * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) 14594 * @static 14595 * @memberOf fabric.Rect 14596 * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement 14597 */ 14598 fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); 14599 14600 /** 14601 * Returns {@link fabric.Rect} instance from an SVG element 14602 * @static 14603 * @memberOf fabric.Rect 14604 * @param {SVGElement} element Element to parse 14605 * @param {Object} [options] Options object 14606 * @return {fabric.Rect} Instance of fabric.Rect 14607 */ 14608 fabric.Rect.fromElement = function(element, options) { 14609 if (!element) { 14610 return null; 14611 } 14612 options = options || { }; 14613 14614 var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); 14615 14616 parsedAttributes.left = parsedAttributes.left || 0; 14617 parsedAttributes.top = parsedAttributes.top || 0; 14618 var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); 14619 rect.visible = rect.width > 0 && rect.height > 0; 14620 return rect; 14621 }; 14622 /* _FROM_SVG_END_ */ 14623 14624 /** 14625 * Returns {@link fabric.Rect} instance from an object representation 14626 * @static 14627 * @memberOf fabric.Rect 14628 * @param {Object} object Object to create an instance from 14629 * @return {Object} instance of fabric.Rect 14630 */ 14631 fabric.Rect.fromObject = function(object) { 14632 return new fabric.Rect(object); 14633 }; 14634 14635})(typeof exports !== 'undefined' ? exports : this); 14636 14637 14638(function(global) { 14639 14640 'use strict'; 14641 14642 var fabric = global.fabric || (global.fabric = { }); 14643 14644 if (fabric.Polyline) { 14645 fabric.warn('fabric.Polyline is already defined'); 14646 return; 14647 } 14648 14649 /** 14650 * Polyline class 14651 * @class fabric.Polyline 14652 * @extends fabric.Object 14653 * @see {@link fabric.Polyline#initialize} for constructor definition 14654 */ 14655 fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { 14656 14657 /** 14658 * Type of an object 14659 * @type String 14660 * @default 14661 */ 14662 type: 'polyline', 14663 14664 /** 14665 * Points array 14666 * @type Array 14667 * @default 14668 */ 14669 points: null, 14670 14671 /** 14672 * Minimum X from points values, necessary to offset points 14673 * @type Number 14674 * @default 14675 */ 14676 minX: 0, 14677 14678 /** 14679 * Minimum Y from points values, necessary to offset points 14680 * @type Number 14681 * @default 14682 */ 14683 minY: 0, 14684 14685 /** 14686 * Constructor 14687 * @param {Array} points Array of points (where each point is an object with x and y) 14688 * @param {Object} [options] Options object 14689 * @param {Boolean} [skipOffset] Whether points offsetting should be skipped 14690 * @return {fabric.Polyline} thisArg 14691 * @example 14692 * var poly = new fabric.Polyline([ 14693 * { x: 10, y: 10 }, 14694 * { x: 50, y: 30 }, 14695 * { x: 40, y: 70 }, 14696 * { x: 60, y: 50 }, 14697 * { x: 100, y: 150 }, 14698 * { x: 40, y: 100 } 14699 * ], { 14700 * stroke: 'red', 14701 * left: 100, 14702 * top: 100 14703 * }); 14704 */ 14705 initialize: function(points, options) { 14706 return fabric.Polygon.prototype.initialize.call(this, points, options); 14707 }, 14708 14709 /** 14710 * @private 14711 */ 14712 _calcDimensions: function() { 14713 return fabric.Polygon.prototype._calcDimensions.call(this); 14714 }, 14715 14716 /** 14717 * @private 14718 */ 14719 _applyPointOffset: function() { 14720 return fabric.Polygon.prototype._applyPointOffset.call(this); 14721 }, 14722 14723 /** 14724 * Returns object representation of an instance 14725 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 14726 * @return {Object} Object representation of an instance 14727 */ 14728 toObject: function(propertiesToInclude) { 14729 return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); 14730 }, 14731 14732 /* _TO_SVG_START_ */ 14733 /** 14734 * Returns SVG representation of an instance 14735 * @param {Function} [reviver] Method for further parsing of svg representation. 14736 * @return {String} svg representation of an instance 14737 */ 14738 toSVG: function(reviver) { 14739 return fabric.Polygon.prototype.toSVG.call(this, reviver); 14740 }, 14741 /* _TO_SVG_END_ */ 14742 14743 /** 14744 * @private 14745 * @param {CanvasRenderingContext2D} ctx Context to render on 14746 */ 14747 _render: function(ctx) { 14748 if (!fabric.Polygon.prototype.commonRender.call(this, ctx)) { 14749 return; 14750 } 14751 this._renderFill(ctx); 14752 this._renderStroke(ctx); 14753 }, 14754 14755 /** 14756 * @private 14757 * @param {CanvasRenderingContext2D} ctx Context to render on 14758 */ 14759 _renderDashedStroke: function(ctx) { 14760 var p1, p2; 14761 14762 ctx.beginPath(); 14763 for (var i = 0, len = this.points.length; i < len; i++) { 14764 p1 = this.points[i]; 14765 p2 = this.points[i + 1] || p1; 14766 fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); 14767 } 14768 }, 14769 14770 /** 14771 * Returns complexity of an instance 14772 * @return {Number} complexity of this instance 14773 */ 14774 complexity: function() { 14775 return this.get('points').length; 14776 } 14777 }); 14778 14779 /* _FROM_SVG_START_ */ 14780 /** 14781 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) 14782 * @static 14783 * @memberOf fabric.Polyline 14784 * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement 14785 */ 14786 fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); 14787 14788 /** 14789 * Returns fabric.Polyline instance from an SVG element 14790 * @static 14791 * @memberOf fabric.Polyline 14792 * @param {SVGElement} element Element to parse 14793 * @param {Object} [options] Options object 14794 * @return {fabric.Polyline} Instance of fabric.Polyline 14795 */ 14796 fabric.Polyline.fromElement = function(element, options) { 14797 if (!element) { 14798 return null; 14799 } 14800 options || (options = { }); 14801 14802 var points = fabric.parsePointsAttribute(element.getAttribute('points')), 14803 parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); 14804 14805 return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options)); 14806 }; 14807 /* _FROM_SVG_END_ */ 14808 14809 /** 14810 * Returns fabric.Polyline instance from an object representation 14811 * @static 14812 * @memberOf fabric.Polyline 14813 * @param {Object} object Object to create an instance from 14814 * @return {fabric.Polyline} Instance of fabric.Polyline 14815 */ 14816 fabric.Polyline.fromObject = function(object) { 14817 var points = object.points; 14818 return new fabric.Polyline(points, object, true); 14819 }; 14820 14821})(typeof exports !== 'undefined' ? exports : this); 14822 14823 14824(function(global) { 14825 14826 'use strict'; 14827 14828 var fabric = global.fabric || (global.fabric = { }), 14829 extend = fabric.util.object.extend, 14830 min = fabric.util.array.min, 14831 max = fabric.util.array.max, 14832 toFixed = fabric.util.toFixed; 14833 14834 if (fabric.Polygon) { 14835 fabric.warn('fabric.Polygon is already defined'); 14836 return; 14837 } 14838 14839 /** 14840 * Polygon class 14841 * @class fabric.Polygon 14842 * @extends fabric.Object 14843 * @see {@link fabric.Polygon#initialize} for constructor definition 14844 */ 14845 fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ { 14846 14847 /** 14848 * Type of an object 14849 * @type String 14850 * @default 14851 */ 14852 type: 'polygon', 14853 14854 /** 14855 * Points array 14856 * @type Array 14857 * @default 14858 */ 14859 points: null, 14860 14861 /** 14862 * Minimum X from points values, necessary to offset points 14863 * @type Number 14864 * @default 14865 */ 14866 minX: 0, 14867 14868 /** 14869 * Minimum Y from points values, necessary to offset points 14870 * @type Number 14871 * @default 14872 */ 14873 minY: 0, 14874 14875 /** 14876 * Constructor 14877 * @param {Array} points Array of points 14878 * @param {Object} [options] Options object 14879 * @return {fabric.Polygon} thisArg 14880 */ 14881 initialize: function(points, options) { 14882 options = options || { }; 14883 this.points = points || [ ]; 14884 this.callSuper('initialize', options); 14885 this._calcDimensions(); 14886 if (!('top' in options)) { 14887 this.top = this.minY; 14888 } 14889 if (!('left' in options)) { 14890 this.left = this.minX; 14891 } 14892 }, 14893 14894 /** 14895 * @private 14896 */ 14897 _calcDimensions: function() { 14898 14899 var points = this.points, 14900 minX = min(points, 'x'), 14901 minY = min(points, 'y'), 14902 maxX = max(points, 'x'), 14903 maxY = max(points, 'y'); 14904 14905 this.width = (maxX - minX) || 0; 14906 this.height = (maxY - minY) || 0; 14907 14908 this.minX = minX || 0, 14909 this.minY = minY || 0; 14910 }, 14911 14912 /** 14913 * @private 14914 */ 14915 _applyPointOffset: function() { 14916 // change points to offset polygon into a bounding box 14917 // executed one time 14918 this.points.forEach(function(p) { 14919 p.x -= (this.minX + this.width / 2); 14920 p.y -= (this.minY + this.height / 2); 14921 }, this); 14922 }, 14923 14924 /** 14925 * Returns object representation of an instance 14926 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 14927 * @return {Object} Object representation of an instance 14928 */ 14929 toObject: function(propertiesToInclude) { 14930 return extend(this.callSuper('toObject', propertiesToInclude), { 14931 points: this.points.concat() 14932 }); 14933 }, 14934 14935 /* _TO_SVG_START_ */ 14936 /** 14937 * Returns svg representation of an instance 14938 * @param {Function} [reviver] Method for further parsing of svg representation. 14939 * @return {String} svg representation of an instance 14940 */ 14941 toSVG: function(reviver) { 14942 var points = [], 14943 markup = this._createBaseSVGMarkup(); 14944 14945 for (var i = 0, len = this.points.length; i < len; i++) { 14946 points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); 14947 } 14948 14949 markup.push( 14950 '<', this.type, ' ', 14951 'points="', points.join(''), 14952 '" style="', this.getSvgStyles(), 14953 '" transform="', this.getSvgTransform(), 14954 ' ', this.getSvgTransformMatrix(), 14955 '"/>\n' 14956 ); 14957 14958 return reviver ? reviver(markup.join('')) : markup.join(''); 14959 }, 14960 /* _TO_SVG_END_ */ 14961 14962 /** 14963 * @private 14964 * @param {CanvasRenderingContext2D} ctx Context to render on 14965 */ 14966 _render: function(ctx) { 14967 if (!this.commonRender(ctx)) { 14968 return; 14969 } 14970 this._renderFill(ctx); 14971 if (this.stroke || this.strokeDashArray) { 14972 ctx.closePath(); 14973 this._renderStroke(ctx); 14974 } 14975 }, 14976 14977 /** 14978 * @private 14979 * @param {CanvasRenderingContext2D} ctx Context to render on 14980 */ 14981 commonRender: function(ctx) { 14982 var point, len = this.points.length; 14983 14984 if (!len || isNaN(this.points[len - 1].y)) { 14985 // do not draw if no points or odd points 14986 // NaN comes from parseFloat of a empty string in parser 14987 return false; 14988 } 14989 14990 ctx.beginPath(); 14991 14992 if (this._applyPointOffset) { 14993 if (!(this.group && this.group.type === 'path-group')) { 14994 this._applyPointOffset(); 14995 } 14996 this._applyPointOffset = null; 14997 } 14998 14999 ctx.moveTo(this.points[0].x, this.points[0].y); 15000 for (var i = 0; i < len; i++) { 15001 point = this.points[i]; 15002 ctx.lineTo(point.x, point.y); 15003 } 15004 return true; 15005 }, 15006 15007 /** 15008 * @private 15009 * @param {CanvasRenderingContext2D} ctx Context to render on 15010 */ 15011 _renderDashedStroke: function(ctx) { 15012 fabric.Polyline.prototype._renderDashedStroke.call(this, ctx); 15013 ctx.closePath(); 15014 }, 15015 15016 /** 15017 * Returns complexity of an instance 15018 * @return {Number} complexity of this instance 15019 */ 15020 complexity: function() { 15021 return this.points.length; 15022 } 15023 }); 15024 15025 /* _FROM_SVG_START_ */ 15026 /** 15027 * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) 15028 * @static 15029 * @memberOf fabric.Polygon 15030 * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement 15031 */ 15032 fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); 15033 15034 /** 15035 * Returns {@link fabric.Polygon} instance from an SVG element 15036 * @static 15037 * @memberOf fabric.Polygon 15038 * @param {SVGElement} element Element to parse 15039 * @param {Object} [options] Options object 15040 * @return {fabric.Polygon} Instance of fabric.Polygon 15041 */ 15042 fabric.Polygon.fromElement = function(element, options) { 15043 if (!element) { 15044 return null; 15045 } 15046 15047 options || (options = { }); 15048 15049 var points = fabric.parsePointsAttribute(element.getAttribute('points')), 15050 parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); 15051 15052 return new fabric.Polygon(points, extend(parsedAttributes, options)); 15053 }; 15054 /* _FROM_SVG_END_ */ 15055 15056 /** 15057 * Returns fabric.Polygon instance from an object representation 15058 * @static 15059 * @memberOf fabric.Polygon 15060 * @param {Object} object Object to create an instance from 15061 * @return {fabric.Polygon} Instance of fabric.Polygon 15062 */ 15063 fabric.Polygon.fromObject = function(object) { 15064 return new fabric.Polygon(object.points, object, true); 15065 }; 15066 15067})(typeof exports !== 'undefined' ? exports : this); 15068 15069 15070(function(global) { 15071 15072 'use strict'; 15073 15074 var fabric = global.fabric || (global.fabric = { }), 15075 min = fabric.util.array.min, 15076 max = fabric.util.array.max, 15077 extend = fabric.util.object.extend, 15078 _toString = Object.prototype.toString, 15079 drawArc = fabric.util.drawArc, 15080 commandLengths = { 15081 m: 2, 15082 l: 2, 15083 h: 1, 15084 v: 1, 15085 c: 6, 15086 s: 4, 15087 q: 4, 15088 t: 2, 15089 a: 7 15090 }, 15091 repeatedCommands = { 15092 m: 'l', 15093 M: 'L' 15094 }; 15095 15096 if (fabric.Path) { 15097 fabric.warn('fabric.Path is already defined'); 15098 return; 15099 } 15100 15101 /** 15102 * Path class 15103 * @class fabric.Path 15104 * @extends fabric.Object 15105 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} 15106 * @see {@link fabric.Path#initialize} for constructor definition 15107 */ 15108 fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { 15109 15110 /** 15111 * Type of an object 15112 * @type String 15113 * @default 15114 */ 15115 type: 'path', 15116 15117 /** 15118 * Array of path points 15119 * @type Array 15120 * @default 15121 */ 15122 path: null, 15123 15124 /** 15125 * Minimum X from points values, necessary to offset points 15126 * @type Number 15127 * @default 15128 */ 15129 minX: 0, 15130 15131 /** 15132 * Minimum Y from points values, necessary to offset points 15133 * @type Number 15134 * @default 15135 */ 15136 minY: 0, 15137 15138 /** 15139 * Constructor 15140 * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) 15141 * @param {Object} [options] Options object 15142 * @return {fabric.Path} thisArg 15143 */ 15144 initialize: function(path, options) { 15145 options = options || { }; 15146 15147 this.setOptions(options); 15148 15149 if (!path) { 15150 throw new Error('`path` argument is required'); 15151 } 15152 15153 var fromArray = _toString.call(path) === '[object Array]'; 15154 15155 this.path = fromArray 15156 ? path 15157 // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) 15158 : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); 15159 15160 if (!this.path) { 15161 return; 15162 } 15163 15164 if (!fromArray) { 15165 this.path = this._parsePath(); 15166 } 15167 15168 this._setPositionDimensions(); 15169 15170 if (options.sourcePath) { 15171 this.setSourcePath(options.sourcePath); 15172 } 15173 }, 15174 15175 /** 15176 * @private 15177 */ 15178 _setPositionDimensions: function() { 15179 var calcDim = this._parseDimensions(); 15180 15181 this.minX = calcDim.left; 15182 this.minY = calcDim.top; 15183 this.width = calcDim.width; 15184 this.height = calcDim.height; 15185 15186 calcDim.left += this.originX === 'center' 15187 ? this.width / 2 15188 : this.originX === 'right' 15189 ? this.width 15190 : 0; 15191 15192 calcDim.top += this.originY === 'center' 15193 ? this.height / 2 15194 : this.originY === 'bottom' 15195 ? this.height 15196 : 0; 15197 15198 this.top = this.top || calcDim.top; 15199 this.left = this.left || calcDim.left; 15200 15201 this.pathOffset = this.pathOffset || { 15202 x: this.minX + this.width / 2, 15203 y: this.minY + this.height / 2 15204 }; 15205 }, 15206 15207 /** 15208 * @private 15209 * @param {CanvasRenderingContext2D} ctx context to render path on 15210 */ 15211 _render: function(ctx) { 15212 var current, // current instruction 15213 previous = null, 15214 subpathStartX = 0, 15215 subpathStartY = 0, 15216 x = 0, // current x 15217 y = 0, // current y 15218 controlX = 0, // current control point x 15219 controlY = 0, // current control point y 15220 tempX, 15221 tempY, 15222 l = -this.pathOffset.x, 15223 t = -this.pathOffset.y; 15224 15225 if (this.group && this.group.type === 'path-group') { 15226 l = 0; 15227 t = 0; 15228 } 15229 15230 ctx.beginPath(); 15231 15232 for (var i = 0, len = this.path.length; i < len; ++i) { 15233 15234 current = this.path[i]; 15235 15236 switch (current[0]) { // first letter 15237 15238 case 'l': // lineto, relative 15239 x += current[1]; 15240 y += current[2]; 15241 ctx.lineTo(x + l, y + t); 15242 break; 15243 15244 case 'L': // lineto, absolute 15245 x = current[1]; 15246 y = current[2]; 15247 ctx.lineTo(x + l, y + t); 15248 break; 15249 15250 case 'h': // horizontal lineto, relative 15251 x += current[1]; 15252 ctx.lineTo(x + l, y + t); 15253 break; 15254 15255 case 'H': // horizontal lineto, absolute 15256 x = current[1]; 15257 ctx.lineTo(x + l, y + t); 15258 break; 15259 15260 case 'v': // vertical lineto, relative 15261 y += current[1]; 15262 ctx.lineTo(x + l, y + t); 15263 break; 15264 15265 case 'V': // verical lineto, absolute 15266 y = current[1]; 15267 ctx.lineTo(x + l, y + t); 15268 break; 15269 15270 case 'm': // moveTo, relative 15271 x += current[1]; 15272 y += current[2]; 15273 subpathStartX = x; 15274 subpathStartY = y; 15275 ctx.moveTo(x + l, y + t); 15276 break; 15277 15278 case 'M': // moveTo, absolute 15279 x = current[1]; 15280 y = current[2]; 15281 subpathStartX = x; 15282 subpathStartY = y; 15283 ctx.moveTo(x + l, y + t); 15284 break; 15285 15286 case 'c': // bezierCurveTo, relative 15287 tempX = x + current[5]; 15288 tempY = y + current[6]; 15289 controlX = x + current[3]; 15290 controlY = y + current[4]; 15291 ctx.bezierCurveTo( 15292 x + current[1] + l, // x1 15293 y + current[2] + t, // y1 15294 controlX + l, // x2 15295 controlY + t, // y2 15296 tempX + l, 15297 tempY + t 15298 ); 15299 x = tempX; 15300 y = tempY; 15301 break; 15302 15303 case 'C': // bezierCurveTo, absolute 15304 x = current[5]; 15305 y = current[6]; 15306 controlX = current[3]; 15307 controlY = current[4]; 15308 ctx.bezierCurveTo( 15309 current[1] + l, 15310 current[2] + t, 15311 controlX + l, 15312 controlY + t, 15313 x + l, 15314 y + t 15315 ); 15316 break; 15317 15318 case 's': // shorthand cubic bezierCurveTo, relative 15319 15320 // transform to absolute x,y 15321 tempX = x + current[3]; 15322 tempY = y + current[4]; 15323 15324 if (previous[0].match(/[CcSs]/) === null) { 15325 // If there is no previous command or if the previous command was not a C, c, S, or s, 15326 // the control point is coincident with the current point 15327 controlX = x; 15328 controlY = y; 15329 } 15330 else { 15331 // calculate reflection of previous control points 15332 controlX = 2 * x - controlX; 15333 controlY = 2 * y - controlY; 15334 } 15335 15336 ctx.bezierCurveTo( 15337 controlX + l, 15338 controlY + t, 15339 x + current[1] + l, 15340 y + current[2] + t, 15341 tempX + l, 15342 tempY + t 15343 ); 15344 // set control point to 2nd one of this command 15345 // "... the first control point is assumed to be 15346 // the reflection of the second control point on 15347 // the previous command relative to the current point." 15348 controlX = x + current[1]; 15349 controlY = y + current[2]; 15350 15351 x = tempX; 15352 y = tempY; 15353 break; 15354 15355 case 'S': // shorthand cubic bezierCurveTo, absolute 15356 tempX = current[3]; 15357 tempY = current[4]; 15358 if (previous[0].match(/[CcSs]/) === null) { 15359 // If there is no previous command or if the previous command was not a C, c, S, or s, 15360 // the control point is coincident with the current point 15361 controlX = x; 15362 controlY = y; 15363 } 15364 else { 15365 // calculate reflection of previous control points 15366 controlX = 2 * x - controlX; 15367 controlY = 2 * y - controlY; 15368 } 15369 ctx.bezierCurveTo( 15370 controlX + l, 15371 controlY + t, 15372 current[1] + l, 15373 current[2] + t, 15374 tempX + l, 15375 tempY + t 15376 ); 15377 x = tempX; 15378 y = tempY; 15379 15380 // set control point to 2nd one of this command 15381 // "... the first control point is assumed to be 15382 // the reflection of the second control point on 15383 // the previous command relative to the current point." 15384 controlX = current[1]; 15385 controlY = current[2]; 15386 15387 break; 15388 15389 case 'q': // quadraticCurveTo, relative 15390 // transform to absolute x,y 15391 tempX = x + current[3]; 15392 tempY = y + current[4]; 15393 15394 controlX = x + current[1]; 15395 controlY = y + current[2]; 15396 15397 ctx.quadraticCurveTo( 15398 controlX + l, 15399 controlY + t, 15400 tempX + l, 15401 tempY + t 15402 ); 15403 x = tempX; 15404 y = tempY; 15405 break; 15406 15407 case 'Q': // quadraticCurveTo, absolute 15408 tempX = current[3]; 15409 tempY = current[4]; 15410 15411 ctx.quadraticCurveTo( 15412 current[1] + l, 15413 current[2] + t, 15414 tempX + l, 15415 tempY + t 15416 ); 15417 x = tempX; 15418 y = tempY; 15419 controlX = current[1]; 15420 controlY = current[2]; 15421 break; 15422 15423 case 't': // shorthand quadraticCurveTo, relative 15424 15425 // transform to absolute x,y 15426 tempX = x + current[1]; 15427 tempY = y + current[2]; 15428 15429 if (previous[0].match(/[QqTt]/) === null) { 15430 // If there is no previous command or if the previous command was not a Q, q, T or t, 15431 // assume the control point is coincident with the current point 15432 controlX = x; 15433 controlY = y; 15434 } 15435 else { 15436 // calculate reflection of previous control point 15437 controlX = 2 * x - controlX; 15438 controlY = 2 * y - controlY; 15439 } 15440 15441 ctx.quadraticCurveTo( 15442 controlX + l, 15443 controlY + t, 15444 tempX + l, 15445 tempY + t 15446 ); 15447 x = tempX; 15448 y = tempY; 15449 15450 break; 15451 15452 case 'T': 15453 tempX = current[1]; 15454 tempY = current[2]; 15455 15456 if (previous[0].match(/[QqTt]/) === null) { 15457 // If there is no previous command or if the previous command was not a Q, q, T or t, 15458 // assume the control point is coincident with the current point 15459 controlX = x; 15460 controlY = y; 15461 } 15462 else { 15463 // calculate reflection of previous control point 15464 controlX = 2 * x - controlX; 15465 controlY = 2 * y - controlY; 15466 } 15467 ctx.quadraticCurveTo( 15468 controlX + l, 15469 controlY + t, 15470 tempX + l, 15471 tempY + t 15472 ); 15473 x = tempX; 15474 y = tempY; 15475 break; 15476 15477 case 'a': 15478 // TODO: optimize this 15479 drawArc(ctx, x + l, y + t, [ 15480 current[1], 15481 current[2], 15482 current[3], 15483 current[4], 15484 current[5], 15485 current[6] + x + l, 15486 current[7] + y + t 15487 ]); 15488 x += current[6]; 15489 y += current[7]; 15490 break; 15491 15492 case 'A': 15493 // TODO: optimize this 15494 drawArc(ctx, x + l, y + t, [ 15495 current[1], 15496 current[2], 15497 current[3], 15498 current[4], 15499 current[5], 15500 current[6] + l, 15501 current[7] + t 15502 ]); 15503 x = current[6]; 15504 y = current[7]; 15505 break; 15506 15507 case 'z': 15508 case 'Z': 15509 x = subpathStartX; 15510 y = subpathStartY; 15511 ctx.closePath(); 15512 break; 15513 } 15514 previous = current; 15515 } 15516 this._renderFill(ctx); 15517 this._renderStroke(ctx); 15518 }, 15519 15520 /** 15521 * Returns string representation of an instance 15522 * @return {String} string representation of an instance 15523 */ 15524 toString: function() { 15525 return '#<fabric.Path (' + this.complexity() + 15526 '): { "top": ' + this.top + ', "left": ' + this.left + ' }>'; 15527 }, 15528 15529 /** 15530 * Returns object representation of an instance 15531 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 15532 * @return {Object} object representation of an instance 15533 */ 15534 toObject: function(propertiesToInclude) { 15535 var o = extend(this.callSuper('toObject', propertiesToInclude), { 15536 path: this.path.map(function(item) { return item.slice() }), 15537 pathOffset: this.pathOffset 15538 }); 15539 if (this.sourcePath) { 15540 o.sourcePath = this.sourcePath; 15541 } 15542 if (this.transformMatrix) { 15543 o.transformMatrix = this.transformMatrix; 15544 } 15545 return o; 15546 }, 15547 15548 /** 15549 * Returns dataless object representation of an instance 15550 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 15551 * @return {Object} object representation of an instance 15552 */ 15553 toDatalessObject: function(propertiesToInclude) { 15554 var o = this.toObject(propertiesToInclude); 15555 if (this.sourcePath) { 15556 o.path = this.sourcePath; 15557 } 15558 delete o.sourcePath; 15559 return o; 15560 }, 15561 15562 /* _TO_SVG_START_ */ 15563 /** 15564 * Returns svg representation of an instance 15565 * @param {Function} [reviver] Method for further parsing of svg representation. 15566 * @return {String} svg representation of an instance 15567 */ 15568 toSVG: function(reviver) { 15569 var chunks = [], 15570 markup = this._createBaseSVGMarkup(), addTransform = ''; 15571 15572 for (var i = 0, len = this.path.length; i < len; i++) { 15573 chunks.push(this.path[i].join(' ')); 15574 } 15575 var path = chunks.join(' '); 15576 if (!(this.group && this.group.type === 'path-group')) { 15577 addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') '; 15578 } 15579 markup.push( 15580 //jscs:disable validateIndentation 15581 '<path ', 15582 'd="', path, 15583 '" style="', this.getSvgStyles(), 15584 '" transform="', this.getSvgTransform(), addTransform, 15585 this.getSvgTransformMatrix(), '" stroke-linecap="round" ', 15586 '/>\n' 15587 //jscs:enable validateIndentation 15588 ); 15589 15590 return reviver ? reviver(markup.join('')) : markup.join(''); 15591 }, 15592 /* _TO_SVG_END_ */ 15593 15594 /** 15595 * Returns number representation of an instance complexity 15596 * @return {Number} complexity of this instance 15597 */ 15598 complexity: function() { 15599 return this.path.length; 15600 }, 15601 15602 /** 15603 * @private 15604 */ 15605 _parsePath: function() { 15606 var result = [ ], 15607 coords = [ ], 15608 currentPath, 15609 parsed, 15610 re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, 15611 match, 15612 coordsStr; 15613 15614 for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { 15615 currentPath = this.path[i]; 15616 15617 coordsStr = currentPath.slice(1).trim(); 15618 coords.length = 0; 15619 15620 while ((match = re.exec(coordsStr))) { 15621 coords.push(match[0]); 15622 } 15623 15624 coordsParsed = [ currentPath.charAt(0) ]; 15625 15626 for (var j = 0, jlen = coords.length; j < jlen; j++) { 15627 parsed = parseFloat(coords[j]); 15628 if (!isNaN(parsed)) { 15629 coordsParsed.push(parsed); 15630 } 15631 } 15632 15633 var command = coordsParsed[0], 15634 commandLength = commandLengths[command.toLowerCase()], 15635 repeatedCommand = repeatedCommands[command] || command; 15636 15637 if (coordsParsed.length - 1 > commandLength) { 15638 for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { 15639 result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); 15640 command = repeatedCommand; 15641 } 15642 } 15643 else { 15644 result.push(coordsParsed); 15645 } 15646 } 15647 15648 return result; 15649 }, 15650 15651 /** 15652 * @private 15653 */ 15654 _parseDimensions: function() { 15655 15656 var aX = [], 15657 aY = [], 15658 current, // current instruction 15659 previous = null, 15660 subpathStartX = 0, 15661 subpathStartY = 0, 15662 x = 0, // current x 15663 y = 0, // current y 15664 controlX = 0, // current control point x 15665 controlY = 0, // current control point y 15666 tempX, 15667 tempY, 15668 bounds; 15669 15670 for (var i = 0, len = this.path.length; i < len; ++i) { 15671 15672 current = this.path[i]; 15673 15674 switch (current[0]) { // first letter 15675 15676 case 'l': // lineto, relative 15677 x += current[1]; 15678 y += current[2]; 15679 bounds = [ ]; 15680 break; 15681 15682 case 'L': // lineto, absolute 15683 x = current[1]; 15684 y = current[2]; 15685 bounds = [ ]; 15686 break; 15687 15688 case 'h': // horizontal lineto, relative 15689 x += current[1]; 15690 bounds = [ ]; 15691 break; 15692 15693 case 'H': // horizontal lineto, absolute 15694 x = current[1]; 15695 bounds = [ ]; 15696 break; 15697 15698 case 'v': // vertical lineto, relative 15699 y += current[1]; 15700 bounds = [ ]; 15701 break; 15702 15703 case 'V': // verical lineto, absolute 15704 y = current[1]; 15705 bounds = [ ]; 15706 break; 15707 15708 case 'm': // moveTo, relative 15709 x += current[1]; 15710 y += current[2]; 15711 subpathStartX = x; 15712 subpathStartY = y; 15713 bounds = [ ]; 15714 break; 15715 15716 case 'M': // moveTo, absolute 15717 x = current[1]; 15718 y = current[2]; 15719 subpathStartX = x; 15720 subpathStartY = y; 15721 bounds = [ ]; 15722 break; 15723 15724 case 'c': // bezierCurveTo, relative 15725 tempX = x + current[5]; 15726 tempY = y + current[6]; 15727 controlX = x + current[3]; 15728 controlY = y + current[4]; 15729 bounds = fabric.util.getBoundsOfCurve(x, y, 15730 x + current[1], // x1 15731 y + current[2], // y1 15732 controlX, // x2 15733 controlY, // y2 15734 tempX, 15735 tempY 15736 ); 15737 x = tempX; 15738 y = tempY; 15739 break; 15740 15741 case 'C': // bezierCurveTo, absolute 15742 x = current[5]; 15743 y = current[6]; 15744 controlX = current[3]; 15745 controlY = current[4]; 15746 bounds = fabric.util.getBoundsOfCurve(x, y, 15747 current[1], 15748 current[2], 15749 controlX, 15750 controlY, 15751 x, 15752 y 15753 ); 15754 break; 15755 15756 case 's': // shorthand cubic bezierCurveTo, relative 15757 15758 // transform to absolute x,y 15759 tempX = x + current[3]; 15760 tempY = y + current[4]; 15761 15762 if (previous[0].match(/[CcSs]/) === null) { 15763 // If there is no previous command or if the previous command was not a C, c, S, or s, 15764 // the control point is coincident with the current point 15765 controlX = x; 15766 controlY = y; 15767 } 15768 else { 15769 // calculate reflection of previous control points 15770 controlX = 2 * x - controlX; 15771 controlY = 2 * y - controlY; 15772 } 15773 15774 bounds = fabric.util.getBoundsOfCurve(x, y, 15775 controlX, 15776 controlY, 15777 x + current[1], 15778 y + current[2], 15779 tempX, 15780 tempY 15781 ); 15782 // set control point to 2nd one of this command 15783 // "... the first control point is assumed to be 15784 // the reflection of the second control point on 15785 // the previous command relative to the current point." 15786 controlX = x + current[1]; 15787 controlY = y + current[2]; 15788 x = tempX; 15789 y = tempY; 15790 break; 15791 15792 case 'S': // shorthand cubic bezierCurveTo, absolute 15793 tempX = current[3]; 15794 tempY = current[4]; 15795 if (previous[0].match(/[CcSs]/) === null) { 15796 // If there is no previous command or if the previous command was not a C, c, S, or s, 15797 // the control point is coincident with the current point 15798 controlX = x; 15799 controlY = y; 15800 } 15801 else { 15802 // calculate reflection of previous control points 15803 controlX = 2 * x - controlX; 15804 controlY = 2 * y - controlY; 15805 } 15806 bounds = fabric.util.getBoundsOfCurve(x, y, 15807 controlX, 15808 controlY, 15809 current[1], 15810 current[2], 15811 tempX, 15812 tempY 15813 ); 15814 x = tempX; 15815 y = tempY; 15816 // set control point to 2nd one of this command 15817 // "... the first control point is assumed to be 15818 // the reflection of the second control point on 15819 // the previous command relative to the current point." 15820 controlX = current[1]; 15821 controlY = current[2]; 15822 break; 15823 15824 case 'q': // quadraticCurveTo, relative 15825 // transform to absolute x,y 15826 tempX = x + current[3]; 15827 tempY = y + current[4]; 15828 controlX = x + current[1]; 15829 controlY = y + current[2]; 15830 bounds = fabric.util.getBoundsOfCurve(x, y, 15831 controlX, 15832 controlY, 15833 controlX, 15834 controlY, 15835 tempX, 15836 tempY 15837 ); 15838 x = tempX; 15839 y = tempY; 15840 break; 15841 15842 case 'Q': // quadraticCurveTo, absolute 15843 controlX = current[1]; 15844 controlY = current[2]; 15845 bounds = fabric.util.getBoundsOfCurve(x, y, 15846 controlX, 15847 controlY, 15848 controlX, 15849 controlY, 15850 current[3], 15851 current[4] 15852 ); 15853 x = current[3]; 15854 y = current[4]; 15855 break; 15856 15857 case 't': // shorthand quadraticCurveTo, relative 15858 // transform to absolute x,y 15859 tempX = x + current[1]; 15860 tempY = y + current[2]; 15861 if (previous[0].match(/[QqTt]/) === null) { 15862 // If there is no previous command or if the previous command was not a Q, q, T or t, 15863 // assume the control point is coincident with the current point 15864 controlX = x; 15865 controlY = y; 15866 } 15867 else { 15868 // calculate reflection of previous control point 15869 controlX = 2 * x - controlX; 15870 controlY = 2 * y - controlY; 15871 } 15872 15873 bounds = fabric.util.getBoundsOfCurve(x, y, 15874 controlX, 15875 controlY, 15876 controlX, 15877 controlY, 15878 tempX, 15879 tempY 15880 ); 15881 x = tempX; 15882 y = tempY; 15883 15884 break; 15885 15886 case 'T': 15887 tempX = current[1]; 15888 tempY = current[2]; 15889 15890 if (previous[0].match(/[QqTt]/) === null) { 15891 // If there is no previous command or if the previous command was not a Q, q, T or t, 15892 // assume the control point is coincident with the current point 15893 controlX = x; 15894 controlY = y; 15895 } 15896 else { 15897 // calculate reflection of previous control point 15898 controlX = 2 * x - controlX; 15899 controlY = 2 * y - controlY; 15900 } 15901 bounds = fabric.util.getBoundsOfCurve(x, y, 15902 controlX, 15903 controlY, 15904 controlX, 15905 controlY, 15906 tempX, 15907 tempY 15908 ); 15909 x = tempX; 15910 y = tempY; 15911 break; 15912 15913 case 'a': 15914 // TODO: optimize this 15915 bounds = fabric.util.getBoundsOfArc(x, y, 15916 current[1], 15917 current[2], 15918 current[3], 15919 current[4], 15920 current[5], 15921 current[6] + x, 15922 current[7] + y 15923 ); 15924 x += current[6]; 15925 y += current[7]; 15926 break; 15927 15928 case 'A': 15929 // TODO: optimize this 15930 bounds = fabric.util.getBoundsOfArc(x, y, 15931 current[1], 15932 current[2], 15933 current[3], 15934 current[4], 15935 current[5], 15936 current[6], 15937 current[7] 15938 ); 15939 x = current[6]; 15940 y = current[7]; 15941 break; 15942 15943 case 'z': 15944 case 'Z': 15945 x = subpathStartX; 15946 y = subpathStartY; 15947 break; 15948 } 15949 previous = current; 15950 bounds.forEach(function (point) { 15951 aX.push(point.x); 15952 aY.push(point.y); 15953 }); 15954 aX.push(x); 15955 aY.push(y); 15956 } 15957 15958 var minX = min(aX), 15959 minY = min(aY), 15960 maxX = max(aX), 15961 maxY = max(aY), 15962 deltaX = maxX - minX, 15963 deltaY = maxY - minY, 15964 15965 o = { 15966 left: minX, 15967 top: minY, 15968 width: deltaX, 15969 height: deltaY 15970 }; 15971 15972 return o; 15973 } 15974 }); 15975 15976 /** 15977 * Creates an instance of fabric.Path from an object 15978 * @static 15979 * @memberOf fabric.Path 15980 * @param {Object} object 15981 * @param {Function} callback Callback to invoke when an fabric.Path instance is created 15982 */ 15983 fabric.Path.fromObject = function(object, callback) { 15984 if (typeof object.path === 'string') { 15985 fabric.loadSVGFromURL(object.path, function (elements) { 15986 var path = elements[0], 15987 pathUrl = object.path; 15988 15989 delete object.path; 15990 15991 fabric.util.object.extend(path, object); 15992 path.setSourcePath(pathUrl); 15993 15994 callback(path); 15995 }); 15996 } 15997 else { 15998 callback(new fabric.Path(object.path, object)); 15999 } 16000 }; 16001 16002 /* _FROM_SVG_START_ */ 16003 /** 16004 * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) 16005 * @static 16006 * @memberOf fabric.Path 16007 * @see http://www.w3.org/TR/SVG/paths.html#PathElement 16008 */ 16009 fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); 16010 16011 /** 16012 * Creates an instance of fabric.Path from an SVG <path> element 16013 * @static 16014 * @memberOf fabric.Path 16015 * @param {SVGElement} element to parse 16016 * @param {Function} callback Callback to invoke when an fabric.Path instance is created 16017 * @param {Object} [options] Options object 16018 */ 16019 fabric.Path.fromElement = function(element, callback, options) { 16020 var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); 16021 callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); 16022 }; 16023 /* _FROM_SVG_END_ */ 16024 16025 /** 16026 * Indicates that instances of this type are async 16027 * @static 16028 * @memberOf fabric.Path 16029 * @type Boolean 16030 * @default 16031 */ 16032 fabric.Path.async = true; 16033 16034})(typeof exports !== 'undefined' ? exports : this); 16035 16036 16037(function(global) { 16038 16039 'use strict'; 16040 16041 var fabric = global.fabric || (global.fabric = { }), 16042 extend = fabric.util.object.extend, 16043 invoke = fabric.util.array.invoke, 16044 parentToObject = fabric.Object.prototype.toObject; 16045 16046 if (fabric.PathGroup) { 16047 fabric.warn('fabric.PathGroup is already defined'); 16048 return; 16049 } 16050 16051 /** 16052 * Path group class 16053 * @class fabric.PathGroup 16054 * @extends fabric.Path 16055 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} 16056 * @see {@link fabric.PathGroup#initialize} for constructor definition 16057 */ 16058 fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ { 16059 16060 /** 16061 * Type of an object 16062 * @type String 16063 * @default 16064 */ 16065 type: 'path-group', 16066 16067 /** 16068 * Fill value 16069 * @type String 16070 * @default 16071 */ 16072 fill: '', 16073 16074 /** 16075 * Constructor 16076 * @param {Array} paths 16077 * @param {Object} [options] Options object 16078 * @return {fabric.PathGroup} thisArg 16079 */ 16080 initialize: function(paths, options) { 16081 16082 options = options || { }; 16083 this.paths = paths || [ ]; 16084 16085 for (var i = this.paths.length; i--;) { 16086 this.paths[i].group = this; 16087 } 16088 16089 if (options.toBeParsed) { 16090 this.parseDimensionsFromPaths(options); 16091 delete options.toBeParsed; 16092 } 16093 this.setOptions(options); 16094 this.setCoords(); 16095 16096 if (options.sourcePath) { 16097 this.setSourcePath(options.sourcePath); 16098 } 16099 }, 16100 16101 /** 16102 * Calculate width and height based on paths contained 16103 */ 16104 parseDimensionsFromPaths: function(options) { 16105 var points, p, xC = [ ], yC = [ ], path, height, width, 16106 m = this.transformMatrix; 16107 for (var j = this.paths.length; j--;) { 16108 path = this.paths[j]; 16109 height = path.height + path.strokeWidth; 16110 width = path.width + path.strokeWidth; 16111 points = [ 16112 { x: path.left, y: path.top }, 16113 { x: path.left + width, y: path.top }, 16114 { x: path.left, y: path.top + height }, 16115 { x: path.left + width, y: path.top + height } 16116 ]; 16117 for (var i = 0; i < points.length; i++) { 16118 p = points[i]; 16119 if (m) { 16120 p = fabric.util.transformPoint(p, m, false); 16121 } 16122 xC.push(p.x); 16123 yC.push(p.y); 16124 } 16125 } 16126 options.width = Math.max.apply(null, xC); 16127 options.height = Math.max.apply(null, yC); 16128 }, 16129 16130 /** 16131 * Renders this group on a specified context 16132 * @param {CanvasRenderingContext2D} ctx Context to render this instance on 16133 */ 16134 render: function(ctx) { 16135 // do not render if object is not visible 16136 if (!this.visible) { 16137 return; 16138 } 16139 16140 ctx.save(); 16141 16142 if (this.transformMatrix) { 16143 ctx.transform.apply(ctx, this.transformMatrix); 16144 } 16145 this.transform(ctx); 16146 16147 this._setShadow(ctx); 16148 this.clipTo && fabric.util.clipContext(this, ctx); 16149 ctx.translate(-this.width/2, -this.height/2); 16150 for (var i = 0, l = this.paths.length; i < l; ++i) { 16151 this.paths[i].render(ctx, true); 16152 } 16153 this.clipTo && ctx.restore(); 16154 this._removeShadow(ctx); 16155 ctx.restore(); 16156 }, 16157 16158 /** 16159 * Sets certain property to a certain value 16160 * @param {String} prop 16161 * @param {Any} value 16162 * @return {fabric.PathGroup} thisArg 16163 */ 16164 _set: function(prop, value) { 16165 16166 if (prop === 'fill' && value && this.isSameColor()) { 16167 var i = this.paths.length; 16168 while (i--) { 16169 this.paths[i]._set(prop, value); 16170 } 16171 } 16172 16173 return this.callSuper('_set', prop, value); 16174 }, 16175 16176 /** 16177 * Returns object representation of this path group 16178 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 16179 * @return {Object} object representation of an instance 16180 */ 16181 toObject: function(propertiesToInclude) { 16182 var o = extend(parentToObject.call(this, propertiesToInclude), { 16183 paths: invoke(this.getObjects(), 'toObject', propertiesToInclude) 16184 }); 16185 if (this.sourcePath) { 16186 o.sourcePath = this.sourcePath; 16187 } 16188 return o; 16189 }, 16190 16191 /** 16192 * Returns dataless object representation of this path group 16193 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 16194 * @return {Object} dataless object representation of an instance 16195 */ 16196 toDatalessObject: function(propertiesToInclude) { 16197 var o = this.toObject(propertiesToInclude); 16198 if (this.sourcePath) { 16199 o.paths = this.sourcePath; 16200 } 16201 return o; 16202 }, 16203 16204 /* _TO_SVG_START_ */ 16205 /** 16206 * Returns svg representation of an instance 16207 * @param {Function} [reviver] Method for further parsing of svg representation. 16208 * @return {String} svg representation of an instance 16209 */ 16210 toSVG: function(reviver) { 16211 var objects = this.getObjects(), 16212 p = this.getPointByOrigin('left', 'top'), 16213 translatePart = 'translate(' + p.x + ' ' + p.y + ')', 16214 markup = [ 16215 //jscs:disable validateIndentation 16216 '<g ', 16217 'style="', this.getSvgStyles(), '" ', 16218 'transform="', this.getSvgTransformMatrix(), translatePart, this.getSvgTransform(), '" ', 16219 '>\n' 16220 //jscs:enable validateIndentation 16221 ]; 16222 16223 for (var i = 0, len = objects.length; i < len; i++) { 16224 markup.push(objects[i].toSVG(reviver)); 16225 } 16226 markup.push('</g>\n'); 16227 16228 return reviver ? reviver(markup.join('')) : markup.join(''); 16229 }, 16230 /* _TO_SVG_END_ */ 16231 16232 /** 16233 * Returns a string representation of this path group 16234 * @return {String} string representation of an object 16235 */ 16236 toString: function() { 16237 return '#<fabric.PathGroup (' + this.complexity() + 16238 '): { top: ' + this.top + ', left: ' + this.left + ' }>'; 16239 }, 16240 16241 /** 16242 * Returns true if all paths in this group are of same color 16243 * @return {Boolean} true if all paths are of the same color (`fill`) 16244 */ 16245 isSameColor: function() { 16246 var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase(); 16247 return this.getObjects().every(function(path) { 16248 return (path.get('fill') || '').toLowerCase() === firstPathFill; 16249 }); 16250 }, 16251 16252 /** 16253 * Returns number representation of object's complexity 16254 * @return {Number} complexity 16255 */ 16256 complexity: function() { 16257 return this.paths.reduce(function(total, path) { 16258 return total + ((path && path.complexity) ? path.complexity() : 0); 16259 }, 0); 16260 }, 16261 16262 /** 16263 * Returns all paths in this path group 16264 * @return {Array} array of path objects included in this path group 16265 */ 16266 getObjects: function() { 16267 return this.paths; 16268 } 16269 }); 16270 16271 /** 16272 * Creates fabric.PathGroup instance from an object representation 16273 * @static 16274 * @memberOf fabric.PathGroup 16275 * @param {Object} object Object to create an instance from 16276 * @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created 16277 */ 16278 fabric.PathGroup.fromObject = function(object, callback) { 16279 if (typeof object.paths === 'string') { 16280 fabric.loadSVGFromURL(object.paths, function (elements) { 16281 16282 var pathUrl = object.paths; 16283 delete object.paths; 16284 16285 var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); 16286 16287 callback(pathGroup); 16288 }); 16289 } 16290 else { 16291 fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { 16292 delete object.paths; 16293 callback(new fabric.PathGroup(enlivenedObjects, object)); 16294 }); 16295 } 16296 }; 16297 16298 /** 16299 * Indicates that instances of this type are async 16300 * @static 16301 * @memberOf fabric.PathGroup 16302 * @type Boolean 16303 * @default 16304 */ 16305 fabric.PathGroup.async = true; 16306 16307})(typeof exports !== 'undefined' ? exports : this); 16308 16309 16310(function(global) { 16311 16312 'use strict'; 16313 16314 var fabric = global.fabric || (global.fabric = { }), 16315 extend = fabric.util.object.extend, 16316 min = fabric.util.array.min, 16317 max = fabric.util.array.max, 16318 invoke = fabric.util.array.invoke; 16319 16320 if (fabric.Group) { 16321 return; 16322 } 16323 16324 // lock-related properties, for use in fabric.Group#get 16325 // to enable locking behavior on group 16326 // when one of its objects has lock-related properties set 16327 var _lockProperties = { 16328 lockMovementX: true, 16329 lockMovementY: true, 16330 lockRotation: true, 16331 lockScalingX: true, 16332 lockScalingY: true, 16333 lockUniScaling: true 16334 }; 16335 16336 /** 16337 * Group class 16338 * @class fabric.Group 16339 * @extends fabric.Object 16340 * @mixes fabric.Collection 16341 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups} 16342 * @see {@link fabric.Group#initialize} for constructor definition 16343 */ 16344 fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { 16345 16346 /** 16347 * Type of an object 16348 * @type String 16349 * @default 16350 */ 16351 type: 'group', 16352 16353 /** 16354 * Constructor 16355 * @param {Object} objects Group objects 16356 * @param {Object} [options] Options object 16357 * @return {Object} thisArg 16358 */ 16359 initialize: function(objects, options) { 16360 options = options || { }; 16361 16362 this._objects = objects || []; 16363 for (var i = this._objects.length; i--; ) { 16364 this._objects[i].group = this; 16365 } 16366 16367 this.originalState = { }; 16368 this.callSuper('initialize'); 16369 16370 if (options.originX) { 16371 this.originX = options.originX; 16372 } 16373 16374 if (options.originY) { 16375 this.originY = options.originY; 16376 } 16377 16378 this._calcBounds(); 16379 this._updateObjectsCoords(); 16380 16381 this.callSuper('initialize', options); 16382 16383 this.setCoords(); 16384 this.saveCoords(); 16385 }, 16386 16387 /** 16388 * @private 16389 */ 16390 _updateObjectsCoords: function() { 16391 this.forEachObject(this._updateObjectCoords, this); 16392 }, 16393 16394 /** 16395 * @private 16396 */ 16397 _updateObjectCoords: function(object) { 16398 var objectLeft = object.getLeft(), 16399 objectTop = object.getTop(), 16400 center = this.getCenterPoint(); 16401 16402 object.set({ 16403 originalLeft: objectLeft, 16404 originalTop: objectTop, 16405 left: objectLeft - center.x, 16406 top: objectTop - center.y 16407 }); 16408 16409 object.setCoords(); 16410 16411 // do not display corners of objects enclosed in a group 16412 object.__origHasControls = object.hasControls; 16413 object.hasControls = false; 16414 }, 16415 16416 /** 16417 * Returns string represenation of a group 16418 * @return {String} 16419 */ 16420 toString: function() { 16421 return '#<fabric.Group: (' + this.complexity() + ')>'; 16422 }, 16423 16424 /** 16425 * Adds an object to a group; Then recalculates group's dimension, position. 16426 * @param {Object} object 16427 * @return {fabric.Group} thisArg 16428 * @chainable 16429 */ 16430 addWithUpdate: function(object) { 16431 this._restoreObjectsState(); 16432 if (object) { 16433 this._objects.push(object); 16434 object.group = this; 16435 } 16436 // since _restoreObjectsState set objects inactive 16437 this.forEachObject(this._setObjectActive, this); 16438 this._calcBounds(); 16439 this._updateObjectsCoords(); 16440 return this; 16441 }, 16442 16443 /** 16444 * @private 16445 */ 16446 _setObjectActive: function(object) { 16447 object.set('active', true); 16448 object.group = this; 16449 }, 16450 16451 /** 16452 * Removes an object from a group; Then recalculates group's dimension, position. 16453 * @param {Object} object 16454 * @return {fabric.Group} thisArg 16455 * @chainable 16456 */ 16457 removeWithUpdate: function(object) { 16458 this._moveFlippedObject(object); 16459 this._restoreObjectsState(); 16460 16461 // since _restoreObjectsState set objects inactive 16462 this.forEachObject(this._setObjectActive, this); 16463 16464 this.remove(object); 16465 this._calcBounds(); 16466 this._updateObjectsCoords(); 16467 16468 return this; 16469 }, 16470 16471 /** 16472 * @private 16473 */ 16474 _onObjectAdded: function(object) { 16475 object.group = this; 16476 }, 16477 16478 /** 16479 * @private 16480 */ 16481 _onObjectRemoved: function(object) { 16482 delete object.group; 16483 object.set('active', false); 16484 }, 16485 16486 /** 16487 * Properties that are delegated to group objects when reading/writing 16488 * @param {Object} delegatedProperties 16489 */ 16490 delegatedProperties: { 16491 fill: true, 16492 opacity: true, 16493 fontFamily: true, 16494 fontWeight: true, 16495 fontSize: true, 16496 fontStyle: true, 16497 lineHeight: true, 16498 textDecoration: true, 16499 textAlign: true, 16500 backgroundColor: true 16501 }, 16502 16503 /** 16504 * @private 16505 */ 16506 _set: function(key, value) { 16507 if (key in this.delegatedProperties) { 16508 var i = this._objects.length; 16509 while (i--) { 16510 this._objects[i].set(key, value); 16511 } 16512 } 16513 this.callSuper('_set', key, value); 16514 }, 16515 16516 /** 16517 * Returns object representation of an instance 16518 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 16519 * @return {Object} object representation of an instance 16520 */ 16521 toObject: function(propertiesToInclude) { 16522 return extend(this.callSuper('toObject', propertiesToInclude), { 16523 objects: invoke(this._objects, 'toObject', propertiesToInclude) 16524 }); 16525 }, 16526 16527 /** 16528 * Renders instance on a given context 16529 * @param {CanvasRenderingContext2D} ctx context to render instance on 16530 */ 16531 render: function(ctx) { 16532 // do not render if object is not visible 16533 if (!this.visible) { 16534 return; 16535 } 16536 16537 ctx.save(); 16538 this.clipTo && fabric.util.clipContext(this, ctx); 16539 this.transform(ctx); 16540 // the array is now sorted in order of highest first, so start from end 16541 for (var i = 0, len = this._objects.length; i < len; i++) { 16542 this._renderObject(this._objects[i], ctx); 16543 } 16544 16545 this.clipTo && ctx.restore(); 16546 16547 ctx.restore(); 16548 }, 16549 16550 /** 16551 * Renders controls and borders for the object 16552 * @param {CanvasRenderingContext2D} ctx Context to render on 16553 * @param {Boolean} [noTransform] When true, context is not transformed 16554 */ 16555 _renderControls: function(ctx, noTransform) { 16556 this.callSuper('_renderControls', ctx, noTransform); 16557 for (var i = 0, len = this._objects.length; i < len; i++) { 16558 this._objects[i]._renderControls(ctx); 16559 } 16560 }, 16561 16562 /** 16563 * @private 16564 */ 16565 _renderObject: function(object, ctx) { 16566 var originalHasRotatingPoint = object.hasRotatingPoint; 16567 16568 // do not render if object is not visible 16569 if (!object.visible) { 16570 return; 16571 } 16572 16573 object.hasRotatingPoint = false; 16574 16575 object.render(ctx); 16576 16577 object.hasRotatingPoint = originalHasRotatingPoint; 16578 }, 16579 16580 /** 16581 * Retores original state of each of group objects (original state is that which was before group was created). 16582 * @private 16583 * @return {fabric.Group} thisArg 16584 * @chainable 16585 */ 16586 _restoreObjectsState: function() { 16587 this._objects.forEach(this._restoreObjectState, this); 16588 return this; 16589 }, 16590 16591 /** 16592 * Realises the transform from this group onto the supplied object 16593 * i.e. it tells you what would happen if the supplied object was in 16594 * the group, and then the group was destroyed. It mutates the supplied 16595 * object. 16596 * @param {fabric.Object} object 16597 * @return {fabric.Object} transformedObject 16598 */ 16599 realizeTransform: function(object) { 16600 this._moveFlippedObject(object); 16601 this._setObjectPosition(object); 16602 return object; 16603 }, 16604 /** 16605 * Moves a flipped object to the position where it's displayed 16606 * @private 16607 * @param {fabric.Object} object 16608 * @return {fabric.Group} thisArg 16609 */ 16610 _moveFlippedObject: function(object) { 16611 var oldOriginX = object.get('originX'), 16612 oldOriginY = object.get('originY'), 16613 center = object.getCenterPoint(); 16614 16615 object.set({ 16616 originX: 'center', 16617 originY: 'center', 16618 left: center.x, 16619 top: center.y 16620 }); 16621 16622 this._toggleFlipping(object); 16623 16624 var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY); 16625 16626 object.set({ 16627 originX: oldOriginX, 16628 originY: oldOriginY, 16629 left: newOrigin.x, 16630 top: newOrigin.y 16631 }); 16632 16633 return this; 16634 }, 16635 16636 /** 16637 * @private 16638 */ 16639 _toggleFlipping: function(object) { 16640 if (this.flipX) { 16641 object.toggle('flipX'); 16642 object.set('left', -object.get('left')); 16643 object.setAngle(-object.getAngle()); 16644 } 16645 if (this.flipY) { 16646 object.toggle('flipY'); 16647 object.set('top', -object.get('top')); 16648 object.setAngle(-object.getAngle()); 16649 } 16650 }, 16651 16652 /** 16653 * Restores original state of a specified object in group 16654 * @private 16655 * @param {fabric.Object} object 16656 * @return {fabric.Group} thisArg 16657 */ 16658 _restoreObjectState: function(object) { 16659 this._setObjectPosition(object); 16660 16661 object.setCoords(); 16662 object.hasControls = object.__origHasControls; 16663 delete object.__origHasControls; 16664 object.set('active', false); 16665 object.setCoords(); 16666 delete object.group; 16667 16668 return this; 16669 }, 16670 16671 /** 16672 * @private 16673 */ 16674 _setObjectPosition: function(object) { 16675 var center = this.getCenterPoint(), 16676 rotated = this._getRotatedLeftTop(object); 16677 16678 object.set({ 16679 angle: object.getAngle() + this.getAngle(), 16680 left: center.x + rotated.left, 16681 top: center.y + rotated.top, 16682 scaleX: object.get('scaleX') * this.get('scaleX'), 16683 scaleY: object.get('scaleY') * this.get('scaleY') 16684 }); 16685 }, 16686 16687 /** 16688 * @private 16689 */ 16690 _getRotatedLeftTop: function(object) { 16691 var groupAngle = this.getAngle() * (Math.PI / 180); 16692 return { 16693 left: (-Math.sin(groupAngle) * object.getTop() * this.get('scaleY') + 16694 Math.cos(groupAngle) * object.getLeft() * this.get('scaleX')), 16695 16696 top: (Math.cos(groupAngle) * object.getTop() * this.get('scaleY') + 16697 Math.sin(groupAngle) * object.getLeft() * this.get('scaleX')) 16698 }; 16699 }, 16700 16701 /** 16702 * Destroys a group (restoring state of its objects) 16703 * @return {fabric.Group} thisArg 16704 * @chainable 16705 */ 16706 destroy: function() { 16707 this._objects.forEach(this._moveFlippedObject, this); 16708 return this._restoreObjectsState(); 16709 }, 16710 16711 /** 16712 * Saves coordinates of this instance (to be used together with `hasMoved`) 16713 * @saveCoords 16714 * @return {fabric.Group} thisArg 16715 * @chainable 16716 */ 16717 saveCoords: function() { 16718 this._originalLeft = this.get('left'); 16719 this._originalTop = this.get('top'); 16720 return this; 16721 }, 16722 16723 /** 16724 * Checks whether this group was moved (since `saveCoords` was called last) 16725 * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called) 16726 */ 16727 hasMoved: function() { 16728 return this._originalLeft !== this.get('left') || 16729 this._originalTop !== this.get('top'); 16730 }, 16731 16732 /** 16733 * Sets coordinates of all group objects 16734 * @return {fabric.Group} thisArg 16735 * @chainable 16736 */ 16737 setObjectsCoords: function() { 16738 this.forEachObject(function(object) { 16739 object.setCoords(); 16740 }); 16741 return this; 16742 }, 16743 16744 /** 16745 * @private 16746 */ 16747 _calcBounds: function(onlyWidthHeight) { 16748 var aX = [], 16749 aY = [], 16750 o, prop, 16751 props = ['tr', 'br', 'bl', 'tl']; 16752 16753 for (var i = 0, len = this._objects.length; i < len; ++i) { 16754 o = this._objects[i]; 16755 o.setCoords(); 16756 for (var j = 0; j < props.length; j++) { 16757 prop = props[j]; 16758 aX.push(o.oCoords[prop].x); 16759 aY.push(o.oCoords[prop].y); 16760 } 16761 } 16762 16763 this.set(this._getBounds(aX, aY, onlyWidthHeight)); 16764 }, 16765 16766 /** 16767 * @private 16768 */ 16769 _getBounds: function(aX, aY, onlyWidthHeight) { 16770 var ivt = fabric.util.invertTransform(this.getViewportTransform()), 16771 minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), 16772 maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), 16773 obj = { 16774 width: (maxXY.x - minXY.x) || 0, 16775 height: (maxXY.y - minXY.y) || 0 16776 }; 16777 16778 if (!onlyWidthHeight) { 16779 obj.left = minXY.x || 0; 16780 obj.top = minXY.y || 0; 16781 if (this.originX === 'center') { 16782 obj.left += obj.width / 2; 16783 } 16784 if (this.originX === 'right') { 16785 obj.left += obj.width; 16786 } 16787 if (this.originY === 'center') { 16788 obj.top += obj.height / 2; 16789 } 16790 if (this.originY === 'bottom') { 16791 obj.top += obj.height; 16792 } 16793 } 16794 return obj; 16795 }, 16796 16797 /* _TO_SVG_START_ */ 16798 /** 16799 * Returns svg representation of an instance 16800 * @param {Function} [reviver] Method for further parsing of svg representation. 16801 * @return {String} svg representation of an instance 16802 */ 16803 toSVG: function(reviver) { 16804 var markup = [ 16805 //jscs:disable validateIndentation 16806 '<g ', 16807 'transform="', this.getSvgTransform(), 16808 '">\n' 16809 //jscs:enable validateIndentation 16810 ]; 16811 16812 for (var i = 0, len = this._objects.length; i < len; i++) { 16813 markup.push(this._objects[i].toSVG(reviver)); 16814 } 16815 16816 markup.push('</g>\n'); 16817 16818 return reviver ? reviver(markup.join('')) : markup.join(''); 16819 }, 16820 /* _TO_SVG_END_ */ 16821 16822 /** 16823 * Returns requested property 16824 * @param {String} prop Property to get 16825 * @return {Any} 16826 */ 16827 get: function(prop) { 16828 if (prop in _lockProperties) { 16829 if (this[prop]) { 16830 return this[prop]; 16831 } 16832 else { 16833 for (var i = 0, len = this._objects.length; i < len; i++) { 16834 if (this._objects[i][prop]) { 16835 return true; 16836 } 16837 } 16838 return false; 16839 } 16840 } 16841 else { 16842 if (prop in this.delegatedProperties) { 16843 return this._objects[0] && this._objects[0].get(prop); 16844 } 16845 return this[prop]; 16846 } 16847 } 16848 }); 16849 16850 /** 16851 * Returns {@link fabric.Group} instance from an object representation 16852 * @static 16853 * @memberOf fabric.Group 16854 * @param {Object} object Object to create a group from 16855 * @param {Function} [callback] Callback to invoke when an group instance is created 16856 * @return {fabric.Group} An instance of fabric.Group 16857 */ 16858 fabric.Group.fromObject = function(object, callback) { 16859 fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { 16860 delete object.objects; 16861 callback && callback(new fabric.Group(enlivenedObjects, object)); 16862 }); 16863 }; 16864 16865 /** 16866 * Indicates that instances of this type are async 16867 * @static 16868 * @memberOf fabric.Group 16869 * @type Boolean 16870 * @default 16871 */ 16872 fabric.Group.async = true; 16873 16874})(typeof exports !== 'undefined' ? exports : this); 16875 16876 16877(function(global) { 16878 16879 'use strict'; 16880 16881 var extend = fabric.util.object.extend; 16882 16883 if (!global.fabric) { 16884 global.fabric = { }; 16885 } 16886 16887 if (global.fabric.Image) { 16888 fabric.warn('fabric.Image is already defined.'); 16889 return; 16890 } 16891 16892 /** 16893 * Image class 16894 * @class fabric.Image 16895 * @extends fabric.Object 16896 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images} 16897 * @see {@link fabric.Image#initialize} for constructor definition 16898 */ 16899 fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { 16900 16901 /** 16902 * Type of an object 16903 * @type String 16904 * @default 16905 */ 16906 type: 'image', 16907 16908 /** 16909 * crossOrigin value (one of "", "anonymous", "allow-credentials") 16910 * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes 16911 * @type String 16912 * @default 16913 */ 16914 crossOrigin: '', 16915 16916 /** 16917 * AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max") 16918 * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute 16919 * This parameter defines how the picture is aligned to its viewport when image element width differs from image width. 16920 * @type String 16921 * @default 16922 */ 16923 alignX: 'none', 16924 16925 /** 16926 * AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max") 16927 * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute 16928 * This parameter defines how the picture is aligned to its viewport when image element height differs from image height. 16929 * @type String 16930 * @default 16931 */ 16932 alignY: 'none', 16933 16934 /** 16935 * meetOrSlice value, part of preserveAspectRatio (one of "meet", "slice"). 16936 * if meet the image is always fully visibile, if slice the viewport is always filled with image. 16937 * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute 16938 * @type String 16939 * @default 16940 */ 16941 meetOrSlice: 'meet', 16942 16943 /** 16944 * private 16945 * contains last value of scaleX to detect 16946 * if the Image got resized after the last Render 16947 * @type Number 16948 */ 16949 _lastScaleX: 1, 16950 16951 /** 16952 * private 16953 * contains last value of scaleY to detect 16954 * if the Image got resized after the last Render 16955 * @type Number 16956 */ 16957 _lastScaleY: 1, 16958 16959 /** 16960 * Constructor 16961 * @param {HTMLImageElement | String} element Image element 16962 * @param {Object} [options] Options object 16963 * @return {fabric.Image} thisArg 16964 */ 16965 initialize: function(element, options) { 16966 options || (options = { }); 16967 16968 this.filters = [ ]; 16969 this.resizeFilters = [ ]; 16970 this.callSuper('initialize', options); 16971 16972 this._initElement(element, options); 16973 this._initConfig(options); 16974 16975 if (options.filters) { 16976 this.filters = options.filters; 16977 this.applyFilters(); 16978 } 16979 }, 16980 16981 /** 16982 * Returns image element which this instance if based on 16983 * @return {HTMLImageElement} Image element 16984 */ 16985 getElement: function() { 16986 return this._element; 16987 }, 16988 16989 /** 16990 * Sets image element for this instance to a specified one. 16991 * If filters defined they are applied to new image. 16992 * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. 16993 * @param {HTMLImageElement} element 16994 * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated 16995 * @param {Object} [options] Options object 16996 * @return {fabric.Image} thisArg 16997 * @chainable 16998 */ 16999 setElement: function(element, callback, options) { 17000 this._element = element; 17001 this._originalElement = element; 17002 this._initConfig(options); 17003 17004 if (this.filters.length !== 0) { 17005 this.applyFilters(callback); 17006 } 17007 else if (callback) { 17008 callback(); 17009 } 17010 17011 return this; 17012 }, 17013 17014 /** 17015 * Sets crossOrigin value (on an instance and corresponding image element) 17016 * @return {fabric.Image} thisArg 17017 * @chainable 17018 */ 17019 setCrossOrigin: function(value) { 17020 this.crossOrigin = value; 17021 this._element.crossOrigin = value; 17022 17023 return this; 17024 }, 17025 17026 /** 17027 * Returns original size of an image 17028 * @return {Object} Object with "width" and "height" properties 17029 */ 17030 getOriginalSize: function() { 17031 var element = this.getElement(); 17032 return { 17033 width: element.width, 17034 height: element.height 17035 }; 17036 }, 17037 17038 /** 17039 * @private 17040 * @param {CanvasRenderingContext2D} ctx Context to render on 17041 */ 17042 _stroke: function(ctx) { 17043 ctx.save(); 17044 this._setStrokeStyles(ctx); 17045 ctx.beginPath(); 17046 ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); 17047 ctx.closePath(); 17048 ctx.restore(); 17049 }, 17050 17051 /** 17052 * @private 17053 * @param {CanvasRenderingContext2D} ctx Context to render on 17054 */ 17055 _renderDashedStroke: function(ctx) { 17056 var x = -this.width / 2, 17057 y = -this.height / 2, 17058 w = this.width, 17059 h = this.height; 17060 17061 ctx.save(); 17062 this._setStrokeStyles(ctx); 17063 17064 ctx.beginPath(); 17065 fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); 17066 fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); 17067 fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); 17068 fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); 17069 ctx.closePath(); 17070 ctx.restore(); 17071 }, 17072 17073 /** 17074 * Returns object representation of an instance 17075 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 17076 * @return {Object} Object representation of an instance 17077 */ 17078 toObject: function(propertiesToInclude) { 17079 return extend(this.callSuper('toObject', propertiesToInclude), { 17080 src: this._originalElement.src || this._originalElement._src, 17081 filters: this.filters.map(function(filterObj) { 17082 return filterObj && filterObj.toObject(); 17083 }), 17084 crossOrigin: this.crossOrigin, 17085 alignX: this.alignX, 17086 alignY: this.alignY, 17087 meetOrSlice: this.meetOrSlice 17088 }); 17089 }, 17090 17091 /* _TO_SVG_START_ */ 17092 /** 17093 * Returns SVG representation of an instance 17094 * @param {Function} [reviver] Method for further parsing of svg representation. 17095 * @return {String} svg representation of an instance 17096 */ 17097 toSVG: function(reviver) { 17098 var markup = [], x = -this.width / 2, y = -this.height / 2, 17099 preserveAspectRatio = 'none'; 17100 if (this.group && this.group.type === 'path-group') { 17101 x = this.left; 17102 y = this.top; 17103 } 17104 if (this.alignX !== 'none' && this.alignY !== 'none') { 17105 preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice; 17106 } 17107 markup.push( 17108 '<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n', 17109 '<image xlink:href="', this.getSvgSrc(), 17110 '" x="', x, '" y="', y, 17111 '" style="', this.getSvgStyles(), 17112 // we're essentially moving origin of transformation from top/left corner to the center of the shape 17113 // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left 17114 // so that object's center aligns with container's left/top 17115 '" width="', this.width, 17116 '" height="', this.height, 17117 '" preserveAspectRatio="', preserveAspectRatio, '"', 17118 '></image>\n' 17119 ); 17120 17121 if (this.stroke || this.strokeDashArray) { 17122 var origFill = this.fill; 17123 this.fill = null; 17124 markup.push( 17125 '<rect ', 17126 'x="', x, '" y="', y, 17127 '" width="', this.width, '" height="', this.height, 17128 '" style="', this.getSvgStyles(), 17129 '"/>\n' 17130 ); 17131 this.fill = origFill; 17132 } 17133 17134 markup.push('</g>\n'); 17135 17136 return reviver ? reviver(markup.join('')) : markup.join(''); 17137 }, 17138 /* _TO_SVG_END_ */ 17139 17140 /** 17141 * Returns source of an image 17142 * @return {String} Source of an image 17143 */ 17144 getSrc: function() { 17145 if (this.getElement()) { 17146 return this.getElement().src || this.getElement()._src; 17147 } 17148 }, 17149 17150 /** 17151 * Sets source of an image 17152 * @param {String} src Source string (URL) 17153 * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied) 17154 * @param {Object} [options] Options object 17155 * @return {fabric.Image} thisArg 17156 * @chainable 17157 */ 17158 setSrc: function(src, callback, options) { 17159 fabric.util.loadImage(src, function(img) { 17160 return this.setElement(img, callback, options); 17161 }, this, options && options.crossOrigin); 17162 }, 17163 17164 /** 17165 * Returns string representation of an instance 17166 * @return {String} String representation of an instance 17167 */ 17168 toString: function() { 17169 return '#<fabric.Image: { src: "' + this.getSrc() + '" }>'; 17170 }, 17171 17172 /** 17173 * Returns a clone of an instance 17174 * @param {Function} callback Callback is invoked with a clone as a first argument 17175 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 17176 */ 17177 clone: function(callback, propertiesToInclude) { 17178 this.constructor.fromObject(this.toObject(propertiesToInclude), callback); 17179 }, 17180 17181 /** 17182 * Applies filters assigned to this image (from "filters" array) 17183 * @method applyFilters 17184 * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated 17185 * @return {fabric.Image} thisArg 17186 * @chainable 17187 */ 17188 applyFilters: function(callback, filters, imgElement, forResizing) { 17189 17190 filters = filters || this.filters; 17191 imgElement = imgElement || this._originalElement; 17192 17193 if (!imgElement) { 17194 return; 17195 } 17196 17197 var imgEl = imgElement, 17198 canvasEl = fabric.util.createCanvasElement(), 17199 replacement = fabric.util.createImage(), 17200 _this = this; 17201 17202 canvasEl.width = imgEl.width; 17203 canvasEl.height = imgEl.height; 17204 canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); 17205 17206 if (filters.length === 0) { 17207 this._element = imgElement; 17208 callback && callback(); 17209 return canvasEl; 17210 } 17211 filters.forEach(function(filter) { 17212 filter && filter.applyTo(canvasEl, filter.scaleX || _this.scaleX, filter.scaleY || _this.scaleY); 17213 if (!forResizing && filter && filter.type === 'Resize') { 17214 _this.width *= filter.scaleX; 17215 _this.height *= filter.scaleY; 17216 } 17217 }); 17218 17219 /** @ignore */ 17220 replacement.width = canvasEl.width; 17221 replacement.height = canvasEl.height; 17222 17223 if (fabric.isLikelyNode) { 17224 replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); 17225 // onload doesn't fire in some node versions, so we invoke callback manually 17226 _this._element = replacement; 17227 !forResizing && (_this._filteredEl = replacement); 17228 callback && callback(); 17229 } 17230 else { 17231 replacement.onload = function() { 17232 _this._element = replacement; 17233 !forResizing && (_this._filteredEl = replacement); 17234 callback && callback(); 17235 replacement.onload = canvasEl = imgEl = null; 17236 }; 17237 replacement.src = canvasEl.toDataURL('image/png'); 17238 } 17239 return canvasEl; 17240 }, 17241 /** 17242 * @private 17243 * @param {CanvasRenderingContext2D} ctx Context to render on 17244 */ 17245 _render: function(ctx, noTransform) { 17246 var x, y, imageMargins = this._findMargins(), elementToDraw; 17247 17248 x = (noTransform ? this.left : -this.width / 2); 17249 y = (noTransform ? this.top : -this.height / 2); 17250 17251 if (this.meetOrSlice === 'slice') { 17252 ctx.beginPath(); 17253 ctx.rect(x, y, this.width, this.height); 17254 ctx.clip(); 17255 } 17256 17257 if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) { 17258 this._lastScaleX = this.scaleX; 17259 this._lastScaleY = this.scaleY; 17260 elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true); 17261 } 17262 else { 17263 elementToDraw = this._element; 17264 } 17265 elementToDraw && ctx.drawImage(elementToDraw, 17266 x + imageMargins.marginX, 17267 y + imageMargins.marginY, 17268 imageMargins.width, 17269 imageMargins.height 17270 ); 17271 17272 this._renderStroke(ctx); 17273 }, 17274 /** 17275 * @private, needed to check if image needs resize 17276 */ 17277 _needsResize: function() { 17278 return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY); 17279 }, 17280 17281 /** 17282 * @private 17283 */ 17284 _findMargins: function() { 17285 var width = this.width, height = this.height, scales, 17286 scale, marginX = 0, marginY = 0; 17287 17288 if (this.alignX !== 'none' || this.alignY !== 'none') { 17289 scales = [this.width / this._element.width, this.height / this._element.height]; 17290 scale = this.meetOrSlice === 'meet' 17291 ? Math.min.apply(null, scales) : Math.max.apply(null, scales); 17292 width = this._element.width * scale; 17293 height = this._element.height * scale; 17294 if (this.alignX === 'Mid') { 17295 marginX = (this.width - width) / 2; 17296 } 17297 if (this.alignX === 'Max') { 17298 marginX = this.width - width; 17299 } 17300 if (this.alignY === 'Mid') { 17301 marginY = (this.height - height) / 2; 17302 } 17303 if (this.alignY === 'Max') { 17304 marginY = this.height - height; 17305 } 17306 } 17307 return { 17308 width: width, 17309 height: height, 17310 marginX: marginX, 17311 marginY: marginY 17312 }; 17313 }, 17314 17315 /** 17316 * @private 17317 */ 17318 _resetWidthHeight: function() { 17319 var element = this.getElement(); 17320 17321 this.set('width', element.width); 17322 this.set('height', element.height); 17323 }, 17324 17325 /** 17326 * The Image class's initialization method. This method is automatically 17327 * called by the constructor. 17328 * @private 17329 * @param {HTMLImageElement|String} element The element representing the image 17330 */ 17331 _initElement: function(element) { 17332 this.setElement(fabric.util.getById(element)); 17333 fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); 17334 }, 17335 17336 /** 17337 * @private 17338 * @param {Object} [options] Options object 17339 */ 17340 _initConfig: function(options) { 17341 options || (options = { }); 17342 this.setOptions(options); 17343 this._setWidthHeight(options); 17344 if (this._element && this.crossOrigin) { 17345 this._element.crossOrigin = this.crossOrigin; 17346 } 17347 }, 17348 17349 /** 17350 * @private 17351 * @param {Object} object Object with filters property 17352 * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created 17353 */ 17354 _initFilters: function(object, callback) { 17355 if (object.filters && object.filters.length) { 17356 fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { 17357 callback && callback(enlivenedObjects); 17358 }, 'fabric.Image.filters'); 17359 } 17360 else { 17361 callback && callback(); 17362 } 17363 }, 17364 17365 /** 17366 * @private 17367 * @param {Object} [options] Object with width/height properties 17368 */ 17369 _setWidthHeight: function(options) { 17370 this.width = 'width' in options 17371 ? options.width 17372 : (this.getElement() 17373 ? this.getElement().width || 0 17374 : 0); 17375 17376 this.height = 'height' in options 17377 ? options.height 17378 : (this.getElement() 17379 ? this.getElement().height || 0 17380 : 0); 17381 }, 17382 17383 /** 17384 * Returns complexity of an instance 17385 * @return {Number} complexity of this instance 17386 */ 17387 complexity: function() { 17388 return 1; 17389 } 17390 }); 17391 17392 /** 17393 * Default CSS class name for canvas 17394 * @static 17395 * @type String 17396 * @default 17397 */ 17398 fabric.Image.CSS_CANVAS = 'canvas-img'; 17399 17400 /** 17401 * Alias for getSrc 17402 * @static 17403 */ 17404 fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; 17405 17406 /** 17407 * Creates an instance of fabric.Image from its object representation 17408 * @static 17409 * @param {Object} object Object to create an instance from 17410 * @param {Function} [callback] Callback to invoke when an image instance is created 17411 */ 17412 fabric.Image.fromObject = function(object, callback) { 17413 fabric.util.loadImage(object.src, function(img) { 17414 fabric.Image.prototype._initFilters.call(object, object, function(filters) { 17415 object.filters = filters || [ ]; 17416 var instance = new fabric.Image(img, object); 17417 callback && callback(instance); 17418 }); 17419 }, null, object.crossOrigin); 17420 }; 17421 17422 /** 17423 * Creates an instance of fabric.Image from an URL string 17424 * @static 17425 * @param {String} url URL to create an image from 17426 * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) 17427 * @param {Object} [imgOptions] Options object 17428 */ 17429 fabric.Image.fromURL = function(url, callback, imgOptions) { 17430 fabric.util.loadImage(url, function(img) { 17431 callback && callback(new fabric.Image(img, imgOptions)); 17432 }, null, imgOptions && imgOptions.crossOrigin); 17433 }; 17434 17435 /* _FROM_SVG_START_ */ 17436 /** 17437 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) 17438 * @static 17439 * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} 17440 */ 17441 fabric.Image.ATTRIBUTE_NAMES = 17442 fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href'.split(' ')); 17443 17444 /** 17445 * Returns {@link fabric.Image} instance from an SVG element 17446 * @static 17447 * @param {SVGElement} element Element to parse 17448 * @param {Function} callback Callback to execute when fabric.Image object is created 17449 * @param {Object} [options] Options object 17450 * @return {fabric.Image} Instance of fabric.Image 17451 */ 17452 fabric.Image.fromElement = function(element, callback, options) { 17453 var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES), 17454 align = 'xMidYMid', meetOrSlice = 'meet', alignX, alignY, aspectRatioAttrs; 17455 17456 if (parsedAttributes.preserveAspectRatio) { 17457 aspectRatioAttrs = parsedAttributes.preserveAspectRatio.split(' '); 17458 } 17459 17460 if (aspectRatioAttrs && aspectRatioAttrs.length) { 17461 meetOrSlice = aspectRatioAttrs.pop(); 17462 if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') { 17463 align = meetOrSlice; 17464 meetOrSlice = 'meet'; 17465 } 17466 else if (aspectRatioAttrs.length) { 17467 align = aspectRatioAttrs.pop(); 17468 } 17469 } 17470 //divide align in alignX and alignY 17471 alignX = align !== 'none' ? align.slice(1, 4) : 'none'; 17472 alignY = align !== 'none' ? align.slice(5, 8) : 'none'; 17473 parsedAttributes.alignX = alignX; 17474 parsedAttributes.alignY = alignY; 17475 parsedAttributes.meetOrSlice = meetOrSlice; 17476 fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, 17477 extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); 17478 }; 17479 /* _FROM_SVG_END_ */ 17480 17481 /** 17482 * Indicates that instances of this type are async 17483 * @static 17484 * @type Boolean 17485 * @default 17486 */ 17487 fabric.Image.async = true; 17488 17489 /** 17490 * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 17491 * @static 17492 * @type Number 17493 * @default 17494 */ 17495 fabric.Image.pngCompression = 1; 17496 17497})(typeof exports !== 'undefined' ? exports : this); 17498 17499 17500fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { 17501 17502 /** 17503 * @private 17504 * @return {Number} angle value 17505 */ 17506 _getAngleValueForStraighten: function() { 17507 var angle = this.getAngle() % 360; 17508 if (angle > 0) { 17509 return Math.round((angle - 1) / 90) * 90; 17510 } 17511 return Math.round(angle / 90) * 90; 17512 }, 17513 17514 /** 17515 * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer) 17516 * @return {fabric.Object} thisArg 17517 * @chainable 17518 */ 17519 straighten: function() { 17520 this.setAngle(this._getAngleValueForStraighten()); 17521 return this; 17522 }, 17523 17524 /** 17525 * Same as {@link fabric.Object.prototype.straighten} but with animation 17526 * @param {Object} callbacks Object with callback functions 17527 * @param {Function} [callbacks.onComplete] Invoked on completion 17528 * @param {Function} [callbacks.onChange] Invoked on every step of animation 17529 * @return {fabric.Object} thisArg 17530 * @chainable 17531 */ 17532 fxStraighten: function(callbacks) { 17533 callbacks = callbacks || { }; 17534 17535 var empty = function() { }, 17536 onComplete = callbacks.onComplete || empty, 17537 onChange = callbacks.onChange || empty, 17538 _this = this; 17539 17540 fabric.util.animate({ 17541 startValue: this.get('angle'), 17542 endValue: this._getAngleValueForStraighten(), 17543 duration: this.FX_DURATION, 17544 onChange: function(value) { 17545 _this.setAngle(value); 17546 onChange(); 17547 }, 17548 onComplete: function() { 17549 _this.setCoords(); 17550 onComplete(); 17551 }, 17552 onStart: function() { 17553 _this.set('active', false); 17554 } 17555 }); 17556 17557 return this; 17558 } 17559}); 17560 17561fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { 17562 17563 /** 17564 * Straightens object, then rerenders canvas 17565 * @param {fabric.Object} object Object to straighten 17566 * @return {fabric.Canvas} thisArg 17567 * @chainable 17568 */ 17569 straightenObject: function (object) { 17570 object.straighten(); 17571 this.renderAll(); 17572 return this; 17573 }, 17574 17575 /** 17576 * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated 17577 * @param {fabric.Object} object Object to straighten 17578 * @return {fabric.Canvas} thisArg 17579 * @chainable 17580 */ 17581 fxStraightenObject: function (object) { 17582 object.fxStraighten({ 17583 onChange: this.renderAll.bind(this) 17584 }); 17585 return this; 17586 } 17587}); 17588 17589 17590/** 17591 * @namespace fabric.Image.filters 17592 * @memberOf fabric.Image 17593 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters} 17594 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 17595 */ 17596fabric.Image.filters = fabric.Image.filters || { }; 17597 17598/** 17599 * Root filter class from which all filter classes inherit from 17600 * @class fabric.Image.filters.BaseFilter 17601 * @memberOf fabric.Image.filters 17602 */ 17603fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { 17604 17605 /** 17606 * Filter type 17607 * @param {String} type 17608 * @default 17609 */ 17610 type: 'BaseFilter', 17611 17612 /** 17613 * Constructor 17614 * @param {Object} [options] Options object 17615 */ 17616 initialize: function(options) { 17617 if (options) { 17618 this.setOptions(options); 17619 } 17620 }, 17621 17622 /** 17623 * Sets filter's properties from options 17624 * @param {Object} [options] Options object 17625 */ 17626 setOptions: function(options) { 17627 for (var prop in options) { 17628 this[prop] = options[prop]; 17629 } 17630 }, 17631 17632 /** 17633 * Returns object representation of an instance 17634 * @return {Object} Object representation of an instance 17635 */ 17636 toObject: function() { 17637 return { type: this.type }; 17638 }, 17639 17640 /** 17641 * Returns a JSON representation of an instance 17642 * @return {Object} JSON 17643 */ 17644 toJSON: function() { 17645 // delegate, not alias 17646 return this.toObject(); 17647 } 17648}); 17649 17650 17651(function(global) { 17652 17653 'use strict'; 17654 17655 var fabric = global.fabric || (global.fabric = { }), 17656 extend = fabric.util.object.extend; 17657 17658 /** 17659 * Brightness filter class 17660 * @class fabric.Image.filters.Brightness 17661 * @memberOf fabric.Image.filters 17662 * @extends fabric.Image.filters.BaseFilter 17663 * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition 17664 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 17665 * @example 17666 * var filter = new fabric.Image.filters.Brightness({ 17667 * brightness: 200 17668 * }); 17669 * object.filters.push(filter); 17670 * object.applyFilters(canvas.renderAll.bind(canvas)); 17671 */ 17672 fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { 17673 17674 /** 17675 * Filter type 17676 * @param {String} type 17677 * @default 17678 */ 17679 type: 'Brightness', 17680 17681 /** 17682 * Constructor 17683 * @memberOf fabric.Image.filters.Brightness.prototype 17684 * @param {Object} [options] Options object 17685 * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) 17686 */ 17687 initialize: function(options) { 17688 options = options || { }; 17689 this.brightness = options.brightness || 0; 17690 }, 17691 17692 /** 17693 * Applies filter to canvas element 17694 * @param {Object} canvasEl Canvas element to apply filter to 17695 */ 17696 applyTo: function(canvasEl) { 17697 var context = canvasEl.getContext('2d'), 17698 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 17699 data = imageData.data, 17700 brightness = this.brightness; 17701 17702 for (var i = 0, len = data.length; i < len; i += 4) { 17703 data[i] += brightness; 17704 data[i + 1] += brightness; 17705 data[i + 2] += brightness; 17706 } 17707 17708 context.putImageData(imageData, 0, 0); 17709 }, 17710 17711 /** 17712 * Returns object representation of an instance 17713 * @return {Object} Object representation of an instance 17714 */ 17715 toObject: function() { 17716 return extend(this.callSuper('toObject'), { 17717 brightness: this.brightness 17718 }); 17719 } 17720 }); 17721 17722 /** 17723 * Returns filter instance from an object representation 17724 * @static 17725 * @param {Object} object Object to create an instance from 17726 * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness 17727 */ 17728 fabric.Image.filters.Brightness.fromObject = function(object) { 17729 return new fabric.Image.filters.Brightness(object); 17730 }; 17731 17732})(typeof exports !== 'undefined' ? exports : this); 17733 17734 17735(function(global) { 17736 17737 'use strict'; 17738 17739 var fabric = global.fabric || (global.fabric = { }), 17740 extend = fabric.util.object.extend; 17741 17742 /** 17743 * Adapted from <a href="http://www.html5rocks.com/en/tutorials/canvas/imagefilters/">html5rocks article</a> 17744 * @class fabric.Image.filters.Convolute 17745 * @memberOf fabric.Image.filters 17746 * @extends fabric.Image.filters.BaseFilter 17747 * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition 17748 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 17749 * @example <caption>Sharpen filter</caption> 17750 * var filter = new fabric.Image.filters.Convolute({ 17751 * matrix: [ 0, -1, 0, 17752 * -1, 5, -1, 17753 * 0, -1, 0 ] 17754 * }); 17755 * object.filters.push(filter); 17756 * object.applyFilters(canvas.renderAll.bind(canvas)); 17757 * @example <caption>Blur filter</caption> 17758 * var filter = new fabric.Image.filters.Convolute({ 17759 * matrix: [ 1/9, 1/9, 1/9, 17760 * 1/9, 1/9, 1/9, 17761 * 1/9, 1/9, 1/9 ] 17762 * }); 17763 * object.filters.push(filter); 17764 * object.applyFilters(canvas.renderAll.bind(canvas)); 17765 * @example <caption>Emboss filter</caption> 17766 * var filter = new fabric.Image.filters.Convolute({ 17767 * matrix: [ 1, 1, 1, 17768 * 1, 0.7, -1, 17769 * -1, -1, -1 ] 17770 * }); 17771 * object.filters.push(filter); 17772 * object.applyFilters(canvas.renderAll.bind(canvas)); 17773 * @example <caption>Emboss filter with opaqueness</caption> 17774 * var filter = new fabric.Image.filters.Convolute({ 17775 * opaque: true, 17776 * matrix: [ 1, 1, 1, 17777 * 1, 0.7, -1, 17778 * -1, -1, -1 ] 17779 * }); 17780 * object.filters.push(filter); 17781 * object.applyFilters(canvas.renderAll.bind(canvas)); 17782 */ 17783 fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { 17784 17785 /** 17786 * Filter type 17787 * @param {String} type 17788 * @default 17789 */ 17790 type: 'Convolute', 17791 17792 /** 17793 * Constructor 17794 * @memberOf fabric.Image.filters.Convolute.prototype 17795 * @param {Object} [options] Options object 17796 * @param {Boolean} [options.opaque=false] Opaque value (true/false) 17797 * @param {Array} [options.matrix] Filter matrix 17798 */ 17799 initialize: function(options) { 17800 options = options || { }; 17801 17802 this.opaque = options.opaque; 17803 this.matrix = options.matrix || [ 17804 0, 0, 0, 17805 0, 1, 0, 17806 0, 0, 0 17807 ]; 17808 17809 var canvasEl = fabric.util.createCanvasElement(); 17810 this.tmpCtx = canvasEl.getContext('2d'); 17811 }, 17812 17813 /** 17814 * @private 17815 */ 17816 _createImageData: function(w, h) { 17817 return this.tmpCtx.createImageData(w, h); 17818 }, 17819 17820 /** 17821 * Applies filter to canvas element 17822 * @param {Object} canvasEl Canvas element to apply filter to 17823 */ 17824 applyTo: function(canvasEl) { 17825 17826 var weights = this.matrix, 17827 context = canvasEl.getContext('2d'), 17828 pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 17829 17830 side = Math.round(Math.sqrt(weights.length)), 17831 halfSide = Math.floor(side/2), 17832 src = pixels.data, 17833 sw = pixels.width, 17834 sh = pixels.height, 17835 17836 // pad output by the convolution matrix 17837 w = sw, 17838 h = sh, 17839 output = this._createImageData(w, h), 17840 17841 dst = output.data, 17842 17843 // go through the destination image pixels 17844 alphaFac = this.opaque ? 1 : 0; 17845 17846 for (var y = 0; y < h; y++) { 17847 for (var x = 0; x < w; x++) { 17848 var sy = y, 17849 sx = x, 17850 dstOff = (y * w + x) * 4, 17851 // calculate the weighed sum of the source image pixels that 17852 // fall under the convolution matrix 17853 r = 0, g = 0, b = 0, a = 0; 17854 17855 for (var cy = 0; cy < side; cy++) { 17856 for (var cx = 0; cx < side; cx++) { 17857 17858 var scy = sy + cy - halfSide, 17859 scx = sx + cx - halfSide; 17860 17861 /* jshint maxdepth:5 */ 17862 if (scy < 0 || scy > sh || scx < 0 || scx > sw) { 17863 continue; 17864 } 17865 17866 var srcOff = (scy * sw + scx) * 4, 17867 wt = weights[cy * side + cx]; 17868 17869 r += src[srcOff] * wt; 17870 g += src[srcOff + 1] * wt; 17871 b += src[srcOff + 2] * wt; 17872 a += src[srcOff + 3] * wt; 17873 } 17874 } 17875 dst[dstOff] = r; 17876 dst[dstOff + 1] = g; 17877 dst[dstOff + 2] = b; 17878 dst[dstOff + 3] = a + alphaFac * (255 - a); 17879 } 17880 } 17881 17882 context.putImageData(output, 0, 0); 17883 }, 17884 17885 /** 17886 * Returns object representation of an instance 17887 * @return {Object} Object representation of an instance 17888 */ 17889 toObject: function() { 17890 return extend(this.callSuper('toObject'), { 17891 opaque: this.opaque, 17892 matrix: this.matrix 17893 }); 17894 } 17895 }); 17896 17897 /** 17898 * Returns filter instance from an object representation 17899 * @static 17900 * @param {Object} object Object to create an instance from 17901 * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute 17902 */ 17903 fabric.Image.filters.Convolute.fromObject = function(object) { 17904 return new fabric.Image.filters.Convolute(object); 17905 }; 17906 17907})(typeof exports !== 'undefined' ? exports : this); 17908 17909 17910(function(global) { 17911 17912 'use strict'; 17913 17914 var fabric = global.fabric || (global.fabric = { }), 17915 extend = fabric.util.object.extend; 17916 17917 /** 17918 * GradientTransparency filter class 17919 * @class fabric.Image.filters.GradientTransparency 17920 * @memberOf fabric.Image.filters 17921 * @extends fabric.Image.filters.BaseFilter 17922 * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition 17923 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 17924 * @example 17925 * var filter = new fabric.Image.filters.GradientTransparency({ 17926 * threshold: 200 17927 * }); 17928 * object.filters.push(filter); 17929 * object.applyFilters(canvas.renderAll.bind(canvas)); 17930 */ 17931 fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ { 17932 17933 /** 17934 * Filter type 17935 * @param {String} type 17936 * @default 17937 */ 17938 type: 'GradientTransparency', 17939 17940 /** 17941 * Constructor 17942 * @memberOf fabric.Image.filters.GradientTransparency.prototype 17943 * @param {Object} [options] Options object 17944 * @param {Number} [options.threshold=100] Threshold value 17945 */ 17946 initialize: function(options) { 17947 options = options || { }; 17948 this.threshold = options.threshold || 100; 17949 }, 17950 17951 /** 17952 * Applies filter to canvas element 17953 * @param {Object} canvasEl Canvas element to apply filter to 17954 */ 17955 applyTo: function(canvasEl) { 17956 var context = canvasEl.getContext('2d'), 17957 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 17958 data = imageData.data, 17959 threshold = this.threshold, 17960 total = data.length; 17961 17962 for (var i = 0, len = data.length; i < len; i += 4) { 17963 data[i + 3] = threshold + 255 * (total - i) / total; 17964 } 17965 17966 context.putImageData(imageData, 0, 0); 17967 }, 17968 17969 /** 17970 * Returns object representation of an instance 17971 * @return {Object} Object representation of an instance 17972 */ 17973 toObject: function() { 17974 return extend(this.callSuper('toObject'), { 17975 threshold: this.threshold 17976 }); 17977 } 17978 }); 17979 17980 /** 17981 * Returns filter instance from an object representation 17982 * @static 17983 * @param {Object} object Object to create an instance from 17984 * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency 17985 */ 17986 fabric.Image.filters.GradientTransparency.fromObject = function(object) { 17987 return new fabric.Image.filters.GradientTransparency(object); 17988 }; 17989 17990})(typeof exports !== 'undefined' ? exports : this); 17991 17992 17993(function(global) { 17994 17995 'use strict'; 17996 17997 var fabric = global.fabric || (global.fabric = { }); 17998 17999 /** 18000 * Grayscale image filter class 18001 * @class fabric.Image.filters.Grayscale 18002 * @memberOf fabric.Image.filters 18003 * @extends fabric.Image.filters.BaseFilter 18004 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18005 * @example 18006 * var filter = new fabric.Image.filters.Grayscale(); 18007 * object.filters.push(filter); 18008 * object.applyFilters(canvas.renderAll.bind(canvas)); 18009 */ 18010 fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { 18011 18012 /** 18013 * Filter type 18014 * @param {String} type 18015 * @default 18016 */ 18017 type: 'Grayscale', 18018 18019 /** 18020 * Applies filter to canvas element 18021 * @memberOf fabric.Image.filters.Grayscale.prototype 18022 * @param {Object} canvasEl Canvas element to apply filter to 18023 */ 18024 applyTo: function(canvasEl) { 18025 var context = canvasEl.getContext('2d'), 18026 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18027 data = imageData.data, 18028 len = imageData.width * imageData.height * 4, 18029 index = 0, 18030 average; 18031 18032 while (index < len) { 18033 average = (data[index] + data[index + 1] + data[index + 2]) / 3; 18034 data[index] = average; 18035 data[index + 1] = average; 18036 data[index + 2] = average; 18037 index += 4; 18038 } 18039 18040 context.putImageData(imageData, 0, 0); 18041 } 18042 }); 18043 18044 /** 18045 * Returns filter instance from an object representation 18046 * @static 18047 * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale 18048 */ 18049 fabric.Image.filters.Grayscale.fromObject = function() { 18050 return new fabric.Image.filters.Grayscale(); 18051 }; 18052 18053})(typeof exports !== 'undefined' ? exports : this); 18054 18055 18056(function(global) { 18057 18058 'use strict'; 18059 18060 var fabric = global.fabric || (global.fabric = { }); 18061 18062 /** 18063 * Invert filter class 18064 * @class fabric.Image.filters.Invert 18065 * @memberOf fabric.Image.filters 18066 * @extends fabric.Image.filters.BaseFilter 18067 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18068 * @example 18069 * var filter = new fabric.Image.filters.Invert(); 18070 * object.filters.push(filter); 18071 * object.applyFilters(canvas.renderAll.bind(canvas)); 18072 */ 18073 fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { 18074 18075 /** 18076 * Filter type 18077 * @param {String} type 18078 * @default 18079 */ 18080 type: 'Invert', 18081 18082 /** 18083 * Applies filter to canvas element 18084 * @memberOf fabric.Image.filters.Invert.prototype 18085 * @param {Object} canvasEl Canvas element to apply filter to 18086 */ 18087 applyTo: function(canvasEl) { 18088 var context = canvasEl.getContext('2d'), 18089 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18090 data = imageData.data, 18091 iLen = data.length, i; 18092 18093 for (i = 0; i < iLen; i+=4) { 18094 data[i] = 255 - data[i]; 18095 data[i + 1] = 255 - data[i + 1]; 18096 data[i + 2] = 255 - data[i + 2]; 18097 } 18098 18099 context.putImageData(imageData, 0, 0); 18100 } 18101 }); 18102 18103 /** 18104 * Returns filter instance from an object representation 18105 * @static 18106 * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert 18107 */ 18108 fabric.Image.filters.Invert.fromObject = function() { 18109 return new fabric.Image.filters.Invert(); 18110 }; 18111 18112})(typeof exports !== 'undefined' ? exports : this); 18113 18114 18115(function(global) { 18116 18117 'use strict'; 18118 18119 var fabric = global.fabric || (global.fabric = { }), 18120 extend = fabric.util.object.extend; 18121 18122 /** 18123 * Mask filter class 18124 * See http://resources.aleph-1.com/mask/ 18125 * @class fabric.Image.filters.Mask 18126 * @memberOf fabric.Image.filters 18127 * @extends fabric.Image.filters.BaseFilter 18128 * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition 18129 */ 18130 fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ { 18131 18132 /** 18133 * Filter type 18134 * @param {String} type 18135 * @default 18136 */ 18137 type: 'Mask', 18138 18139 /** 18140 * Constructor 18141 * @memberOf fabric.Image.filters.Mask.prototype 18142 * @param {Object} [options] Options object 18143 * @param {fabric.Image} [options.mask] Mask image object 18144 * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3) 18145 */ 18146 initialize: function(options) { 18147 options = options || { }; 18148 18149 this.mask = options.mask; 18150 this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; 18151 }, 18152 18153 /** 18154 * Applies filter to canvas element 18155 * @param {Object} canvasEl Canvas element to apply filter to 18156 */ 18157 applyTo: function(canvasEl) { 18158 if (!this.mask) { 18159 return; 18160 } 18161 18162 var context = canvasEl.getContext('2d'), 18163 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18164 data = imageData.data, 18165 maskEl = this.mask.getElement(), 18166 maskCanvasEl = fabric.util.createCanvasElement(), 18167 channel = this.channel, 18168 i, 18169 iLen = imageData.width * imageData.height * 4; 18170 18171 maskCanvasEl.width = maskEl.width; 18172 maskCanvasEl.height = maskEl.height; 18173 18174 maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height); 18175 18176 var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height), 18177 maskData = maskImageData.data; 18178 18179 for (i = 0; i < iLen; i += 4) { 18180 data[i + 3] = maskData[i + channel]; 18181 } 18182 18183 context.putImageData(imageData, 0, 0); 18184 }, 18185 18186 /** 18187 * Returns object representation of an instance 18188 * @return {Object} Object representation of an instance 18189 */ 18190 toObject: function() { 18191 return extend(this.callSuper('toObject'), { 18192 mask: this.mask.toObject(), 18193 channel: this.channel 18194 }); 18195 } 18196 }); 18197 18198 /** 18199 * Returns filter instance from an object representation 18200 * @static 18201 * @param {Object} object Object to create an instance from 18202 * @param {Function} [callback] Callback to invoke when a mask filter instance is created 18203 */ 18204 fabric.Image.filters.Mask.fromObject = function(object, callback) { 18205 fabric.util.loadImage(object.mask.src, function(img) { 18206 object.mask = new fabric.Image(img, object.mask); 18207 callback && callback(new fabric.Image.filters.Mask(object)); 18208 }); 18209 }; 18210 18211 /** 18212 * Indicates that instances of this type are async 18213 * @static 18214 * @type Boolean 18215 * @default 18216 */ 18217 fabric.Image.filters.Mask.async = true; 18218 18219})(typeof exports !== 'undefined' ? exports : this); 18220 18221 18222(function(global) { 18223 18224 'use strict'; 18225 18226 var fabric = global.fabric || (global.fabric = { }), 18227 extend = fabric.util.object.extend; 18228 18229 /** 18230 * Noise filter class 18231 * @class fabric.Image.filters.Noise 18232 * @memberOf fabric.Image.filters 18233 * @extends fabric.Image.filters.BaseFilter 18234 * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition 18235 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18236 * @example 18237 * var filter = new fabric.Image.filters.Noise({ 18238 * noise: 700 18239 * }); 18240 * object.filters.push(filter); 18241 * object.applyFilters(canvas.renderAll.bind(canvas)); 18242 */ 18243 fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { 18244 18245 /** 18246 * Filter type 18247 * @param {String} type 18248 * @default 18249 */ 18250 type: 'Noise', 18251 18252 /** 18253 * Constructor 18254 * @memberOf fabric.Image.filters.Noise.prototype 18255 * @param {Object} [options] Options object 18256 * @param {Number} [options.noise=0] Noise value 18257 */ 18258 initialize: function(options) { 18259 options = options || { }; 18260 this.noise = options.noise || 0; 18261 }, 18262 18263 /** 18264 * Applies filter to canvas element 18265 * @param {Object} canvasEl Canvas element to apply filter to 18266 */ 18267 applyTo: function(canvasEl) { 18268 var context = canvasEl.getContext('2d'), 18269 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18270 data = imageData.data, 18271 noise = this.noise, rand; 18272 18273 for (var i = 0, len = data.length; i < len; i += 4) { 18274 18275 rand = (0.5 - Math.random()) * noise; 18276 18277 data[i] += rand; 18278 data[i + 1] += rand; 18279 data[i + 2] += rand; 18280 } 18281 18282 context.putImageData(imageData, 0, 0); 18283 }, 18284 18285 /** 18286 * Returns object representation of an instance 18287 * @return {Object} Object representation of an instance 18288 */ 18289 toObject: function() { 18290 return extend(this.callSuper('toObject'), { 18291 noise: this.noise 18292 }); 18293 } 18294 }); 18295 18296 /** 18297 * Returns filter instance from an object representation 18298 * @static 18299 * @param {Object} object Object to create an instance from 18300 * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise 18301 */ 18302 fabric.Image.filters.Noise.fromObject = function(object) { 18303 return new fabric.Image.filters.Noise(object); 18304 }; 18305 18306})(typeof exports !== 'undefined' ? exports : this); 18307 18308 18309(function(global) { 18310 18311 'use strict'; 18312 18313 var fabric = global.fabric || (global.fabric = { }), 18314 extend = fabric.util.object.extend; 18315 18316 /** 18317 * Pixelate filter class 18318 * @class fabric.Image.filters.Pixelate 18319 * @memberOf fabric.Image.filters 18320 * @extends fabric.Image.filters.BaseFilter 18321 * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition 18322 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18323 * @example 18324 * var filter = new fabric.Image.filters.Pixelate({ 18325 * blocksize: 8 18326 * }); 18327 * object.filters.push(filter); 18328 * object.applyFilters(canvas.renderAll.bind(canvas)); 18329 */ 18330 fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { 18331 18332 /** 18333 * Filter type 18334 * @param {String} type 18335 * @default 18336 */ 18337 type: 'Pixelate', 18338 18339 /** 18340 * Constructor 18341 * @memberOf fabric.Image.filters.Pixelate.prototype 18342 * @param {Object} [options] Options object 18343 * @param {Number} [options.blocksize=4] Blocksize for pixelate 18344 */ 18345 initialize: function(options) { 18346 options = options || { }; 18347 this.blocksize = options.blocksize || 4; 18348 }, 18349 18350 /** 18351 * Applies filter to canvas element 18352 * @param {Object} canvasEl Canvas element to apply filter to 18353 */ 18354 applyTo: function(canvasEl) { 18355 var context = canvasEl.getContext('2d'), 18356 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18357 data = imageData.data, 18358 iLen = imageData.height, 18359 jLen = imageData.width, 18360 index, i, j, r, g, b, a; 18361 18362 for (i = 0; i < iLen; i += this.blocksize) { 18363 for (j = 0; j < jLen; j += this.blocksize) { 18364 18365 index = (i * 4) * jLen + (j * 4); 18366 18367 r = data[index]; 18368 g = data[index + 1]; 18369 b = data[index + 2]; 18370 a = data[index + 3]; 18371 18372 /* 18373 blocksize: 4 18374 18375 [1,x,x,x,1] 18376 [x,x,x,x,1] 18377 [x,x,x,x,1] 18378 [x,x,x,x,1] 18379 [1,1,1,1,1] 18380 */ 18381 18382 for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { 18383 for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { 18384 index = (_i * 4) * jLen + (_j * 4); 18385 data[index] = r; 18386 data[index + 1] = g; 18387 data[index + 2] = b; 18388 data[index + 3] = a; 18389 } 18390 } 18391 } 18392 } 18393 18394 context.putImageData(imageData, 0, 0); 18395 }, 18396 18397 /** 18398 * Returns object representation of an instance 18399 * @return {Object} Object representation of an instance 18400 */ 18401 toObject: function() { 18402 return extend(this.callSuper('toObject'), { 18403 blocksize: this.blocksize 18404 }); 18405 } 18406 }); 18407 18408 /** 18409 * Returns filter instance from an object representation 18410 * @static 18411 * @param {Object} object Object to create an instance from 18412 * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate 18413 */ 18414 fabric.Image.filters.Pixelate.fromObject = function(object) { 18415 return new fabric.Image.filters.Pixelate(object); 18416 }; 18417 18418})(typeof exports !== 'undefined' ? exports : this); 18419 18420 18421(function(global) { 18422 18423 'use strict'; 18424 18425 var fabric = global.fabric || (global.fabric = { }), 18426 extend = fabric.util.object.extend; 18427 18428 /** 18429 * Remove white filter class 18430 * @class fabric.Image.filters.RemoveWhite 18431 * @memberOf fabric.Image.filters 18432 * @extends fabric.Image.filters.BaseFilter 18433 * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition 18434 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18435 * @example 18436 * var filter = new fabric.Image.filters.RemoveWhite({ 18437 * threshold: 40, 18438 * distance: 140 18439 * }); 18440 * object.filters.push(filter); 18441 * object.applyFilters(canvas.renderAll.bind(canvas)); 18442 */ 18443 fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ { 18444 18445 /** 18446 * Filter type 18447 * @param {String} type 18448 * @default 18449 */ 18450 type: 'RemoveWhite', 18451 18452 /** 18453 * Constructor 18454 * @memberOf fabric.Image.filters.RemoveWhite.prototype 18455 * @param {Object} [options] Options object 18456 * @param {Number} [options.threshold=30] Threshold value 18457 * @param {Number} [options.distance=20] Distance value 18458 */ 18459 initialize: function(options) { 18460 options = options || { }; 18461 this.threshold = options.threshold || 30; 18462 this.distance = options.distance || 20; 18463 }, 18464 18465 /** 18466 * Applies filter to canvas element 18467 * @param {Object} canvasEl Canvas element to apply filter to 18468 */ 18469 applyTo: function(canvasEl) { 18470 var context = canvasEl.getContext('2d'), 18471 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18472 data = imageData.data, 18473 threshold = this.threshold, 18474 distance = this.distance, 18475 limit = 255 - threshold, 18476 abs = Math.abs, 18477 r, g, b; 18478 18479 for (var i = 0, len = data.length; i < len; i += 4) { 18480 r = data[i]; 18481 g = data[i + 1]; 18482 b = data[i + 2]; 18483 18484 if (r > limit && 18485 g > limit && 18486 b > limit && 18487 abs(r - g) < distance && 18488 abs(r - b) < distance && 18489 abs(g - b) < distance 18490 ) { 18491 data[i + 3] = 1; 18492 } 18493 } 18494 18495 context.putImageData(imageData, 0, 0); 18496 }, 18497 18498 /** 18499 * Returns object representation of an instance 18500 * @return {Object} Object representation of an instance 18501 */ 18502 toObject: function() { 18503 return extend(this.callSuper('toObject'), { 18504 threshold: this.threshold, 18505 distance: this.distance 18506 }); 18507 } 18508 }); 18509 18510 /** 18511 * Returns filter instance from an object representation 18512 * @static 18513 * @param {Object} object Object to create an instance from 18514 * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite 18515 */ 18516 fabric.Image.filters.RemoveWhite.fromObject = function(object) { 18517 return new fabric.Image.filters.RemoveWhite(object); 18518 }; 18519 18520})(typeof exports !== 'undefined' ? exports : this); 18521 18522 18523(function(global) { 18524 18525 'use strict'; 18526 18527 var fabric = global.fabric || (global.fabric = { }); 18528 18529 /** 18530 * Sepia filter class 18531 * @class fabric.Image.filters.Sepia 18532 * @memberOf fabric.Image.filters 18533 * @extends fabric.Image.filters.BaseFilter 18534 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18535 * @example 18536 * var filter = new fabric.Image.filters.Sepia(); 18537 * object.filters.push(filter); 18538 * object.applyFilters(canvas.renderAll.bind(canvas)); 18539 */ 18540 fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ { 18541 18542 /** 18543 * Filter type 18544 * @param {String} type 18545 * @default 18546 */ 18547 type: 'Sepia', 18548 18549 /** 18550 * Applies filter to canvas element 18551 * @memberOf fabric.Image.filters.Sepia.prototype 18552 * @param {Object} canvasEl Canvas element to apply filter to 18553 */ 18554 applyTo: function(canvasEl) { 18555 var context = canvasEl.getContext('2d'), 18556 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18557 data = imageData.data, 18558 iLen = data.length, i, avg; 18559 18560 for (i = 0; i < iLen; i+=4) { 18561 avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2]; 18562 data[i] = avg + 100; 18563 data[i + 1] = avg + 50; 18564 data[i + 2] = avg + 255; 18565 } 18566 18567 context.putImageData(imageData, 0, 0); 18568 } 18569 }); 18570 18571 /** 18572 * Returns filter instance from an object representation 18573 * @static 18574 * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia 18575 */ 18576 fabric.Image.filters.Sepia.fromObject = function() { 18577 return new fabric.Image.filters.Sepia(); 18578 }; 18579 18580})(typeof exports !== 'undefined' ? exports : this); 18581 18582 18583(function(global) { 18584 18585 'use strict'; 18586 18587 var fabric = global.fabric || (global.fabric = { }); 18588 18589 /** 18590 * Sepia2 filter class 18591 * @class fabric.Image.filters.Sepia2 18592 * @memberOf fabric.Image.filters 18593 * @extends fabric.Image.filters.BaseFilter 18594 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18595 * @example 18596 * var filter = new fabric.Image.filters.Sepia2(); 18597 * object.filters.push(filter); 18598 * object.applyFilters(canvas.renderAll.bind(canvas)); 18599 */ 18600 fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ { 18601 18602 /** 18603 * Filter type 18604 * @param {String} type 18605 * @default 18606 */ 18607 type: 'Sepia2', 18608 18609 /** 18610 * Applies filter to canvas element 18611 * @memberOf fabric.Image.filters.Sepia.prototype 18612 * @param {Object} canvasEl Canvas element to apply filter to 18613 */ 18614 applyTo: function(canvasEl) { 18615 var context = canvasEl.getContext('2d'), 18616 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18617 data = imageData.data, 18618 iLen = data.length, i, r, g, b; 18619 18620 for (i = 0; i < iLen; i+=4) { 18621 r = data[i]; 18622 g = data[i + 1]; 18623 b = data[i + 2]; 18624 18625 data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351; 18626 data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203; 18627 data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140; 18628 } 18629 18630 context.putImageData(imageData, 0, 0); 18631 } 18632 }); 18633 18634 /** 18635 * Returns filter instance from an object representation 18636 * @static 18637 * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2 18638 */ 18639 fabric.Image.filters.Sepia2.fromObject = function() { 18640 return new fabric.Image.filters.Sepia2(); 18641 }; 18642 18643})(typeof exports !== 'undefined' ? exports : this); 18644 18645 18646(function(global) { 18647 18648 'use strict'; 18649 18650 var fabric = global.fabric || (global.fabric = { }), 18651 extend = fabric.util.object.extend; 18652 18653 /** 18654 * Tint filter class 18655 * Adapted from <a href="https://github.com/mezzoblue/PaintbrushJS">https://github.com/mezzoblue/PaintbrushJS</a> 18656 * @class fabric.Image.filters.Tint 18657 * @memberOf fabric.Image.filters 18658 * @extends fabric.Image.filters.BaseFilter 18659 * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition 18660 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 18661 * @example <caption>Tint filter with hex color and opacity</caption> 18662 * var filter = new fabric.Image.filters.Tint({ 18663 * color: '#3513B0', 18664 * opacity: 0.5 18665 * }); 18666 * object.filters.push(filter); 18667 * object.applyFilters(canvas.renderAll.bind(canvas)); 18668 * @example <caption>Tint filter with rgba color</caption> 18669 * var filter = new fabric.Image.filters.Tint({ 18670 * color: 'rgba(53, 21, 176, 0.5)' 18671 * }); 18672 * object.filters.push(filter); 18673 * object.applyFilters(canvas.renderAll.bind(canvas)); 18674 */ 18675 fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ { 18676 18677 /** 18678 * Filter type 18679 * @param {String} type 18680 * @default 18681 */ 18682 type: 'Tint', 18683 18684 /** 18685 * Constructor 18686 * @memberOf fabric.Image.filters.Tint.prototype 18687 * @param {Object} [options] Options object 18688 * @param {String} [options.color=#000000] Color to tint the image with 18689 * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1) 18690 */ 18691 initialize: function(options) { 18692 options = options || { }; 18693 18694 this.color = options.color || '#000000'; 18695 this.opacity = typeof options.opacity !== 'undefined' 18696 ? options.opacity 18697 : new fabric.Color(this.color).getAlpha(); 18698 }, 18699 18700 /** 18701 * Applies filter to canvas element 18702 * @param {Object} canvasEl Canvas element to apply filter to 18703 */ 18704 applyTo: function(canvasEl) { 18705 var context = canvasEl.getContext('2d'), 18706 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18707 data = imageData.data, 18708 iLen = data.length, i, 18709 tintR, tintG, tintB, 18710 r, g, b, alpha1, 18711 source; 18712 18713 source = new fabric.Color(this.color).getSource(); 18714 18715 tintR = source[0] * this.opacity; 18716 tintG = source[1] * this.opacity; 18717 tintB = source[2] * this.opacity; 18718 18719 alpha1 = 1 - this.opacity; 18720 18721 for (i = 0; i < iLen; i+=4) { 18722 r = data[i]; 18723 g = data[i + 1]; 18724 b = data[i + 2]; 18725 18726 // alpha compositing 18727 data[i] = tintR + r * alpha1; 18728 data[i + 1] = tintG + g * alpha1; 18729 data[i + 2] = tintB + b * alpha1; 18730 } 18731 18732 context.putImageData(imageData, 0, 0); 18733 }, 18734 18735 /** 18736 * Returns object representation of an instance 18737 * @return {Object} Object representation of an instance 18738 */ 18739 toObject: function() { 18740 return extend(this.callSuper('toObject'), { 18741 color: this.color, 18742 opacity: this.opacity 18743 }); 18744 } 18745 }); 18746 18747 /** 18748 * Returns filter instance from an object representation 18749 * @static 18750 * @param {Object} object Object to create an instance from 18751 * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint 18752 */ 18753 fabric.Image.filters.Tint.fromObject = function(object) { 18754 return new fabric.Image.filters.Tint(object); 18755 }; 18756 18757})(typeof exports !== 'undefined' ? exports : this); 18758 18759 18760(function(global) { 18761 18762 'use strict'; 18763 18764 var fabric = global.fabric || (global.fabric = { }), 18765 extend = fabric.util.object.extend; 18766 18767 /** 18768 * Multiply filter class 18769 * Adapted from <a href="http://www.laurenscorijn.com/articles/colormath-basics">http://www.laurenscorijn.com/articles/colormath-basics</a> 18770 * @class fabric.Image.filters.Multiply 18771 * @memberOf fabric.Image.filters 18772 * @extends fabric.Image.filters.BaseFilter 18773 * @example <caption>Multiply filter with hex color</caption> 18774 * var filter = new fabric.Image.filters.Multiply({ 18775 * color: '#F0F' 18776 * }); 18777 * object.filters.push(filter); 18778 * object.applyFilters(canvas.renderAll.bind(canvas)); 18779 * @example <caption>Multiply filter with rgb color</caption> 18780 * var filter = new fabric.Image.filters.Multiply({ 18781 * color: 'rgb(53, 21, 176)' 18782 * }); 18783 * object.filters.push(filter); 18784 * object.applyFilters(canvas.renderAll.bind(canvas)); 18785 */ 18786 fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { 18787 18788 /** 18789 * Filter type 18790 * @param {String} type 18791 * @default 18792 */ 18793 type: 'Multiply', 18794 18795 /** 18796 * Constructor 18797 * @memberOf fabric.Image.filters.Multiply.prototype 18798 * @param {Object} [options] Options object 18799 * @param {String} [options.color=#000000] Color to multiply the image pixels with 18800 */ 18801 initialize: function(options) { 18802 options = options || { }; 18803 18804 this.color = options.color || '#000000'; 18805 }, 18806 18807 /** 18808 * Applies filter to canvas element 18809 * @param {Object} canvasEl Canvas element to apply filter to 18810 */ 18811 applyTo: function(canvasEl) { 18812 var context = canvasEl.getContext('2d'), 18813 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18814 data = imageData.data, 18815 iLen = data.length, i, 18816 source; 18817 18818 source = new fabric.Color(this.color).getSource(); 18819 18820 for (i = 0; i < iLen; i+=4) { 18821 data[i] *= source[0] / 255; 18822 data[i + 1] *= source[1] / 255; 18823 data[i + 2] *= source[2] / 255; 18824 } 18825 18826 context.putImageData(imageData, 0, 0); 18827 }, 18828 18829 /** 18830 * Returns object representation of an instance 18831 * @return {Object} Object representation of an instance 18832 */ 18833 toObject: function() { 18834 return extend(this.callSuper('toObject'), { 18835 color: this.color 18836 }); 18837 } 18838 }); 18839 18840 /** 18841 * Returns filter instance from an object representation 18842 * @static 18843 * @param {Object} object Object to create an instance from 18844 * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply 18845 */ 18846 fabric.Image.filters.Multiply.fromObject = function(object) { 18847 return new fabric.Image.filters.Multiply(object); 18848 }; 18849 18850})(typeof exports !== 'undefined' ? exports : this); 18851 18852 18853(function(global) { 18854 'use strict'; 18855 18856 var fabric = global.fabric; 18857 18858 /** 18859 * Color Blend filter class 18860 * @class fabric.Image.filter.Blend 18861 * @memberOf fabric.Image.filters 18862 * @extends fabric.Image.filters.BaseFilter 18863 * @example 18864 * var filter = new fabric.Image.filters.Blend({ 18865 * color: '#000', 18866 * mode: 'multiply' 18867 * }); 18868 * 18869 * var filter = new fabric.Image.filters.Blend({ 18870 * image: fabricImageObject, 18871 * mode: 'multiply', 18872 * alpha: 0.5 18873 * }); 18874 18875 * object.filters.push(filter); 18876 * object.applyFilters(canvas.renderAll.bind(canvas)); 18877 */ 18878 fabric.Image.filters.Blend = fabric.util.createClass({ 18879 type: 'Blend', 18880 18881 initialize: function(options) { 18882 options = options || {}; 18883 this.color = options.color || '#000'; 18884 this.image = options.image || false; 18885 this.mode = options.mode || 'multiply'; 18886 this.alpha = options.alpha || 1; 18887 }, 18888 18889 applyTo: function(canvasEl) { 18890 var context = canvasEl.getContext('2d'), 18891 imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), 18892 data = imageData.data, 18893 tr, tg, tb, 18894 r, g, b, 18895 _r, _g, _b, 18896 source, 18897 isImage = false; 18898 18899 if (this.image) { 18900 // Blend images 18901 isImage = true; 18902 18903 var _el = fabric.util.createCanvasElement(); 18904 _el.width = this.image.width; 18905 _el.height = this.image.height; 18906 18907 var tmpCanvas = new fabric.StaticCanvas(_el); 18908 tmpCanvas.add(this.image); 18909 var context2 = tmpCanvas.getContext('2d'); 18910 source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data; 18911 } 18912 else { 18913 // Blend color 18914 source = new fabric.Color(this.color).getSource(); 18915 18916 tr = source[0] * this.alpha; 18917 tg = source[1] * this.alpha; 18918 tb = source[2] * this.alpha; 18919 } 18920 18921 for (var i = 0, len = data.length; i < len; i += 4) { 18922 18923 r = data[i]; 18924 g = data[i + 1]; 18925 b = data[i + 2]; 18926 18927 if (isImage) { 18928 tr = source[i] * this.alpha; 18929 tg = source[i + 1] * this.alpha; 18930 tb = source[i + 2] * this.alpha; 18931 } 18932 18933 switch (this.mode) { 18934 case 'multiply': 18935 data[i] = r * tr / 255; 18936 data[i + 1] = g * tg / 255; 18937 data[i + 2] = b * tb / 255; 18938 break; 18939 case 'screen': 18940 data[i] = 1 - (1 - r) * (1 - tr); 18941 data[i + 1] = 1 - (1 - g) * (1 - tg); 18942 data[i + 2] = 1 - (1 - b) * (1 - tb); 18943 break; 18944 case 'add': 18945 data[i] = Math.min(255, r + tr); 18946 data[i + 1] = Math.min(255, g + tg); 18947 data[i + 2] = Math.min(255, b + tb); 18948 break; 18949 case 'diff': 18950 case 'difference': 18951 data[i] = Math.abs(r - tr); 18952 data[i + 1] = Math.abs(g - tg); 18953 data[i + 2] = Math.abs(b - tb); 18954 break; 18955 case 'subtract': 18956 _r = r - tr; 18957 _g = g - tg; 18958 _b = b - tb; 18959 18960 data[i] = (_r < 0) ? 0 : _r; 18961 data[i + 1] = (_g < 0) ? 0 : _g; 18962 data[i + 2] = (_b < 0) ? 0 : _b; 18963 break; 18964 case 'darken': 18965 data[i] = Math.min(r, tr); 18966 data[i + 1] = Math.min(g, tg); 18967 data[i + 2] = Math.min(b, tb); 18968 break; 18969 case 'lighten': 18970 data[i] = Math.max(r, tr); 18971 data[i + 1] = Math.max(g, tg); 18972 data[i + 2] = Math.max(b, tb); 18973 break; 18974 } 18975 } 18976 18977 context.putImageData(imageData, 0, 0); 18978 }, 18979 18980 /** 18981 * Returns object representation of an instance 18982 * @return {Object} Object representation of an instance 18983 */ 18984 toObject: function() { 18985 return { 18986 color: this.color, 18987 image: this.image, 18988 mode: this.mode, 18989 alpha: this.alpha 18990 }; 18991 } 18992 }); 18993 18994 fabric.Image.filters.Blend.fromObject = function(object) { 18995 return new fabric.Image.filters.Blend(object); 18996 }; 18997})(typeof exports !== 'undefined' ? exports : this); 18998 18999 19000(function(global) { 19001 19002 'use strict'; 19003 19004 var fabric = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor, 19005 sqrt = Math.sqrt, abs = Math.abs, max = Math.max, round = Math.round, sin = Math.sin, 19006 ceil = Math.ceil; 19007 19008 /** 19009 * Resize image filter class 19010 * @class fabric.Image.filters.Resize 19011 * @memberOf fabric.Image.filters 19012 * @extends fabric.Image.filters.BaseFilter 19013 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} 19014 * @example 19015 * var filter = new fabric.Image.filters.Resize(); 19016 * object.filters.push(filter); 19017 * object.applyFilters(canvas.renderAll.bind(canvas)); 19018 */ 19019 fabric.Image.filters.Resize = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ { 19020 19021 /** 19022 * Filter type 19023 * @param {String} type 19024 * @default 19025 */ 19026 type: 'Resize', 19027 19028 /** 19029 * Resize type 19030 * @param {String} resizeType 19031 * @default 19032 */ 19033 resizeType: 'hermite', 19034 19035 /** 19036 * Scale factor for resizing, x axis 19037 * @param {Number} scaleX 19038 * @default 19039 */ 19040 scaleX: 0, 19041 19042 /** 19043 * Scale factor for resizing, y axis 19044 * @param {Number} scaleY 19045 * @default 19046 */ 19047 scaleY: 0, 19048 19049 /** 19050 * LanczosLobes parameter for lanczos filter 19051 * @param {Number} lanczosLobes 19052 * @default 19053 */ 19054 lanczosLobes: 3, 19055 19056 /** 19057 * Applies filter to canvas element 19058 * @memberOf fabric.Image.filters.Resize.prototype 19059 * @param {Object} canvasEl Canvas element to apply filter to 19060 */ 19061 applyTo: function(canvasEl, scaleX, scaleY) { 19062 19063 this.rcpScaleX = 1 / scaleX; 19064 this.rcpScaleY = 1 / scaleY; 19065 19066 var oW = canvasEl.width, oH = canvasEl.height, 19067 dW = round(oW * scaleX), dH = round(oH * scaleY), 19068 imageData; 19069 19070 if (this.resizeType === 'sliceHack') { 19071 imageData = this.sliceByTwo(canvasEl, oW, oH, dW, dH); 19072 } 19073 if (this.resizeType === 'hermite') { 19074 imageData = this.hermiteFastResize(canvasEl, oW, oH, dW, dH); 19075 } 19076 if (this.resizeType === 'bilinear') { 19077 imageData = this.bilinearFiltering(canvasEl, oW, oH, dW, dH); 19078 } 19079 if (this.resizeType === 'lanczos') { 19080 imageData = this.lanczosResize(canvasEl, oW, oH, dW, dH); 19081 } 19082 canvasEl.width = dW; 19083 canvasEl.height = dH; 19084 canvasEl.getContext('2d').putImageData(imageData, 0, 0); 19085 }, 19086 19087 sliceByTwo: function(canvasEl, width, height, newWidth, newHeight) { 19088 var context = canvasEl.getContext('2d'), imageData, 19089 multW = 0.5, multH = 0.5, signW = 1, signH = 1, 19090 doneW = false, doneH = false, stepW = width, stepH = height, 19091 tmpCanvas = fabric.util.createCanvasElement(), 19092 tmpCtx = tmpCanvas.getContext('2d'); 19093 newWidth = floor(newWidth); 19094 newHeight = floor(newHeight); 19095 tmpCanvas.width = max(newWidth, width); 19096 tmpCanvas.height = max(newHeight, height); 19097 19098 if (newWidth > width) { 19099 multW = 2; 19100 signW = -1; 19101 } 19102 if (newHeight > height) { 19103 multH = 2; 19104 signH = -1; 19105 } 19106 imageData = context.getImageData(0, 0, width, height); 19107 canvasEl.width = max(newWidth, width); 19108 canvasEl.height = max(newHeight, height); 19109 context.putImageData(imageData, 0, 0); 19110 19111 while (!doneW || !doneH) { 19112 width = stepW; 19113 height = stepH; 19114 if (newWidth * signW < floor(stepW * multW * signW)) { 19115 stepW = floor(stepW * multW); 19116 } 19117 else { 19118 stepW = newWidth; 19119 doneW = true; 19120 } 19121 if (newHeight * signH < floor(stepH * multH * signH)) { 19122 stepH = floor(stepH * multH); 19123 } 19124 else { 19125 stepH = newHeight; 19126 doneH = true; 19127 } 19128 imageData = context.getImageData(0, 0, width, height); 19129 tmpCtx.putImageData(imageData, 0, 0); 19130 context.clearRect(0, 0, stepW, stepH); 19131 context.drawImage(tmpCanvas, 0, 0, width, height, 0, 0, stepW, stepH); 19132 } 19133 return context.getImageData(0, 0, newWidth, newHeight); 19134 }, 19135 19136 lanczosResize: function(canvasEl, oW, oH, dW, dH) { 19137 19138 function lanczosCreate(lobes) { 19139 return function(x) { 19140 if (x > lobes) { 19141 return 0; 19142 } 19143 x *= Math.PI; 19144 if (abs(x) < 1e-16) { 19145 return 1; 19146 } 19147 var xx = x / lobes; 19148 return sin(x) * sin(xx) / x / xx; 19149 }; 19150 } 19151 19152 function process(u) { 19153 var v, i, weight, idx, a, red, green, 19154 blue, alpha, fX, fY; 19155 center.x = (u + 0.5) * ratioX; 19156 icenter.x = floor(center.x); 19157 for (v = 0; v < dH; v++) { 19158 center.y = (v + 0.5) * ratioY; 19159 icenter.y = floor(center.y); 19160 a = 0, red = 0, green = 0, blue = 0, alpha = 0; 19161 for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) { 19162 if (i < 0 || i >= oW) { 19163 continue; 19164 } 19165 fX = floor(1000 * abs(i - center.x)); 19166 if (!cacheLanc[fX]) { 19167 cacheLanc[fX] = { }; 19168 } 19169 for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) { 19170 if (j < 0 || j >= oH) { 19171 continue; 19172 } 19173 fY = floor(1000 * abs(j - center.y)); 19174 if (!cacheLanc[fX][fY]) { 19175 cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000); 19176 } 19177 weight = cacheLanc[fX][fY]; 19178 if (weight > 0) { 19179 idx = (j * oW + i) * 4; 19180 a += weight; 19181 red += weight * srcData[idx]; 19182 green += weight * srcData[idx + 1]; 19183 blue += weight * srcData[idx + 2]; 19184 alpha += weight * srcData[idx + 3]; 19185 } 19186 } 19187 } 19188 idx = (v * dW + u) * 4; 19189 destData[idx] = red / a; 19190 destData[idx + 1] = green / a; 19191 destData[idx + 2] = blue / a; 19192 destData[idx + 3] = alpha / a; 19193 } 19194 19195 if (++u < dW) { 19196 return process(u); 19197 } 19198 else { 19199 return destImg; 19200 } 19201 } 19202 19203 var context = canvasEl.getContext('2d'), 19204 srcImg = context.getImageData(0, 0, oW, oH), 19205 destImg = context.getImageData(0, 0, dW, dH), 19206 srcData = srcImg.data, destData = destImg.data, 19207 lanczos = lanczosCreate(this.lanczosLobes), 19208 ratioX = this.rcpScaleX, ratioY = this.rcpScaleY, 19209 rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY, 19210 range2X = ceil(ratioX * this.lanczosLobes / 2), 19211 range2Y = ceil(ratioY * this.lanczosLobes / 2), 19212 cacheLanc = { }, center = { }, icenter = { }; 19213 19214 return process(0); 19215 }, 19216 19217 bilinearFiltering: function(canvasEl, w, h, w2, h2) { 19218 var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl, 19219 color, offset = 0, origPix, ratioX = this.rcpScaleX, 19220 ratioY = this.rcpScaleY, context = canvasEl.getContext('2d'), 19221 w4 = 4 * (w - 1), img = context.getImageData(0, 0, w, h), 19222 pixels = img.data, destImage = context.getImageData(0, 0, w2, h2), 19223 destPixels = destImage.data; 19224 for (i = 0; i < h2; i++) { 19225 for (j = 0; j < w2; j++) { 19226 x = floor(ratioX * j); 19227 y = floor(ratioY * i); 19228 xDiff = ratioX * j - x; 19229 yDiff = ratioY * i - y; 19230 origPix = 4 * (y * w + x); 19231 19232 for (chnl = 0; chnl < 4; chnl++) { 19233 a = pixels[origPix + chnl]; 19234 b = pixels[origPix + 4 + chnl]; 19235 c = pixels[origPix + w4 + chnl]; 19236 d = pixels[origPix + w4 + 4 + chnl]; 19237 color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) + 19238 c * yDiff * (1 - xDiff) + d * xDiff * yDiff; 19239 destPixels[offset++] = color; 19240 } 19241 } 19242 } 19243 return destImage; 19244 }, 19245 19246 hermiteFastResize: function(canvasEl, oW, oH, dW, dH) { 19247 var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY, 19248 ratioWHalf = ceil(ratioW / 2), 19249 ratioHHalf = ceil(ratioH / 2), 19250 context = canvasEl.getContext('2d'), 19251 img = context.getImageData(0, 0, oW, oH), data = img.data, 19252 img2 = context.getImageData(0, 0, dW, dH), data2 = img2.data; 19253 for (var j = 0; j < dH; j++) { 19254 for (var i = 0; i < dW; i++) { 19255 var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0, 19256 gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH; 19257 for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) { 19258 var dy = abs(centerY - (yy + 0.5)) / ratioHHalf, 19259 centerX = (i + 0.5) * ratioW, w0 = dy * dy; 19260 for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) { 19261 var dx = abs(centerX - (xx + 0.5)) / ratioWHalf, 19262 w = sqrt(w0 + dx * dx); 19263 /*jshint maxdepth:5 */ 19264 if (w > 1 && w < -1) { 19265 continue; 19266 } 19267 //hermite filter 19268 weight = 2 * w * w * w - 3 * w * w + 1; 19269 if (weight > 0) { 19270 dx = 4 * (xx + yy * oW); 19271 //alpha 19272 gxA += weight * data[dx + 3]; 19273 weightsAlpha += weight; 19274 //colors 19275 /*jshint maxdepth:6 */ 19276 if (data[dx + 3] < 255) { 19277 weight = weight * data[dx + 3] / 250; 19278 } 19279 /*jshint maxdepth:5 */ 19280 gxR += weight * data[dx]; 19281 gxG += weight * data[dx + 1]; 19282 gxB += weight * data[dx + 2]; 19283 weights += weight; 19284 } 19285 /*jshint maxdepth:4 */ 19286 } 19287 } 19288 data2[x2] = gxR / weights; 19289 data2[x2 + 1] = gxG / weights; 19290 data2[x2 + 2] = gxB / weights; 19291 data2[x2 + 3] = gxA / weightsAlpha; 19292 } 19293 } 19294 return img2; 19295 }, 19296 19297 /** 19298 * Returns object representation of an instance 19299 * @return {Object} Object representation of an instance 19300 */ 19301 toObject: function() { 19302 return { 19303 type: this.type, 19304 scaleX: this.scaleX, 19305 scaley: this.scaleY, 19306 resizeType: this.resizeType, 19307 lanczosLobes: this.lanczosLobes 19308 }; 19309 } 19310 }); 19311 19312 /** 19313 * Returns filter instance from an object representation 19314 * @static 19315 * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize 19316 */ 19317 fabric.Image.filters.Resize.fromObject = function() { 19318 return new fabric.Image.filters.Resize(); 19319 }; 19320 19321})(typeof exports !== 'undefined' ? exports : this); 19322 19323 19324(function(global) { 19325 19326 'use strict'; 19327 19328 var fabric = global.fabric || (global.fabric = { }), 19329 extend = fabric.util.object.extend, 19330 clone = fabric.util.object.clone, 19331 toFixed = fabric.util.toFixed, 19332 supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); 19333 19334 if (fabric.Text) { 19335 fabric.warn('fabric.Text is already defined'); 19336 return; 19337 } 19338 19339 var stateProperties = fabric.Object.prototype.stateProperties.concat(); 19340 stateProperties.push( 19341 'fontFamily', 19342 'fontWeight', 19343 'fontSize', 19344 'text', 19345 'textDecoration', 19346 'textAlign', 19347 'fontStyle', 19348 'lineHeight', 19349 'textBackgroundColor' 19350 ); 19351 19352 /** 19353 * Text class 19354 * @class fabric.Text 19355 * @extends fabric.Object 19356 * @return {fabric.Text} thisArg 19357 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text} 19358 * @see {@link fabric.Text#initialize} for constructor definition 19359 */ 19360 fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { 19361 19362 /** 19363 * Properties which when set cause object to change dimensions 19364 * @type Object 19365 * @private 19366 */ 19367 _dimensionAffectingProps: { 19368 fontSize: true, 19369 fontWeight: true, 19370 fontFamily: true, 19371 fontStyle: true, 19372 lineHeight: true, 19373 stroke: true, 19374 strokeWidth: true, 19375 text: true, 19376 textAlign: true 19377 }, 19378 19379 /** 19380 * @private 19381 */ 19382 _reNewline: /\r?\n/, 19383 19384 /** 19385 * Retrieves object's fontSize 19386 * @method getFontSize 19387 * @memberOf fabric.Text.prototype 19388 * @return {String} Font size (in pixels) 19389 */ 19390 19391 /** 19392 * Sets object's fontSize 19393 * @method setFontSize 19394 * @memberOf fabric.Text.prototype 19395 * @param {Number} fontSize Font size (in pixels) 19396 * @return {fabric.Text} 19397 * @chainable 19398 */ 19399 19400 /** 19401 * Retrieves object's fontWeight 19402 * @method getFontWeight 19403 * @memberOf fabric.Text.prototype 19404 * @return {(String|Number)} Font weight 19405 */ 19406 19407 /** 19408 * Sets object's fontWeight 19409 * @method setFontWeight 19410 * @memberOf fabric.Text.prototype 19411 * @param {(Number|String)} fontWeight Font weight 19412 * @return {fabric.Text} 19413 * @chainable 19414 */ 19415 19416 /** 19417 * Retrieves object's fontFamily 19418 * @method getFontFamily 19419 * @memberOf fabric.Text.prototype 19420 * @return {String} Font family 19421 */ 19422 19423 /** 19424 * Sets object's fontFamily 19425 * @method setFontFamily 19426 * @memberOf fabric.Text.prototype 19427 * @param {String} fontFamily Font family 19428 * @return {fabric.Text} 19429 * @chainable 19430 */ 19431 19432 /** 19433 * Retrieves object's text 19434 * @method getText 19435 * @memberOf fabric.Text.prototype 19436 * @return {String} text 19437 */ 19438 19439 /** 19440 * Sets object's text 19441 * @method setText 19442 * @memberOf fabric.Text.prototype 19443 * @param {String} text Text 19444 * @return {fabric.Text} 19445 * @chainable 19446 */ 19447 19448 /** 19449 * Retrieves object's textDecoration 19450 * @method getTextDecoration 19451 * @memberOf fabric.Text.prototype 19452 * @return {String} Text decoration 19453 */ 19454 19455 /** 19456 * Sets object's textDecoration 19457 * @method setTextDecoration 19458 * @memberOf fabric.Text.prototype 19459 * @param {String} textDecoration Text decoration 19460 * @return {fabric.Text} 19461 * @chainable 19462 */ 19463 19464 /** 19465 * Retrieves object's fontStyle 19466 * @method getFontStyle 19467 * @memberOf fabric.Text.prototype 19468 * @return {String} Font style 19469 */ 19470 19471 /** 19472 * Sets object's fontStyle 19473 * @method setFontStyle 19474 * @memberOf fabric.Text.prototype 19475 * @param {String} fontStyle Font style 19476 * @return {fabric.Text} 19477 * @chainable 19478 */ 19479 19480 /** 19481 * Retrieves object's lineHeight 19482 * @method getLineHeight 19483 * @memberOf fabric.Text.prototype 19484 * @return {Number} Line height 19485 */ 19486 19487 /** 19488 * Sets object's lineHeight 19489 * @method setLineHeight 19490 * @memberOf fabric.Text.prototype 19491 * @param {Number} lineHeight Line height 19492 * @return {fabric.Text} 19493 * @chainable 19494 */ 19495 19496 /** 19497 * Retrieves object's textAlign 19498 * @method getTextAlign 19499 * @memberOf fabric.Text.prototype 19500 * @return {String} Text alignment 19501 */ 19502 19503 /** 19504 * Sets object's textAlign 19505 * @method setTextAlign 19506 * @memberOf fabric.Text.prototype 19507 * @param {String} textAlign Text alignment 19508 * @return {fabric.Text} 19509 * @chainable 19510 */ 19511 19512 /** 19513 * Retrieves object's textBackgroundColor 19514 * @method getTextBackgroundColor 19515 * @memberOf fabric.Text.prototype 19516 * @return {String} Text background color 19517 */ 19518 19519 /** 19520 * Sets object's textBackgroundColor 19521 * @method setTextBackgroundColor 19522 * @memberOf fabric.Text.prototype 19523 * @param {String} textBackgroundColor Text background color 19524 * @return {fabric.Text} 19525 * @chainable 19526 */ 19527 19528 /** 19529 * Type of an object 19530 * @type String 19531 * @default 19532 */ 19533 type: 'text', 19534 19535 /** 19536 * Font size (in pixels) 19537 * @type Number 19538 * @default 19539 */ 19540 fontSize: 40, 19541 19542 /** 19543 * Font weight (e.g. bold, normal, 400, 600, 800) 19544 * @type {(Number|String)} 19545 * @default 19546 */ 19547 fontWeight: 'normal', 19548 19549 /** 19550 * Font family 19551 * @type String 19552 * @default 19553 */ 19554 fontFamily: 'Times New Roman', 19555 19556 /** 19557 * Text decoration Possible values: "", "underline", "overline" or "line-through". 19558 * @type String 19559 * @default 19560 */ 19561 textDecoration: '', 19562 19563 /** 19564 * Text alignment. Possible values: "left", "center", or "right". 19565 * @type String 19566 * @default 19567 */ 19568 textAlign: 'left', 19569 19570 /** 19571 * Font style . Possible values: "", "normal", "italic" or "oblique". 19572 * @type String 19573 * @default 19574 */ 19575 fontStyle: '', 19576 19577 /** 19578 * Line height 19579 * @type Number 19580 * @default 19581 */ 19582 lineHeight: 1.16, 19583 19584 /** 19585 * Background color of text lines 19586 * @type String 19587 * @default 19588 */ 19589 textBackgroundColor: '', 19590 19591 /** 19592 * List of properties to consider when checking if 19593 * state of an object is changed ({@link fabric.Object#hasStateChanged}) 19594 * as well as for history (undo/redo) purposes 19595 * @type Array 19596 */ 19597 stateProperties: stateProperties, 19598 19599 /** 19600 * When defined, an object is rendered via stroke and this property specifies its color. 19601 * <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6 19602 * @type String 19603 * @default 19604 */ 19605 stroke: null, 19606 19607 /** 19608 * Shadow object representing shadow of this shape. 19609 * <b>Backwards incompatibility note:</b> This property was named "textShadow" (String) until v1.2.11 19610 * @type fabric.Shadow 19611 * @default 19612 */ 19613 shadow: null, 19614 19615 /** 19616 * @private 19617 */ 19618 _fontSizeFraction: 0.25, 19619 19620 /** 19621 * Text Line proportion to font Size (in pixels) 19622 * @type Number 19623 * @default 19624 */ 19625 _fontSizeMult: 1.13, 19626 19627 /** 19628 * Constructor 19629 * @param {String} text Text string 19630 * @param {Object} [options] Options object 19631 * @return {fabric.Text} thisArg 19632 */ 19633 initialize: function(text, options) { 19634 options = options || { }; 19635 this.text = text; 19636 this.__skipDimension = true; 19637 this.setOptions(options); 19638 this.__skipDimension = false; 19639 this._initDimensions(); 19640 }, 19641 19642 /** 19643 * Renders text object on offscreen canvas, so that it would get dimensions 19644 * @private 19645 */ 19646 _initDimensions: function(ctx) { 19647 if (this.__skipDimension) { 19648 return; 19649 } 19650 if (!ctx) { 19651 ctx = fabric.util.createCanvasElement().getContext('2d'); 19652 this._setTextStyles(ctx); 19653 } 19654 this._textLines = this.text.split(this._reNewline); 19655 this._clearCache(); 19656 var currentTextAlign = this.textAlign; 19657 this.textAlign = 'left'; 19658 this.width = this._getTextWidth(ctx); 19659 this.textAlign = currentTextAlign; 19660 this.height = this._getTextHeight(ctx); 19661 }, 19662 19663 /** 19664 * Returns string representation of an instance 19665 * @return {String} String representation of text object 19666 */ 19667 toString: function() { 19668 return '#<fabric.Text (' + this.complexity() + 19669 '): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>'; 19670 }, 19671 19672 /** 19673 * @private 19674 * @param {CanvasRenderingContext2D} ctx Context to render on 19675 */ 19676 _render: function(ctx) { 19677 19678 this.clipTo && fabric.util.clipContext(this, ctx); 19679 19680 this._renderTextBackground(ctx); 19681 this._renderText(ctx); 19682 19683 this._renderTextDecoration(ctx); 19684 this.clipTo && ctx.restore(); 19685 }, 19686 19687 /** 19688 * @private 19689 * @param {CanvasRenderingContext2D} ctx Context to render on 19690 */ 19691 _renderText: function(ctx) { 19692 ctx.save(); 19693 this._translateForTextAlign(ctx); 19694 this._setOpacity(ctx); 19695 this._setShadow(ctx); 19696 this._setupCompositeOperation(ctx); 19697 this._renderTextFill(ctx); 19698 this._renderTextStroke(ctx); 19699 this._restoreCompositeOperation(ctx); 19700 this._removeShadow(ctx); 19701 ctx.restore(); 19702 }, 19703 19704 /** 19705 * @private 19706 * @param {CanvasRenderingContext2D} ctx Context to render on 19707 */ 19708 _translateForTextAlign: function(ctx) { 19709 if (this.textAlign !== 'left' && this.textAlign !== 'justify') { 19710 ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0); 19711 } 19712 }, 19713 19714 /** 19715 * @private 19716 * @param {CanvasRenderingContext2D} ctx Context to render on 19717 */ 19718 _setTextStyles: function(ctx) { 19719 ctx.textBaseline = 'alphabetic'; 19720 if (!this.skipTextAlign) { 19721 ctx.textAlign = this.textAlign; 19722 } 19723 ctx.font = this._getFontDeclaration(); 19724 }, 19725 19726 /** 19727 * @private 19728 * @param {CanvasRenderingContext2D} ctx Context to render on 19729 * @return {Number} Height of fabric.Text object 19730 */ 19731 _getTextHeight: function() { 19732 return this._textLines.length * this._getHeightOfLine(); 19733 }, 19734 19735 /** 19736 * @private 19737 * @param {CanvasRenderingContext2D} ctx Context to render on 19738 * @return {Number} Maximum width of fabric.Text object 19739 */ 19740 _getTextWidth: function(ctx) { 19741 var maxWidth = this._getLineWidth(ctx, 0); 19742 19743 for (var i = 1, len = this._textLines.length; i < len; i++) { 19744 var currentLineWidth = this._getLineWidth(ctx, i); 19745 if (currentLineWidth > maxWidth) { 19746 maxWidth = currentLineWidth; 19747 } 19748 } 19749 return maxWidth; 19750 }, 19751 19752 /** 19753 * @private 19754 * @param {String} method Method name ("fillText" or "strokeText") 19755 * @param {CanvasRenderingContext2D} ctx Context to render on 19756 * @param {String} chars Chars to render 19757 * @param {Number} left Left position of text 19758 * @param {Number} top Top position of text 19759 */ 19760 _renderChars: function(method, ctx, chars, left, top) { 19761 ctx[method](chars, left, top); 19762 }, 19763 19764 /** 19765 * @private 19766 * @param {String} method Method name ("fillText" or "strokeText") 19767 * @param {CanvasRenderingContext2D} ctx Context to render on 19768 * @param {String} line Text to render 19769 * @param {Number} left Left position of text 19770 * @param {Number} top Top position of text 19771 * @param {Number} lineIndex Index of a line in a text 19772 */ 19773 _renderTextLine: function(method, ctx, line, left, top, lineIndex) { 19774 // lift the line by quarter of fontSize 19775 top -= this.fontSize * this._fontSizeFraction; 19776 19777 // short-circuit 19778 if (this.textAlign !== 'justify') { 19779 this._renderChars(method, ctx, line, left, top, lineIndex); 19780 return; 19781 } 19782 19783 var lineWidth = this._getLineWidth(ctx, lineIndex), 19784 totalWidth = this.width; 19785 if (totalWidth >= lineWidth) { 19786 // stretch the line 19787 var words = line.split(/\s+/), 19788 wordsWidth = this._getWidthOfWords(ctx, line, lineIndex), 19789 widthDiff = totalWidth - wordsWidth, 19790 numSpaces = words.length - 1, 19791 spaceWidth = widthDiff / numSpaces, 19792 leftOffset = 0; 19793 19794 for (var i = 0, len = words.length; i < len; i++) { 19795 this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); 19796 leftOffset += ctx.measureText(words[i]).width + spaceWidth; 19797 } 19798 } 19799 else { 19800 this._renderChars(method, ctx, line, left, top, lineIndex); 19801 } 19802 }, 19803 19804 /** 19805 * @private 19806 * @param {CanvasRenderingContext2D} ctx Context to render on 19807 * @param {Number} line 19808 */ 19809 _getWidthOfWords: function (ctx, line) { 19810 return ctx.measureText(line.replace(/\s+/g, '')).width; 19811 }, 19812 19813 /** 19814 * @private 19815 * @return {Number} Left offset 19816 */ 19817 _getLeftOffset: function() { 19818 return -this.width / 2; 19819 }, 19820 19821 /** 19822 * @private 19823 * @return {Number} Top offset 19824 */ 19825 _getTopOffset: function() { 19826 return -this.height / 2; 19827 }, 19828 19829 /** 19830 * @private 19831 * @param {CanvasRenderingContext2D} ctx Context to render on 19832 */ 19833 _renderTextFill: function(ctx) { 19834 if (!this.fill && !this._skipFillStrokeCheck) { 19835 return; 19836 } 19837 19838 var lineHeights = 0; 19839 19840 for (var i = 0, len = this._textLines.length; i < len; i++) { 19841 var heightOfLine = this._getHeightOfLine(ctx, i), 19842 maxHeight = heightOfLine / this.lineHeight; 19843 19844 this._renderTextLine( 19845 'fillText', 19846 ctx, 19847 this._textLines[i], 19848 this._getLeftOffset(), 19849 this._getTopOffset() + lineHeights + maxHeight, 19850 i 19851 ); 19852 lineHeights += heightOfLine; 19853 } 19854 if (this.shadow && !this.shadow.affectStroke) { 19855 this._removeShadow(ctx); 19856 } 19857 }, 19858 19859 /** 19860 * @private 19861 * @param {CanvasRenderingContext2D} ctx Context to render on 19862 */ 19863 _renderTextStroke: function(ctx) { 19864 if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) { 19865 return; 19866 } 19867 19868 var lineHeights = 0; 19869 19870 ctx.save(); 19871 19872 if (this.strokeDashArray) { 19873 // Spec requires the concatenation of two copies the dash list when the number of elements is odd 19874 if (1 & this.strokeDashArray.length) { 19875 this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); 19876 } 19877 supportsLineDash && ctx.setLineDash(this.strokeDashArray); 19878 } 19879 19880 ctx.beginPath(); 19881 for (var i = 0, len = this._textLines.length; i < len; i++) { 19882 var heightOfLine = this._getHeightOfLine(ctx, i), 19883 maxHeight = heightOfLine / this.lineHeight; 19884 19885 this._renderTextLine( 19886 'strokeText', 19887 ctx, 19888 this._textLines[i], 19889 this._getLeftOffset(), 19890 this._getTopOffset() + lineHeights + maxHeight, 19891 i 19892 ); 19893 lineHeights += heightOfLine; 19894 } 19895 ctx.closePath(); 19896 ctx.restore(); 19897 }, 19898 19899 _getHeightOfLine: function() { 19900 return this.fontSize * this._fontSizeMult * this.lineHeight; 19901 }, 19902 19903 /** 19904 * @private 19905 * @param {CanvasRenderingContext2D} ctx Context to render on 19906 * @param {Array} textLines Array of all text lines 19907 */ 19908 _renderTextBackground: function(ctx) { 19909 this._renderTextBoxBackground(ctx); 19910 this._renderTextLinesBackground(ctx); 19911 }, 19912 19913 /** 19914 * @private 19915 * @param {CanvasRenderingContext2D} ctx Context to render on 19916 */ 19917 _renderTextBoxBackground: function(ctx) { 19918 if (!this.backgroundColor) { 19919 return; 19920 } 19921 19922 ctx.save(); 19923 ctx.fillStyle = this.backgroundColor; 19924 19925 ctx.fillRect( 19926 this._getLeftOffset(), 19927 this._getTopOffset(), 19928 this.width, 19929 this.height 19930 ); 19931 19932 ctx.restore(); 19933 }, 19934 19935 /** 19936 * @private 19937 * @param {CanvasRenderingContext2D} ctx Context to render on 19938 */ 19939 _renderTextLinesBackground: function(ctx) { 19940 var lineTopOffset = 0, heightOfLine = this._getHeightOfLine(); 19941 if (!this.textBackgroundColor) { 19942 return; 19943 } 19944 19945 ctx.save(); 19946 ctx.fillStyle = this.textBackgroundColor; 19947 19948 for (var i = 0, len = this._textLines.length; i < len; i++) { 19949 19950 if (this._textLines[i] !== '') { 19951 19952 var lineWidth = this._getLineWidth(ctx, i), 19953 lineLeftOffset = this._getLineLeftOffset(lineWidth); 19954 19955 ctx.fillRect( 19956 this._getLeftOffset() + lineLeftOffset, 19957 this._getTopOffset() + lineTopOffset, 19958 lineWidth, 19959 this.fontSize * this._fontSizeMult 19960 ); 19961 } 19962 lineTopOffset += heightOfLine; 19963 } 19964 ctx.restore(); 19965 }, 19966 19967 /** 19968 * @private 19969 * @param {Number} lineWidth Width of text line 19970 * @return {Number} Line left offset 19971 */ 19972 _getLineLeftOffset: function(lineWidth) { 19973 if (this.textAlign === 'center') { 19974 return (this.width - lineWidth) / 2; 19975 } 19976 if (this.textAlign === 'right') { 19977 return this.width - lineWidth; 19978 } 19979 return 0; 19980 }, 19981 19982 /** 19983 * @private 19984 */ 19985 _clearCache: function() { 19986 this.__lineWidths = [ ]; 19987 this.__lineHeights = [ ]; 19988 this.__lineOffsets = [ ]; 19989 }, 19990 19991 /** 19992 * @private 19993 */ 19994 _shouldClearCache: function() { 19995 var shouldClear = false; 19996 for (var prop in this._dimensionAffectingProps) { 19997 if (this['__' + prop] !== this[prop]) { 19998 this['__' + prop] = this[prop]; 19999 shouldClear = true; 20000 } 20001 } 20002 return shouldClear; 20003 }, 20004 20005 /** 20006 * @private 20007 * @param {CanvasRenderingContext2D} ctx Context to render on 20008 * @return {Number} Line width 20009 */ 20010 _getLineWidth: function(ctx, lineIndex) { 20011 if (this.__lineWidths[lineIndex]) { 20012 return this.__lineWidths[lineIndex]; 20013 } 20014 this.__lineWidths[lineIndex] = ctx.measureText(this._textLines[lineIndex]).width; 20015 return this.__lineWidths[lineIndex]; 20016 }, 20017 20018 /** 20019 * @private 20020 * @param {CanvasRenderingContext2D} ctx Context to render on 20021 */ 20022 _renderTextDecoration: function(ctx) { 20023 if (!this.textDecoration) { 20024 return; 20025 } 20026 20027 var halfOfVerticalBox = this.height / 2, 20028 _this = this, offsets = []; 20029 20030 /** @ignore */ 20031 function renderLinesAtOffset(offsets) { 20032 var i, lineHeight = 0, len, j, oLen; 20033 for (i = 0, len = _this._textLines.length; i < len; i++) { 20034 20035 var lineWidth = _this._getLineWidth(ctx, i), 20036 lineLeftOffset = _this._getLineLeftOffset(lineWidth), 20037 heightOfLine = _this._getHeightOfLine(ctx, i); 20038 20039 for (j = 0, oLen = offsets.length; j < oLen; j++) { 20040 ctx.fillRect( 20041 _this._getLeftOffset() + lineLeftOffset, 20042 lineHeight + (_this._fontSizeMult - 1 + offsets[j] ) * _this.fontSize - halfOfVerticalBox, 20043 lineWidth, 20044 _this.fontSize / 15); 20045 } 20046 lineHeight += heightOfLine; 20047 } 20048 } 20049 20050 if (this.textDecoration.indexOf('underline') > -1) { 20051 offsets.push(0.85); // 1 - 3/16 20052 } 20053 if (this.textDecoration.indexOf('line-through') > -1) { 20054 offsets.push(0.43); 20055 } 20056 if (this.textDecoration.indexOf('overline') > -1) { 20057 offsets.push(-0.12); 20058 } 20059 20060 if (offsets.length > 0) { 20061 renderLinesAtOffset(offsets); 20062 } 20063 }, 20064 20065 /** 20066 * @private 20067 */ 20068 _getFontDeclaration: function() { 20069 return [ 20070 // node-canvas needs "weight style", while browsers need "style weight" 20071 (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), 20072 (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), 20073 this.fontSize + 'px', 20074 (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) 20075 ].join(' '); 20076 }, 20077 20078 /** 20079 * Renders text instance on a specified context 20080 * @param {CanvasRenderingContext2D} ctx Context to render on 20081 */ 20082 render: function(ctx, noTransform) { 20083 // do not render if object is not visible 20084 if (!this.visible) { 20085 return; 20086 } 20087 20088 ctx.save(); 20089 this._setTextStyles(ctx); 20090 20091 if (this._shouldClearCache()) { 20092 this._initDimensions(ctx); 20093 } 20094 if (!noTransform) { 20095 this.transform(ctx); 20096 } 20097 this._setStrokeStyles(ctx); 20098 this._setFillStyles(ctx); 20099 if (this.transformMatrix) { 20100 ctx.transform.apply(ctx, this.transformMatrix); 20101 } 20102 if (this.group && this.group.type === 'path-group') { 20103 ctx.translate(this.left, this.top); 20104 } 20105 this._render(ctx); 20106 ctx.restore(); 20107 }, 20108 20109 /** 20110 * Returns object representation of an instance 20111 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 20112 * @return {Object} Object representation of an instance 20113 */ 20114 toObject: function(propertiesToInclude) { 20115 var object = extend(this.callSuper('toObject', propertiesToInclude), { 20116 text: this.text, 20117 fontSize: this.fontSize, 20118 fontWeight: this.fontWeight, 20119 fontFamily: this.fontFamily, 20120 fontStyle: this.fontStyle, 20121 lineHeight: this.lineHeight, 20122 textDecoration: this.textDecoration, 20123 textAlign: this.textAlign, 20124 textBackgroundColor: this.textBackgroundColor 20125 }); 20126 if (!this.includeDefaultValues) { 20127 this._removeDefaultValues(object); 20128 } 20129 return object; 20130 }, 20131 20132 /* _TO_SVG_START_ */ 20133 /** 20134 * Returns SVG representation of an instance 20135 * @param {Function} [reviver] Method for further parsing of svg representation. 20136 * @return {String} svg representation of an instance 20137 */ 20138 toSVG: function(reviver) { 20139 var markup = this._createBaseSVGMarkup(), 20140 offsets = this._getSVGLeftTopOffsets(this.ctx), 20141 textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); 20142 this._wrapSVGTextAndBg(markup, textAndBg); 20143 20144 return reviver ? reviver(markup.join('')) : markup.join(''); 20145 }, 20146 20147 /** 20148 * @private 20149 */ 20150 _getSVGLeftTopOffsets: function(ctx) { 20151 var lineTop = this._getHeightOfLine(ctx, 0), 20152 textLeft = -this.width / 2, 20153 textTop = 0; 20154 20155 return { 20156 textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0), 20157 textTop: textTop + (this.group && this.group.type === 'path-group' ? -this.top : 0), 20158 lineTop: lineTop 20159 }; 20160 }, 20161 20162 /** 20163 * @private 20164 */ 20165 _wrapSVGTextAndBg: function(markup, textAndBg) { 20166 markup.push( 20167 '\t<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n', 20168 textAndBg.textBgRects.join(''), 20169 '\t\t<text ', 20170 (this.fontFamily ? 'font-family="' + this.fontFamily.replace(/"/g, '\'') + '" ': ''), 20171 (this.fontSize ? 'font-size="' + this.fontSize + '" ': ''), 20172 (this.fontStyle ? 'font-style="' + this.fontStyle + '" ': ''), 20173 (this.fontWeight ? 'font-weight="' + this.fontWeight + '" ': ''), 20174 (this.textDecoration ? 'text-decoration="' + this.textDecoration + '" ': ''), 20175 'style="', this.getSvgStyles(), '" >', 20176 textAndBg.textSpans.join(''), 20177 '</text>\n', 20178 '\t</g>\n' 20179 ); 20180 }, 20181 20182 /** 20183 * @private 20184 * @param {Number} textTopOffset Text top offset 20185 * @param {Number} textLeftOffset Text left offset 20186 * @return {Object} 20187 */ 20188 _getSVGTextAndBg: function(textTopOffset, textLeftOffset) { 20189 var textSpans = [ ], 20190 textBgRects = [ ], 20191 height = 0; 20192 // bounding-box background 20193 this._setSVGBg(textBgRects); 20194 20195 // text and text-background 20196 for (var i = 0, len = this._textLines.length; i < len; i++) { 20197 if (this.textBackgroundColor) { 20198 this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height); 20199 } 20200 this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects); 20201 height += this._getHeightOfLine(this.ctx, i); 20202 } 20203 20204 return { 20205 textSpans: textSpans, 20206 textBgRects: textBgRects 20207 }; 20208 }, 20209 20210 _setSVGTextLineText: function(i, textSpans, height, textLeftOffset, textTopOffset) { 20211 var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction) 20212 - textTopOffset + height - this.height / 2; 20213 textSpans.push( 20214 '<tspan x="', 20215 toFixed(textLeftOffset + this._getLineLeftOffset(this.__lineWidths[i]), 4), '" ', 20216 'y="', 20217 toFixed(yPos, 4), 20218 '" ', 20219 // doing this on <tspan> elements since setting opacity 20220 // on containing <text> one doesn't work in Illustrator 20221 this._getFillAttributes(this.fill), '>', 20222 fabric.util.string.escapeXml(this._textLines[i]), 20223 '</tspan>' 20224 ); 20225 }, 20226 20227 _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, textTopOffset, height) { 20228 textBgRects.push( 20229 '\t\t<rect ', 20230 this._getFillAttributes(this.textBackgroundColor), 20231 ' x="', 20232 toFixed(textLeftOffset + this._getLineLeftOffset(this.__lineWidths[i]), 4), 20233 '" y="', 20234 toFixed(height - this.height / 2, 4), 20235 '" width="', 20236 toFixed(this.__lineWidths[i], 4), 20237 '" height="', 20238 toFixed(this._getHeightOfLine(this.ctx, i) / this.lineHeight, 4), 20239 '"></rect>\n'); 20240 }, 20241 20242 _setSVGBg: function(textBgRects) { 20243 if (this.backgroundColor) { 20244 textBgRects.push( 20245 '\t\t<rect ', 20246 this._getFillAttributes(this.backgroundColor), 20247 ' x="', 20248 toFixed(-this.width / 2, 4), 20249 '" y="', 20250 toFixed(-this.height / 2, 4), 20251 '" width="', 20252 toFixed(this.width, 4), 20253 '" height="', 20254 toFixed(this.height, 4), 20255 '"></rect>\n'); 20256 } 20257 }, 20258 20259 /** 20260 * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values 20261 * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 20262 * 20263 * @private 20264 * @param {Any} value 20265 * @return {String} 20266 */ 20267 _getFillAttributes: function(value) { 20268 var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; 20269 if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { 20270 return 'fill="' + value + '"'; 20271 } 20272 return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; 20273 }, 20274 /* _TO_SVG_END_ */ 20275 20276 /** 20277 * Sets specified property to a specified value 20278 * @param {String} key 20279 * @param {Any} value 20280 * @return {fabric.Text} thisArg 20281 * @chainable 20282 */ 20283 _set: function(key, value) { 20284 this.callSuper('_set', key, value); 20285 20286 if (key in this._dimensionAffectingProps) { 20287 this._initDimensions(); 20288 this.setCoords(); 20289 } 20290 }, 20291 20292 /** 20293 * Returns complexity of an instance 20294 * @return {Number} complexity 20295 */ 20296 complexity: function() { 20297 return 1; 20298 } 20299 }); 20300 20301 /* _FROM_SVG_START_ */ 20302 /** 20303 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) 20304 * @static 20305 * @memberOf fabric.Text 20306 * @see: http://www.w3.org/TR/SVG/text.html#TextElement 20307 */ 20308 fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( 20309 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); 20310 20311 /** 20312 * Default SVG font size 20313 * @static 20314 * @memberOf fabric.Text 20315 */ 20316 fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; 20317 20318 /** 20319 * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>) 20320 * @static 20321 * @memberOf fabric.Text 20322 * @param {SVGElement} element Element to parse 20323 * @param {Object} [options] Options object 20324 * @return {fabric.Text} Instance of fabric.Text 20325 */ 20326 fabric.Text.fromElement = function(element, options) { 20327 if (!element) { 20328 return null; 20329 } 20330 20331 var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); 20332 options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); 20333 20334 options.top = options.top || 0; 20335 options.left = options.left || 0; 20336 if ('dx' in parsedAttributes) { 20337 options.left += parsedAttributes.dx; 20338 } 20339 if ('dy' in parsedAttributes) { 20340 options.top += parsedAttributes.dy; 20341 } 20342 if (!('fontSize' in options)) { 20343 options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; 20344 } 20345 20346 if (!options.originX) { 20347 options.originX = 'left'; 20348 } 20349 var textContent = element.textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' '), 20350 text = new fabric.Text(textContent, options), 20351 /* 20352 Adjust positioning: 20353 x/y attributes in SVG correspond to the bottom-left corner of text bounding box 20354 top/left properties in Fabric correspond to center point of text bounding box 20355 */ 20356 offX = 0; 20357 20358 if (text.originX === 'left') { 20359 offX = text.getWidth() / 2; 20360 } 20361 if (text.originX === 'right') { 20362 offX = -text.getWidth() / 2; 20363 } 20364 text.set({ 20365 left: text.getLeft() + offX, 20366 top: text.getTop() - text.getHeight() / 2 + text.fontSize * (0.18 + text._fontSizeFraction) /* 0.3 is the old lineHeight */ 20367 }); 20368 20369 return text; 20370 }; 20371 /* _FROM_SVG_END_ */ 20372 20373 /** 20374 * Returns fabric.Text instance from an object representation 20375 * @static 20376 * @memberOf fabric.Text 20377 * @param {Object} object Object to create an instance from 20378 * @return {fabric.Text} Instance of fabric.Text 20379 */ 20380 fabric.Text.fromObject = function(object) { 20381 return new fabric.Text(object.text, clone(object)); 20382 }; 20383 20384 fabric.util.createAccessors(fabric.Text); 20385 20386})(typeof exports !== 'undefined' ? exports : this); 20387 20388 20389(function() { 20390 20391 var clone = fabric.util.object.clone; 20392 20393 /** 20394 * IText class (introduced in <b>v1.4</b>) Events are also fired with "text:" 20395 * prefix when observing canvas. 20396 * @class fabric.IText 20397 * @extends fabric.Text 20398 * @mixes fabric.Observable 20399 * 20400 * @fires changed 20401 * @fires selection:changed 20402 * @fires editing:entered 20403 * @fires editing:exited 20404 * 20405 * @return {fabric.IText} thisArg 20406 * @see {@link fabric.IText#initialize} for constructor definition 20407 * 20408 * <p>Supported key combinations:</p> 20409 * <pre> 20410 * Move cursor: left, right, up, down 20411 * Select character: shift + left, shift + right 20412 * Select text vertically: shift + up, shift + down 20413 * Move cursor by word: alt + left, alt + right 20414 * Select words: shift + alt + left, shift + alt + right 20415 * Move cursor to line start/end: cmd + left, cmd + right or home, end 20416 * Select till start/end of line: cmd + shift + left, cmd + shift + right or shift + home, shift + end 20417 * Jump to start/end of text: cmd + up, cmd + down 20418 * Select till start/end of text: cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown 20419 * Delete character: backspace 20420 * Delete word: alt + backspace 20421 * Delete line: cmd + backspace 20422 * Forward delete: delete 20423 * Copy text: ctrl/cmd + c 20424 * Paste text: ctrl/cmd + v 20425 * Cut text: ctrl/cmd + x 20426 * Select entire text: ctrl/cmd + a 20427 * Quit editing tab or esc 20428 * </pre> 20429 * 20430 * <p>Supported mouse/touch combination</p> 20431 * <pre> 20432 * Position cursor: click/touch 20433 * Create selection: click/touch & drag 20434 * Create selection: click & shift + click 20435 * Select word: double click 20436 * Select line: triple click 20437 * </pre> 20438 */ 20439 fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ { 20440 20441 /** 20442 * Type of an object 20443 * @type String 20444 * @default 20445 */ 20446 type: 'i-text', 20447 20448 /** 20449 * Index where text selection starts (or where cursor is when there is no selection) 20450 * @type Nubmer 20451 * @default 20452 */ 20453 selectionStart: 0, 20454 20455 /** 20456 * Index where text selection ends 20457 * @type Nubmer 20458 * @default 20459 */ 20460 selectionEnd: 0, 20461 20462 /** 20463 * Color of text selection 20464 * @type String 20465 * @default 20466 */ 20467 selectionColor: 'rgba(17,119,255,0.3)', 20468 20469 /** 20470 * Indicates whether text is in editing mode 20471 * @type Boolean 20472 * @default 20473 */ 20474 isEditing: false, 20475 20476 /** 20477 * Indicates whether a text can be edited 20478 * @type Boolean 20479 * @default 20480 */ 20481 editable: true, 20482 20483 /** 20484 * Border color of text object while it's in editing mode 20485 * @type String 20486 * @default 20487 */ 20488 editingBorderColor: 'rgba(102,153,255,0.25)', 20489 20490 /** 20491 * Width of cursor (in px) 20492 * @type Number 20493 * @default 20494 */ 20495 cursorWidth: 2, 20496 20497 /** 20498 * Color of default cursor (when not overwritten by character style) 20499 * @type String 20500 * @default 20501 */ 20502 cursorColor: '#333', 20503 20504 /** 20505 * Delay between cursor blink (in ms) 20506 * @type Number 20507 * @default 20508 */ 20509 cursorDelay: 1000, 20510 20511 /** 20512 * Duration of cursor fadein (in ms) 20513 * @type Number 20514 * @default 20515 */ 20516 cursorDuration: 600, 20517 20518 /** 20519 * Object containing character styles 20520 * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line) 20521 * @type Object 20522 * @default 20523 */ 20524 styles: null, 20525 20526 /** 20527 * Indicates whether internal text char widths can be cached 20528 * @type Boolean 20529 * @default 20530 */ 20531 caching: true, 20532 20533 /** 20534 * @private 20535 * @type Boolean 20536 * @default 20537 */ 20538 _skipFillStrokeCheck: false, 20539 20540 /** 20541 * @private 20542 */ 20543 _reSpace: /\s|\n/, 20544 20545 /** 20546 * @private 20547 */ 20548 _currentCursorOpacity: 0, 20549 20550 /** 20551 * @private 20552 */ 20553 _selectionDirection: null, 20554 20555 /** 20556 * @private 20557 */ 20558 _abortCursorAnimation: false, 20559 20560 /** 20561 * @private 20562 */ 20563 _charWidthsCache: { }, 20564 20565 /** 20566 * Constructor 20567 * @param {String} text Text string 20568 * @param {Object} [options] Options object 20569 * @return {fabric.IText} thisArg 20570 */ 20571 initialize: function(text, options) { 20572 this.styles = options ? (options.styles || { }) : { }; 20573 this.callSuper('initialize', text, options); 20574 this.initBehavior(); 20575 }, 20576 20577 /** 20578 * @private 20579 */ 20580 _clearCache: function() { 20581 this.callSuper('_clearCache'); 20582 this.__maxFontHeights = [ ]; 20583 this.__widthOfSpace = [ ]; 20584 }, 20585 20586 /** 20587 * Returns true if object has no styling 20588 */ 20589 isEmptyStyles: function() { 20590 if (!this.styles) { 20591 return true; 20592 } 20593 var obj = this.styles; 20594 20595 for (var p1 in obj) { 20596 for (var p2 in obj[p1]) { 20597 /*jshint unused:false */ 20598 for (var p3 in obj[p1][p2]) { 20599 return false; 20600 } 20601 } 20602 } 20603 return true; 20604 }, 20605 20606 /** 20607 * Sets selection start (left boundary of a selection) 20608 * @param {Number} index Index to set selection start to 20609 */ 20610 setSelectionStart: function(index) { 20611 index = Math.max(index, 0); 20612 if (this.selectionStart !== index) { 20613 this.fire('selection:changed'); 20614 this.canvas && this.canvas.fire('text:selection:changed', { target: this }); 20615 this.selectionStart = index; 20616 } 20617 this._updateTextarea(); 20618 }, 20619 20620 /** 20621 * Sets selection end (right boundary of a selection) 20622 * @param {Number} index Index to set selection end to 20623 */ 20624 setSelectionEnd: function(index) { 20625 index = Math.min(index, this.text.length); 20626 if (this.selectionEnd !== index) { 20627 this.fire('selection:changed'); 20628 this.canvas && this.canvas.fire('text:selection:changed', { target: this }); 20629 this.selectionEnd = index; 20630 } 20631 this._updateTextarea(); 20632 }, 20633 20634 /** 20635 * Gets style of a current selection/cursor (at the start position) 20636 * @param {Number} [startIndex] Start index to get styles at 20637 * @param {Number} [endIndex] End index to get styles at 20638 * @return {Object} styles Style object at a specified (or current) index 20639 */ 20640 getSelectionStyles: function(startIndex, endIndex) { 20641 20642 if (arguments.length === 2) { 20643 var styles = [ ]; 20644 for (var i = startIndex; i < endIndex; i++) { 20645 styles.push(this.getSelectionStyles(i)); 20646 } 20647 return styles; 20648 } 20649 20650 var loc = this.get2DCursorLocation(startIndex); 20651 if (this.styles[loc.lineIndex]) { 20652 return this.styles[loc.lineIndex][loc.charIndex] || { }; 20653 } 20654 20655 return { }; 20656 }, 20657 20658 /** 20659 * Sets style of a current selection 20660 * @param {Object} [styles] Styles object 20661 * @return {fabric.IText} thisArg 20662 * @chainable 20663 */ 20664 setSelectionStyles: function(styles) { 20665 if (this.selectionStart === this.selectionEnd) { 20666 this._extendStyles(this.selectionStart, styles); 20667 } 20668 else { 20669 for (var i = this.selectionStart; i < this.selectionEnd; i++) { 20670 this._extendStyles(i, styles); 20671 } 20672 } 20673 /* not included in _extendStyles to avoid clearing cache more than once */ 20674 this._clearCache(); 20675 return this; 20676 }, 20677 20678 /** 20679 * @private 20680 */ 20681 _extendStyles: function(index, styles) { 20682 var loc = this.get2DCursorLocation(index); 20683 20684 if (!this.styles[loc.lineIndex]) { 20685 this.styles[loc.lineIndex] = { }; 20686 } 20687 if (!this.styles[loc.lineIndex][loc.charIndex]) { 20688 this.styles[loc.lineIndex][loc.charIndex] = { }; 20689 } 20690 fabric.util.object.extend(this.styles[loc.lineIndex][loc.charIndex], styles); 20691 }, 20692 20693 /** 20694 * @private 20695 * @param {CanvasRenderingContext2D} ctx Context to render on 20696 */ 20697 _render: function(ctx) { 20698 this.callSuper('_render', ctx); 20699 this.ctx = ctx; 20700 this.isEditing && this.renderCursorOrSelection(); 20701 }, 20702 20703 /** 20704 * Renders cursor or selection (depending on what exists) 20705 */ 20706 renderCursorOrSelection: function() { 20707 if (!this.active) { 20708 return; 20709 } 20710 20711 var chars = this.text.split(''), 20712 boundaries, ctx; 20713 20714 if (this.canvas.contextTop) { 20715 ctx = this.canvas.contextTop; 20716 ctx.save(); 20717 ctx.transform.apply(ctx, this.canvas.viewportTransform); 20718 this.transform(ctx); 20719 } 20720 else { 20721 ctx = this.ctx; 20722 ctx.save(); 20723 } 20724 20725 if (this.selectionStart === this.selectionEnd) { 20726 boundaries = this._getCursorBoundaries(chars, 'cursor'); 20727 this.renderCursor(boundaries, ctx); 20728 } 20729 else { 20730 boundaries = this._getCursorBoundaries(chars, 'selection'); 20731 this.renderSelection(chars, boundaries, ctx); 20732 } 20733 20734 ctx.restore(); 20735 }, 20736 20737 /** 20738 * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start) 20739 * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. 20740 */ 20741 get2DCursorLocation: function(selectionStart) { 20742 if (typeof selectionStart === 'undefined') { 20743 selectionStart = this.selectionStart; 20744 } 20745 var textBeforeCursor = this.text.slice(0, selectionStart), 20746 linesBeforeCursor = textBeforeCursor.split(this._reNewline); 20747 20748 return { 20749 lineIndex: linesBeforeCursor.length - 1, 20750 charIndex: linesBeforeCursor[linesBeforeCursor.length - 1].length 20751 }; 20752 }, 20753 20754 /** 20755 * Returns complete style of char at the current cursor 20756 * @param {Number} lineIndex Line index 20757 * @param {Number} charIndex Char index 20758 * @return {Object} Character style 20759 */ 20760 getCurrentCharStyle: function(lineIndex, charIndex) { 20761 var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)]; 20762 20763 return { 20764 fontSize: style && style.fontSize || this.fontSize, 20765 fill: style && style.fill || this.fill, 20766 textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor, 20767 textDecoration: style && style.textDecoration || this.textDecoration, 20768 fontFamily: style && style.fontFamily || this.fontFamily, 20769 fontWeight: style && style.fontWeight || this.fontWeight, 20770 fontStyle: style && style.fontStyle || this.fontStyle, 20771 stroke: style && style.stroke || this.stroke, 20772 strokeWidth: style && style.strokeWidth || this.strokeWidth 20773 }; 20774 }, 20775 20776 /** 20777 * Returns fontSize of char at the current cursor 20778 * @param {Number} lineIndex Line index 20779 * @param {Number} charIndex Char index 20780 * @return {Number} Character font size 20781 */ 20782 getCurrentCharFontSize: function(lineIndex, charIndex) { 20783 return ( 20784 this.styles[lineIndex] && 20785 this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] && 20786 this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fontSize) || this.fontSize; 20787 }, 20788 20789 /** 20790 * Returns color (fill) of char at the current cursor 20791 * @param {Number} lineIndex Line index 20792 * @param {Number} charIndex Char index 20793 * @return {String} Character color (fill) 20794 */ 20795 getCurrentCharColor: function(lineIndex, charIndex) { 20796 return ( 20797 this.styles[lineIndex] && 20798 this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] && 20799 this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fill) || this.cursorColor; 20800 }, 20801 20802 /** 20803 * Returns cursor boundaries (left, top, leftOffset, topOffset) 20804 * @private 20805 * @param {Array} chars Array of characters 20806 * @param {String} typeOfBoundaries 20807 */ 20808 _getCursorBoundaries: function(chars, typeOfBoundaries) { 20809 20810 // left/top are left/top of entire text box 20811 // leftOffset/topOffset are offset from that left/top point of a text box 20812 20813 var left = Math.round(this._getLeftOffset()), 20814 top = this._getTopOffset(), 20815 20816 offsets = this._getCursorBoundariesOffsets( 20817 chars, typeOfBoundaries); 20818 20819 return { 20820 left: left, 20821 top: top, 20822 leftOffset: offsets.left + offsets.lineLeft, 20823 topOffset: offsets.top 20824 }; 20825 }, 20826 20827 /** 20828 * @private 20829 */ 20830 _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) { 20831 20832 var lineLeftOffset = 0, 20833 20834 lineIndex = 0, 20835 charIndex = 0, 20836 topOffset = 0, 20837 leftOffset = 0; 20838 20839 for (var i = 0; i < this.selectionStart; i++) { 20840 if (chars[i] === '\n') { 20841 leftOffset = 0; 20842 topOffset += this._getHeightOfLine(this.ctx, lineIndex); 20843 20844 lineIndex++; 20845 charIndex = 0; 20846 } 20847 else { 20848 leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex); 20849 charIndex++; 20850 } 20851 20852 lineLeftOffset = this._getCachedLineOffset(lineIndex); 20853 } 20854 if (typeOfBoundaries === 'cursor') { 20855 topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight 20856 - this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction); 20857 } 20858 20859 return { 20860 top: topOffset, 20861 left: leftOffset, 20862 lineLeft: lineLeftOffset 20863 }; 20864 }, 20865 20866 /** 20867 * @private 20868 */ 20869 _getCachedLineOffset: function(lineIndex) { 20870 var widthOfLine = this._getLineWidth(this.ctx, lineIndex); 20871 20872 return this.__lineOffsets[lineIndex] || 20873 (this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine)); 20874 }, 20875 20876 /** 20877 * Renders cursor 20878 * @param {Object} boundaries 20879 * @param {CanvasRenderingContext2D} ctx transformed context to draw on 20880 */ 20881 renderCursor: function(boundaries, ctx) { 20882 20883 var cursorLocation = this.get2DCursorLocation(), 20884 lineIndex = cursorLocation.lineIndex, 20885 charIndex = cursorLocation.charIndex, 20886 charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), 20887 leftOffset = (lineIndex === 0 && charIndex === 0) 20888 ? this._getCachedLineOffset(lineIndex) 20889 : boundaries.leftOffset; 20890 20891 ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); 20892 ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; 20893 20894 ctx.fillRect( 20895 boundaries.left + leftOffset, 20896 boundaries.top + boundaries.topOffset, 20897 this.cursorWidth / this.scaleX, 20898 charHeight); 20899 20900 }, 20901 20902 /** 20903 * Renders text selection 20904 * @param {Array} chars Array of characters 20905 * @param {Object} boundaries Object with left/top/leftOffset/topOffset 20906 * @param {CanvasRenderingContext2D} ctx transformed context to draw on 20907 */ 20908 renderSelection: function(chars, boundaries, ctx) { 20909 20910 ctx.fillStyle = this.selectionColor; 20911 20912 var start = this.get2DCursorLocation(this.selectionStart), 20913 end = this.get2DCursorLocation(this.selectionEnd), 20914 startLine = start.lineIndex, 20915 endLine = end.lineIndex; 20916 20917 for (var i = startLine; i <= endLine; i++) { 20918 var lineOffset = this._getCachedLineOffset(i) || 0, 20919 lineHeight = this._getHeightOfLine(this.ctx, i), 20920 boxWidth = 0, line = this._textLines[i]; 20921 20922 if (i === startLine) { 20923 for (var j = 0, len = line.length; j < len; j++) { 20924 if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { 20925 boxWidth += this._getWidthOfChar(ctx, line[j], i, j); 20926 } 20927 if (j < start.charIndex) { 20928 lineOffset += this._getWidthOfChar(ctx, line[j], i, j); 20929 } 20930 } 20931 } 20932 else if (i > startLine && i < endLine) { 20933 boxWidth += this._getLineWidth(ctx, i) || 5; 20934 } 20935 else if (i === endLine) { 20936 for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { 20937 boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2); 20938 } 20939 } 20940 20941 ctx.fillRect( 20942 boundaries.left + lineOffset, 20943 boundaries.top + boundaries.topOffset, 20944 boxWidth, 20945 lineHeight); 20946 20947 boundaries.topOffset += lineHeight; 20948 } 20949 }, 20950 20951 /** 20952 * @private 20953 * @param {String} method 20954 * @param {CanvasRenderingContext2D} ctx Context to render on 20955 */ 20956 _renderChars: function(method, ctx, line, left, top, lineIndex) { 20957 20958 if (this.isEmptyStyles()) { 20959 return this._renderCharsFast(method, ctx, line, left, top); 20960 } 20961 20962 this.skipTextAlign = true; 20963 20964 // set proper box offset 20965 left -= this.textAlign === 'center' 20966 ? (this.width / 2) 20967 : (this.textAlign === 'right') 20968 ? this.width 20969 : 0; 20970 20971 // set proper line offset 20972 var lineHeight = this._getHeightOfLine(ctx, lineIndex), 20973 lineLeftOffset = this._getCachedLineOffset(lineIndex), 20974 chars = line.split(''), 20975 prevStyle, 20976 charsToRender = ''; 20977 20978 left += lineLeftOffset || 0; 20979 20980 ctx.save(); 20981 top -= lineHeight / this.lineHeight * this._fontSizeFraction; 20982 for (var i = 0, len = chars.length; i <= len; i++) { 20983 prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); 20984 var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); 20985 20986 if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { 20987 this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); 20988 charsToRender = ''; 20989 prevStyle = thisStyle; 20990 } 20991 charsToRender += chars[i]; 20992 } 20993 20994 ctx.restore(); 20995 }, 20996 20997 /** 20998 * @private 20999 * @param {String} method 21000 * @param {CanvasRenderingContext2D} ctx Context to render on 21001 * @param {String} line Content of the line 21002 * @param {Number} left Left coordinate 21003 * @param {Number} top Top coordinate 21004 */ 21005 _renderCharsFast: function(method, ctx, line, left, top) { 21006 this.skipTextAlign = false; 21007 21008 if (method === 'fillText' && this.fill) { 21009 this.callSuper('_renderChars', method, ctx, line, left, top); 21010 } 21011 if (method === 'strokeText' && ((this.stroke && this.strokeWidth > 0) || this.skipFillStrokeCheck)) { 21012 this.callSuper('_renderChars', method, ctx, line, left, top); 21013 } 21014 }, 21015 21016 /** 21017 * @private 21018 * @param {String} method 21019 * @param {CanvasRenderingContext2D} ctx Context to render on 21020 * @param {Number} lineIndex 21021 * @param {Number} i 21022 * @param {String} _char 21023 * @param {Number} left Left coordinate 21024 * @param {Number} top Top coordinate 21025 * @param {Number} lineHeight Height of the line 21026 */ 21027 _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) { 21028 var decl, charWidth, charHeight, 21029 offset = this._fontSizeFraction * lineHeight / this.lineHeight; 21030 21031 if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) { 21032 21033 var shouldStroke = decl.stroke || this.stroke, 21034 shouldFill = decl.fill || this.fill; 21035 21036 ctx.save(); 21037 charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl); 21038 charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i); 21039 21040 if (shouldFill) { 21041 ctx.fillText(_char, left, top); 21042 } 21043 if (shouldStroke) { 21044 ctx.strokeText(_char, left, top); 21045 } 21046 21047 this._renderCharDecoration(ctx, decl, left, top, offset, charWidth, charHeight); 21048 ctx.restore(); 21049 21050 ctx.translate(charWidth, 0); 21051 } 21052 else { 21053 if (method === 'strokeText' && this.stroke) { 21054 ctx[method](_char, left, top); 21055 } 21056 if (method === 'fillText' && this.fill) { 21057 ctx[method](_char, left, top); 21058 } 21059 charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i); 21060 this._renderCharDecoration(ctx, null, left, top, offset, charWidth, this.fontSize); 21061 21062 ctx.translate(ctx.measureText(_char).width, 0); 21063 } 21064 }, 21065 21066 /** 21067 * @private 21068 * @param {Object} prevStyle 21069 * @param {Object} thisStyle 21070 */ 21071 _hasStyleChanged: function(prevStyle, thisStyle) { 21072 return (prevStyle.fill !== thisStyle.fill || 21073 prevStyle.fontSize !== thisStyle.fontSize || 21074 prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || 21075 prevStyle.textDecoration !== thisStyle.textDecoration || 21076 prevStyle.fontFamily !== thisStyle.fontFamily || 21077 prevStyle.fontWeight !== thisStyle.fontWeight || 21078 prevStyle.fontStyle !== thisStyle.fontStyle || 21079 prevStyle.stroke !== thisStyle.stroke || 21080 prevStyle.strokeWidth !== thisStyle.strokeWidth 21081 ); 21082 }, 21083 21084 /** 21085 * @private 21086 * @param {CanvasRenderingContext2D} ctx Context to render on 21087 */ 21088 _renderCharDecoration: function(ctx, styleDeclaration, left, top, offset, charWidth, charHeight) { 21089 21090 var textDecoration = styleDeclaration 21091 ? (styleDeclaration.textDecoration || this.textDecoration) 21092 : this.textDecoration; 21093 21094 if (!textDecoration) { 21095 return; 21096 } 21097 21098 if (textDecoration.indexOf('underline') > -1) { 21099 ctx.fillRect( 21100 left, 21101 top + charHeight / 10, 21102 charWidth , 21103 charHeight / 15 21104 ); 21105 } 21106 if (textDecoration.indexOf('line-through') > -1) { 21107 ctx.fillRect( 21108 left, 21109 top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + charHeight / 15, 21110 charWidth, 21111 charHeight / 15 21112 ); 21113 } 21114 if (textDecoration.indexOf('overline') > -1) { 21115 ctx.fillRect( 21116 left, 21117 top - (this._fontSizeMult - this._fontSizeFraction) * charHeight, 21118 charWidth, 21119 charHeight / 15 21120 ); 21121 } 21122 }, 21123 21124 /** 21125 * @private 21126 * @param {String} method 21127 * @param {CanvasRenderingContext2D} ctx Context to render on 21128 * @param {String} line 21129 */ 21130 _renderTextLine: function(method, ctx, line, left, top, lineIndex) { 21131 // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine 21132 // the adding 0.03 is just to align text with itext by overlap test 21133 if (!this.isEmptyStyles()) { 21134 top += this.fontSize * (this._fontSizeFraction + 0.03); 21135 } 21136 this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex); 21137 }, 21138 21139 /** 21140 * @private 21141 * @param {CanvasRenderingContext2D} ctx Context to render on 21142 */ 21143 _renderTextDecoration: function(ctx) { 21144 if (this.isEmptyStyles()) { 21145 return this.callSuper('_renderTextDecoration', ctx); 21146 } 21147 }, 21148 21149 /** 21150 * @private 21151 * @param {CanvasRenderingContext2D} ctx Context to render on 21152 */ 21153 _renderTextLinesBackground: function(ctx) { 21154 if (!this.textBackgroundColor && !this.styles) { 21155 return; 21156 } 21157 21158 ctx.save(); 21159 21160 if (this.textBackgroundColor) { 21161 ctx.fillStyle = this.textBackgroundColor; 21162 } 21163 21164 var lineHeights = 0; 21165 21166 for (var i = 0, len = this._textLines.length; i < len; i++) { 21167 21168 var heightOfLine = this._getHeightOfLine(ctx, i); 21169 if (this._textLines[i] === '') { 21170 lineHeights += heightOfLine; 21171 continue; 21172 } 21173 21174 var lineWidth = this._getLineWidth(ctx, i), 21175 lineLeftOffset = this._getCachedLineOffset(i); 21176 21177 if (this.textBackgroundColor) { 21178 ctx.fillStyle = this.textBackgroundColor; 21179 21180 ctx.fillRect( 21181 this._getLeftOffset() + lineLeftOffset, 21182 this._getTopOffset() + lineHeights, 21183 lineWidth, 21184 heightOfLine / this.lineHeight 21185 ); 21186 } 21187 if (this.styles[i]) { 21188 for (var j = 0, jlen = this._textLines[i].length; j < jlen; j++) { 21189 if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) { 21190 21191 var _char = this._textLines[i][j]; 21192 21193 ctx.fillStyle = this.styles[i][j].textBackgroundColor; 21194 21195 ctx.fillRect( 21196 this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j), 21197 this._getTopOffset() + lineHeights, 21198 this._getWidthOfChar(ctx, _char, i, j) + 1, 21199 heightOfLine / this.lineHeight 21200 ); 21201 } 21202 } 21203 } 21204 lineHeights += heightOfLine; 21205 } 21206 ctx.restore(); 21207 }, 21208 21209 /** 21210 * @private 21211 */ 21212 _getCacheProp: function(_char, styleDeclaration) { 21213 return _char + 21214 styleDeclaration.fontFamily + 21215 styleDeclaration.fontSize + 21216 styleDeclaration.fontWeight + 21217 styleDeclaration.fontStyle + 21218 styleDeclaration.shadow; 21219 }, 21220 21221 /** 21222 * @private 21223 * @param {CanvasRenderingContext2D} ctx Context to render on 21224 * @param {String} _char 21225 * @param {Number} lineIndex 21226 * @param {Number} charIndex 21227 * @param {Object} [decl] 21228 */ 21229 _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) { 21230 var styleDeclaration = decl || 21231 (this.styles[lineIndex] && 21232 this.styles[lineIndex][charIndex]); 21233 21234 if (styleDeclaration) { 21235 // cloning so that original style object is not polluted with following font declarations 21236 styleDeclaration = clone(styleDeclaration); 21237 } 21238 else { 21239 styleDeclaration = { }; 21240 } 21241 21242 this._applyFontStyles(styleDeclaration); 21243 21244 var cacheProp = this._getCacheProp(_char, styleDeclaration); 21245 21246 // short-circuit if no styles 21247 if (this.isEmptyStyles() && this._charWidthsCache[cacheProp] && this.caching) { 21248 return this._charWidthsCache[cacheProp]; 21249 } 21250 21251 if (typeof styleDeclaration.shadow === 'string') { 21252 styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow); 21253 } 21254 21255 var fill = styleDeclaration.fill || this.fill; 21256 ctx.fillStyle = fill.toLive 21257 ? fill.toLive(ctx, this) 21258 : fill; 21259 21260 if (styleDeclaration.stroke) { 21261 ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive) 21262 ? styleDeclaration.stroke.toLive(ctx, this) 21263 : styleDeclaration.stroke; 21264 } 21265 21266 ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth; 21267 ctx.font = this._getFontDeclaration.call(styleDeclaration); 21268 this._setShadow.call(styleDeclaration, ctx); 21269 21270 if (!this.caching) { 21271 return ctx.measureText(_char).width; 21272 } 21273 21274 if (!this._charWidthsCache[cacheProp]) { 21275 this._charWidthsCache[cacheProp] = ctx.measureText(_char).width; 21276 } 21277 21278 return this._charWidthsCache[cacheProp]; 21279 }, 21280 21281 /** 21282 * @private 21283 * @param {Object} styleDeclaration 21284 */ 21285 _applyFontStyles: function(styleDeclaration) { 21286 if (!styleDeclaration.fontFamily) { 21287 styleDeclaration.fontFamily = this.fontFamily; 21288 } 21289 if (!styleDeclaration.fontSize) { 21290 styleDeclaration.fontSize = this.fontSize; 21291 } 21292 if (!styleDeclaration.fontWeight) { 21293 styleDeclaration.fontWeight = this.fontWeight; 21294 } 21295 if (!styleDeclaration.fontStyle) { 21296 styleDeclaration.fontStyle = this.fontStyle; 21297 } 21298 }, 21299 21300 /** 21301 * @private 21302 * @param {Number} lineIndex 21303 * @param {Number} charIndex 21304 */ 21305 _getStyleDeclaration: function(lineIndex, charIndex) { 21306 return (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) 21307 ? clone(this.styles[lineIndex][charIndex]) 21308 : { }; 21309 }, 21310 21311 /** 21312 * @private 21313 * @param {CanvasRenderingContext2D} ctx Context to render on 21314 */ 21315 _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { 21316 if (this.textAlign === 'justify' && /\s/.test(_char)) { 21317 return this._getWidthOfSpace(ctx, lineIndex); 21318 } 21319 21320 var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex); 21321 this._applyFontStyles(styleDeclaration); 21322 var cacheProp = this._getCacheProp(_char, styleDeclaration); 21323 21324 if (this._charWidthsCache[cacheProp] && this.caching) { 21325 return this._charWidthsCache[cacheProp]; 21326 } 21327 else if (ctx) { 21328 ctx.save(); 21329 var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); 21330 ctx.restore(); 21331 return width; 21332 } 21333 }, 21334 21335 /** 21336 * @private 21337 * @param {CanvasRenderingContext2D} ctx Context to render on 21338 */ 21339 _getHeightOfChar: function(ctx, _char, lineIndex, charIndex) { 21340 if (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) { 21341 return this.styles[lineIndex][charIndex].fontSize || this.fontSize; 21342 } 21343 return this.fontSize; 21344 }, 21345 21346 /** 21347 * @private 21348 * @param {CanvasRenderingContext2D} ctx Context to render on 21349 */ 21350 _getHeightOfCharAt: function(ctx, lineIndex, charIndex) { 21351 var _char = this._textLines[lineIndex][charIndex]; 21352 return this._getHeightOfChar(ctx, _char, lineIndex, charIndex); 21353 }, 21354 21355 /** 21356 * @private 21357 * @param {CanvasRenderingContext2D} ctx Context to render on 21358 */ 21359 _getWidthOfCharsAt: function(ctx, lineIndex, charIndex) { 21360 var width = 0, i, _char; 21361 for (i = 0; i < charIndex; i++) { 21362 _char = this._textLines[lineIndex][i]; 21363 width += this._getWidthOfChar(ctx, _char, lineIndex, i); 21364 } 21365 return width; 21366 }, 21367 21368 /** 21369 * @private 21370 * @param {CanvasRenderingContext2D} ctx Context to render on 21371 */ 21372 _getLineWidth: function(ctx, lineIndex) { 21373 if (this.__lineWidths[lineIndex]) { 21374 return this.__lineWidths[lineIndex]; 21375 } 21376 this.__lineWidths[lineIndex] = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length); 21377 return this.__lineWidths[lineIndex]; 21378 }, 21379 21380 /** 21381 * @private 21382 * @param {CanvasRenderingContext2D} ctx Context to render on 21383 * @param {Number} lineIndex 21384 */ 21385 _getWidthOfSpace: function (ctx, lineIndex) { 21386 if (this.__widthOfSpace[lineIndex]) { 21387 return this.__widthOfSpace[lineIndex]; 21388 } 21389 var line = this._textLines[lineIndex], 21390 wordsWidth = this._getWidthOfWords(ctx, line, lineIndex), 21391 widthDiff = this.width - wordsWidth, 21392 numSpaces = line.length - line.replace(/\s+/g, '').length, 21393 width = widthDiff / numSpaces; 21394 this.__widthOfSpace[lineIndex] = width; 21395 return width; 21396 }, 21397 21398 /** 21399 * @private 21400 * @param {CanvasRenderingContext2D} ctx Context to render on 21401 * @param {Number} line 21402 * @param {Number} lineIndex 21403 */ 21404 _getWidthOfWords: function (ctx, line, lineIndex) { 21405 var width = 0; 21406 21407 for (var charIndex = 0; charIndex < line.length; charIndex++) { 21408 var _char = line[charIndex]; 21409 21410 if (!_char.match(/\s/)) { 21411 width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex); 21412 } 21413 } 21414 21415 return width; 21416 }, 21417 21418 /** 21419 * @private 21420 * @param {CanvasRenderingContext2D} ctx Context to render on 21421 */ 21422 _getHeightOfLine: function(ctx, lineIndex) { 21423 if (this.__lineHeights[lineIndex]) { 21424 return this.__lineHeights[lineIndex]; 21425 } 21426 21427 var line = this._textLines[lineIndex], 21428 maxHeight = this._getHeightOfChar(ctx, line[0], lineIndex, 0); 21429 21430 for (var i = 1, len = line.length; i < len; i++) { 21431 var currentCharHeight = this._getHeightOfChar(ctx, line[i], lineIndex, i); 21432 if (currentCharHeight > maxHeight) { 21433 maxHeight = currentCharHeight; 21434 } 21435 } 21436 this.__maxFontHeights[lineIndex] = maxHeight; 21437 this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; 21438 return this.__lineHeights[lineIndex]; 21439 }, 21440 21441 /** 21442 * @private 21443 * @param {CanvasRenderingContext2D} ctx Context to render on 21444 */ 21445 _getTextHeight: function(ctx) { 21446 var height = 0; 21447 for (var i = 0, len = this._textLines.length; i < len; i++) { 21448 height += this._getHeightOfLine(ctx, i); 21449 } 21450 return height; 21451 }, 21452 21453 /** 21454 * This method is overwritten to account for different top offset 21455 * @private 21456 */ 21457 _renderTextBoxBackground: function(ctx) { 21458 if (!this.backgroundColor) { 21459 return; 21460 } 21461 21462 ctx.save(); 21463 ctx.fillStyle = this.backgroundColor; 21464 21465 ctx.fillRect( 21466 this._getLeftOffset(), 21467 this._getTopOffset(), 21468 this.width, 21469 this.height 21470 ); 21471 21472 ctx.restore(); 21473 }, 21474 21475 /** 21476 * Returns object representation of an instance 21477 * @method toObject 21478 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 21479 * @return {Object} object representation of an instance 21480 */ 21481 toObject: function(propertiesToInclude) { 21482 return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { 21483 styles: clone(this.styles) 21484 }); 21485 } 21486 }); 21487 21488 /** 21489 * Returns fabric.IText instance from an object representation 21490 * @static 21491 * @memberOf fabric.IText 21492 * @param {Object} object Object to create an instance from 21493 * @return {fabric.IText} instance of fabric.IText 21494 */ 21495 fabric.IText.fromObject = function(object) { 21496 return new fabric.IText(object.text, clone(object)); 21497 }; 21498})(); 21499 21500 21501(function() { 21502 21503 var clone = fabric.util.object.clone; 21504 21505 fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { 21506 21507 /** 21508 * Initializes all the interactive behavior of IText 21509 */ 21510 initBehavior: function() { 21511 this.initAddedHandler(); 21512 this.initRemovedHandler(); 21513 this.initCursorSelectionHandlers(); 21514 this.initDoubleClickSimulation(); 21515 }, 21516 21517 /** 21518 * Initializes "selected" event handler 21519 */ 21520 initSelectedHandler: function() { 21521 this.on('selected', function() { 21522 21523 var _this = this; 21524 setTimeout(function() { 21525 _this.selected = true; 21526 }, 100); 21527 }); 21528 }, 21529 21530 /** 21531 * Initializes "added" event handler 21532 */ 21533 initAddedHandler: function() { 21534 var _this = this; 21535 this.on('added', function() { 21536 if (this.canvas && !this.canvas._hasITextHandlers) { 21537 this.canvas._hasITextHandlers = true; 21538 this._initCanvasHandlers(); 21539 } 21540 21541 // Track IText instances per-canvas. Only register in this array once added 21542 // to a canvas; we don't want to leak a reference to the instance forever 21543 // simply because it existed at some point. 21544 // 21545 // (Might be added to a collection, but not on a canvas.) 21546 if (_this.canvas) { 21547 _this.canvas._iTextInstances = _this.canvas._iTextInstances || []; 21548 _this.canvas._iTextInstances.push(_this); 21549 } 21550 }); 21551 }, 21552 21553 initRemovedHandler: function() { 21554 var _this = this; 21555 this.on('removed', function() { 21556 // (Might be removed from a collection, but not on a canvas.) 21557 if (_this.canvas) { 21558 _this.canvas._iTextInstances = _this.canvas._iTextInstances || []; 21559 fabric.util.removeFromArray(_this.canvas._iTextInstances, _this); 21560 } 21561 }); 21562 }, 21563 21564 /** 21565 * @private 21566 */ 21567 _initCanvasHandlers: function() { 21568 var _this = this; 21569 21570 this.canvas.on('selection:cleared', function() { 21571 fabric.IText.prototype.exitEditingOnOthers(_this.canvas); 21572 }); 21573 21574 this.canvas.on('mouse:up', function() { 21575 if (_this.canvas._iTextInstances) { 21576 _this.canvas._iTextInstances.forEach(function(obj) { 21577 obj.__isMousedown = false; 21578 }); 21579 } 21580 }); 21581 21582 this.canvas.on('object:selected', function() { 21583 fabric.IText.prototype.exitEditingOnOthers(_this.canvas); 21584 }); 21585 }, 21586 21587 /** 21588 * @private 21589 */ 21590 _tick: function() { 21591 this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete'); 21592 }, 21593 21594 /** 21595 * @private 21596 */ 21597 _animateCursor: function(obj, targetOpacity, duration, completeMethod) { 21598 21599 var tickState; 21600 21601 tickState = { 21602 isAborted: false, 21603 abort: function() { 21604 this.isAborted = true; 21605 }, 21606 }; 21607 21608 obj.animate('_currentCursorOpacity', targetOpacity, { 21609 duration: duration, 21610 onComplete: function() { 21611 if (!tickState.isAborted) { 21612 obj[completeMethod](); 21613 } 21614 }, 21615 onChange: function() { 21616 if (obj.canvas) { 21617 obj.canvas.clearContext(obj.canvas.contextTop || obj.ctx); 21618 obj.renderCursorOrSelection(); 21619 } 21620 }, 21621 abort: function() { 21622 return tickState.isAborted; 21623 } 21624 }); 21625 return tickState; 21626 }, 21627 21628 /** 21629 * @private 21630 */ 21631 _onTickComplete: function() { 21632 21633 var _this = this; 21634 21635 if (this._cursorTimeout1) { 21636 clearTimeout(this._cursorTimeout1); 21637 } 21638 this._cursorTimeout1 = setTimeout(function() { 21639 _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick'); 21640 }, 100); 21641 }, 21642 21643 /** 21644 * Initializes delayed cursor 21645 */ 21646 initDelayedCursor: function(restart) { 21647 var _this = this, 21648 delay = restart ? 0 : this.cursorDelay; 21649 21650 this._currentTickState && this._currentTickState.abort(); 21651 this._currentTickCompleteState && this._currentTickCompleteState.abort(); 21652 clearTimeout(this._cursorTimeout1); 21653 this._currentCursorOpacity = 1; 21654 if (this.canvas) { 21655 this.canvas.clearContext(this.canvas.contextTop || this.ctx); 21656 this.renderCursorOrSelection(); 21657 } 21658 if (this._cursorTimeout2) { 21659 clearTimeout(this._cursorTimeout2); 21660 } 21661 this._cursorTimeout2 = setTimeout(function() { 21662 _this._tick(); 21663 }, delay); 21664 }, 21665 21666 /** 21667 * Aborts cursor animation and clears all timeouts 21668 */ 21669 abortCursorAnimation: function() { 21670 this._currentTickState && this._currentTickState.abort(); 21671 this._currentTickCompleteState && this._currentTickCompleteState.abort(); 21672 21673 clearTimeout(this._cursorTimeout1); 21674 clearTimeout(this._cursorTimeout2); 21675 21676 this._currentCursorOpacity = 0; 21677 this.canvas && this.canvas.clearContext(this.canvas.contextTop || this.ctx); 21678 }, 21679 21680 /** 21681 * Selects entire text 21682 */ 21683 selectAll: function() { 21684 this.setSelectionStart(0); 21685 this.setSelectionEnd(this.text.length); 21686 }, 21687 21688 /** 21689 * Returns selected text 21690 * @return {String} 21691 */ 21692 getSelectedText: function() { 21693 return this.text.slice(this.selectionStart, this.selectionEnd); 21694 }, 21695 21696 /** 21697 * Find new selection index representing start of current word according to current selection index 21698 * @param {Number} startFrom Surrent selection index 21699 * @return {Number} New selection index 21700 */ 21701 findWordBoundaryLeft: function(startFrom) { 21702 var offset = 0, index = startFrom - 1; 21703 21704 // remove space before cursor first 21705 if (this._reSpace.test(this.text.charAt(index))) { 21706 while (this._reSpace.test(this.text.charAt(index))) { 21707 offset++; 21708 index--; 21709 } 21710 } 21711 while (/\S/.test(this.text.charAt(index)) && index > -1) { 21712 offset++; 21713 index--; 21714 } 21715 21716 return startFrom - offset; 21717 }, 21718 21719 /** 21720 * Find new selection index representing end of current word according to current selection index 21721 * @param {Number} startFrom Current selection index 21722 * @return {Number} New selection index 21723 */ 21724 findWordBoundaryRight: function(startFrom) { 21725 var offset = 0, index = startFrom; 21726 21727 // remove space after cursor first 21728 if (this._reSpace.test(this.text.charAt(index))) { 21729 while (this._reSpace.test(this.text.charAt(index))) { 21730 offset++; 21731 index++; 21732 } 21733 } 21734 while (/\S/.test(this.text.charAt(index)) && index < this.text.length) { 21735 offset++; 21736 index++; 21737 } 21738 21739 return startFrom + offset; 21740 }, 21741 21742 /** 21743 * Find new selection index representing start of current line according to current selection index 21744 * @param {Number} startFrom Current selection index 21745 * @return {Number} New selection index 21746 */ 21747 findLineBoundaryLeft: function(startFrom) { 21748 var offset = 0, index = startFrom - 1; 21749 21750 while (!/\n/.test(this.text.charAt(index)) && index > -1) { 21751 offset++; 21752 index--; 21753 } 21754 21755 return startFrom - offset; 21756 }, 21757 21758 /** 21759 * Find new selection index representing end of current line according to current selection index 21760 * @param {Number} startFrom Current selection index 21761 * @return {Number} New selection index 21762 */ 21763 findLineBoundaryRight: function(startFrom) { 21764 var offset = 0, index = startFrom; 21765 21766 while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) { 21767 offset++; 21768 index++; 21769 } 21770 21771 return startFrom + offset; 21772 }, 21773 21774 /** 21775 * Returns number of newlines in selected text 21776 * @return {Number} Number of newlines in selected text 21777 */ 21778 getNumNewLinesInSelectedText: function() { 21779 var selectedText = this.getSelectedText(), 21780 numNewLines = 0; 21781 21782 for (var i = 0, chars = selectedText.split(''), len = chars.length; i < len; i++) { 21783 if (chars[i] === '\n') { 21784 numNewLines++; 21785 } 21786 } 21787 return numNewLines; 21788 }, 21789 21790 /** 21791 * Finds index corresponding to beginning or end of a word 21792 * @param {Number} selectionStart Index of a character 21793 * @param {Number} direction: 1 or -1 21794 * @return {Number} Index of the beginning or end of a word 21795 */ 21796 searchWordBoundary: function(selectionStart, direction) { 21797 var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart, 21798 _char = this.text.charAt(index), 21799 reNonWord = /[ \n\.,;!\?\-]/; 21800 21801 while (!reNonWord.test(_char) && index > 0 && index < this.text.length) { 21802 index += direction; 21803 _char = this.text.charAt(index); 21804 } 21805 if (reNonWord.test(_char) && _char !== '\n') { 21806 index += direction === 1 ? 0 : 1; 21807 } 21808 return index; 21809 }, 21810 21811 /** 21812 * Selects a word based on the index 21813 * @param {Number} selectionStart Index of a character 21814 */ 21815 selectWord: function(selectionStart) { 21816 var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ 21817 newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ 21818 21819 this.setSelectionStart(newSelectionStart); 21820 this.setSelectionEnd(newSelectionEnd); 21821 }, 21822 21823 /** 21824 * Selects a line based on the index 21825 * @param {Number} selectionStart Index of a character 21826 */ 21827 selectLine: function(selectionStart) { 21828 var newSelectionStart = this.findLineBoundaryLeft(selectionStart), 21829 newSelectionEnd = this.findLineBoundaryRight(selectionStart); 21830 21831 this.setSelectionStart(newSelectionStart); 21832 this.setSelectionEnd(newSelectionEnd); 21833 }, 21834 21835 /** 21836 * Enters editing state 21837 * @return {fabric.IText} thisArg 21838 * @chainable 21839 */ 21840 enterEditing: function() { 21841 if (this.isEditing || !this.editable) { 21842 return; 21843 } 21844 21845 if (this.canvas) { 21846 this.exitEditingOnOthers(this.canvas); 21847 } 21848 21849 this.isEditing = true; 21850 21851 this.initHiddenTextarea(); 21852 this.hiddenTextarea.focus(); 21853 this._updateTextarea(); 21854 this._saveEditingProps(); 21855 this._setEditingProps(); 21856 21857 this._tick(); 21858 this.fire('editing:entered'); 21859 21860 if (!this.canvas) { 21861 return this; 21862 } 21863 21864 this.canvas.renderAll(); 21865 this.canvas.fire('text:editing:entered', { target: this }); 21866 this.initMouseMoveHandler(); 21867 return this; 21868 }, 21869 21870 exitEditingOnOthers: function(canvas) { 21871 if (canvas._iTextInstances) { 21872 canvas._iTextInstances.forEach(function(obj) { 21873 obj.selected = false; 21874 if (obj.isEditing) { 21875 obj.exitEditing(); 21876 } 21877 }); 21878 } 21879 }, 21880 21881 /** 21882 * Initializes "mousemove" event handler 21883 */ 21884 initMouseMoveHandler: function() { 21885 var _this = this; 21886 this.canvas.on('mouse:move', function(options) { 21887 if (!_this.__isMousedown || !_this.isEditing) { 21888 return; 21889 } 21890 21891 var newSelectionStart = _this.getSelectionStartFromPointer(options.e); 21892 if (newSelectionStart >= _this.__selectionStartOnMouseDown) { 21893 _this.setSelectionStart(_this.__selectionStartOnMouseDown); 21894 _this.setSelectionEnd(newSelectionStart); 21895 } 21896 else { 21897 _this.setSelectionStart(newSelectionStart); 21898 _this.setSelectionEnd(_this.__selectionStartOnMouseDown); 21899 } 21900 }); 21901 }, 21902 21903 /** 21904 * @private 21905 */ 21906 _setEditingProps: function() { 21907 this.hoverCursor = 'text'; 21908 21909 if (this.canvas) { 21910 this.canvas.defaultCursor = this.canvas.moveCursor = 'text'; 21911 } 21912 21913 this.borderColor = this.editingBorderColor; 21914 21915 this.hasControls = this.selectable = false; 21916 this.lockMovementX = this.lockMovementY = true; 21917 }, 21918 21919 /** 21920 * @private 21921 */ 21922 _updateTextarea: function() { 21923 if (!this.hiddenTextarea) { 21924 return; 21925 } 21926 21927 this.hiddenTextarea.value = this.text; 21928 this.hiddenTextarea.selectionStart = this.selectionStart; 21929 this.hiddenTextarea.selectionEnd = this.selectionEnd; 21930 }, 21931 21932 /** 21933 * @private 21934 */ 21935 _saveEditingProps: function() { 21936 this._savedProps = { 21937 hasControls: this.hasControls, 21938 borderColor: this.borderColor, 21939 lockMovementX: this.lockMovementX, 21940 lockMovementY: this.lockMovementY, 21941 hoverCursor: this.hoverCursor, 21942 defaultCursor: this.canvas && this.canvas.defaultCursor, 21943 moveCursor: this.canvas && this.canvas.moveCursor 21944 }; 21945 }, 21946 21947 /** 21948 * @private 21949 */ 21950 _restoreEditingProps: function() { 21951 if (!this._savedProps) { 21952 return; 21953 } 21954 21955 this.hoverCursor = this._savedProps.overCursor; 21956 this.hasControls = this._savedProps.hasControls; 21957 this.borderColor = this._savedProps.borderColor; 21958 this.lockMovementX = this._savedProps.lockMovementX; 21959 this.lockMovementY = this._savedProps.lockMovementY; 21960 21961 if (this.canvas) { 21962 this.canvas.defaultCursor = this._savedProps.defaultCursor; 21963 this.canvas.moveCursor = this._savedProps.moveCursor; 21964 } 21965 }, 21966 21967 /** 21968 * Exits from editing state 21969 * @return {fabric.IText} thisArg 21970 * @chainable 21971 */ 21972 exitEditing: function() { 21973 21974 this.selected = false; 21975 this.isEditing = false; 21976 this.selectable = true; 21977 21978 this.selectionEnd = this.selectionStart; 21979 this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); 21980 this.hiddenTextarea = null; 21981 21982 this.abortCursorAnimation(); 21983 this._restoreEditingProps(); 21984 this._currentCursorOpacity = 0; 21985 21986 this.fire('editing:exited'); 21987 this.canvas && this.canvas.fire('text:editing:exited', { target: this }); 21988 21989 return this; 21990 }, 21991 21992 /** 21993 * @private 21994 */ 21995 _removeExtraneousStyles: function() { 21996 for (var prop in this.styles) { 21997 if (!this._textLines[prop]) { 21998 delete this.styles[prop]; 21999 } 22000 } 22001 }, 22002 22003 /** 22004 * @private 22005 */ 22006 _removeCharsFromTo: function(start, end) { 22007 22008 var i = end; 22009 while (i !== start) { 22010 22011 var prevIndex = this.get2DCursorLocation(i).charIndex; 22012 i--; 22013 22014 var index = this.get2DCursorLocation(i).charIndex, 22015 isNewline = index > prevIndex; 22016 22017 if (isNewline) { 22018 this.removeStyleObject(isNewline, i + 1); 22019 } 22020 else { 22021 this.removeStyleObject(this.get2DCursorLocation(i).charIndex === 0, i); 22022 } 22023 22024 } 22025 22026 this.text = this.text.slice(0, start) + 22027 this.text.slice(end); 22028 this._clearCache(); 22029 }, 22030 22031 /** 22032 * Inserts a character where cursor is (replacing selection if one exists) 22033 * @param {String} _chars Characters to insert 22034 */ 22035 insertChars: function(_chars, useCopiedStyle) { 22036 var isEndOfLine = this.text.slice(this.selectionStart, this.selectionStart + 1) === '\n'; 22037 22038 this.text = this.text.slice(0, this.selectionStart) + 22039 _chars + 22040 this.text.slice(this.selectionEnd); 22041 22042 if (this.selectionStart === this.selectionEnd) { 22043 this.insertStyleObjects(_chars, isEndOfLine, useCopiedStyle); 22044 } 22045 // else if (this.selectionEnd - this.selectionStart > 1) { 22046 // TODO: replace styles properly 22047 // console.log('replacing MORE than 1 char'); 22048 // } 22049 this.setSelectionStart(this.selectionStart + _chars.length); 22050 this.setSelectionEnd(this.selectionStart); 22051 this._clearCache(); 22052 this.canvas && this.canvas.renderAll(); 22053 22054 this.setCoords(); 22055 this.fire('changed'); 22056 this.canvas && this.canvas.fire('text:changed', { target: this }); 22057 }, 22058 22059 /** 22060 * Inserts new style object 22061 * @param {Number} lineIndex Index of a line 22062 * @param {Number} charIndex Index of a char 22063 * @param {Boolean} isEndOfLine True if it's end of line 22064 */ 22065 insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) { 22066 22067 this.shiftLineStyles(lineIndex, +1); 22068 22069 if (!this.styles[lineIndex + 1]) { 22070 this.styles[lineIndex + 1] = { }; 22071 } 22072 22073 var currentCharStyle = this.styles[lineIndex][charIndex - 1], 22074 newLineStyles = { }; 22075 22076 // if there's nothing after cursor, 22077 // we clone current char style onto the next (otherwise empty) line 22078 if (isEndOfLine) { 22079 newLineStyles[0] = clone(currentCharStyle); 22080 this.styles[lineIndex + 1] = newLineStyles; 22081 } 22082 // otherwise we clone styles of all chars 22083 // after cursor onto the next line, from the beginning 22084 else { 22085 for (var index in this.styles[lineIndex]) { 22086 if (parseInt(index, 10) >= charIndex) { 22087 newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index]; 22088 // remove lines from the previous line since they're on a new line now 22089 delete this.styles[lineIndex][index]; 22090 } 22091 } 22092 this.styles[lineIndex + 1] = newLineStyles; 22093 } 22094 this._clearCache(); 22095 }, 22096 22097 /** 22098 * Inserts style object for a given line/char index 22099 * @param {Number} lineIndex Index of a line 22100 * @param {Number} charIndex Index of a char 22101 * @param {Object} [style] Style object to insert, if given 22102 */ 22103 insertCharStyleObject: function(lineIndex, charIndex, style) { 22104 22105 var currentLineStyles = this.styles[lineIndex], 22106 currentLineStylesCloned = clone(currentLineStyles); 22107 22108 if (charIndex === 0 && !style) { 22109 charIndex = 1; 22110 } 22111 22112 // shift all char styles by 1 forward 22113 // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4 22114 for (var index in currentLineStylesCloned) { 22115 var numericIndex = parseInt(index, 10); 22116 if (numericIndex >= charIndex) { 22117 currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex]; 22118 //delete currentLineStyles[index]; 22119 } 22120 } 22121 22122 this.styles[lineIndex][charIndex] = 22123 style || clone(currentLineStyles[charIndex - 1]); 22124 this._clearCache(); 22125 }, 22126 22127 /** 22128 * Inserts style object(s) 22129 * @param {String} _chars Characters at the location where style is inserted 22130 * @param {Boolean} isEndOfLine True if it's end of line 22131 * @param {Boolean} [useCopiedStyle] Style to insert 22132 */ 22133 insertStyleObjects: function(_chars, isEndOfLine, useCopiedStyle) { 22134 // removed shortcircuit over isEmptyStyles 22135 22136 var cursorLocation = this.get2DCursorLocation(), 22137 lineIndex = cursorLocation.lineIndex, 22138 charIndex = cursorLocation.charIndex; 22139 22140 if (!this.styles[lineIndex]) { 22141 this.styles[lineIndex] = { }; 22142 } 22143 22144 if (_chars === '\n') { 22145 this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine); 22146 } 22147 else { 22148 if (useCopiedStyle) { 22149 this._insertStyles(this.copiedStyles); 22150 } 22151 else { 22152 // TODO: support multiple style insertion if _chars.length > 1 22153 this.insertCharStyleObject(lineIndex, charIndex); 22154 } 22155 } 22156 }, 22157 22158 /** 22159 * @private 22160 */ 22161 _insertStyles: function(styles) { 22162 for (var i = 0, len = styles.length; i < len; i++) { 22163 22164 var cursorLocation = this.get2DCursorLocation(this.selectionStart + i), 22165 lineIndex = cursorLocation.lineIndex, 22166 charIndex = cursorLocation.charIndex; 22167 22168 this.insertCharStyleObject(lineIndex, charIndex, styles[i]); 22169 } 22170 }, 22171 22172 /** 22173 * Shifts line styles up or down 22174 * @param {Number} lineIndex Index of a line 22175 * @param {Number} offset Can be -1 or +1 22176 */ 22177 shiftLineStyles: function(lineIndex, offset) { 22178 // shift all line styles by 1 upward 22179 var clonedStyles = clone(this.styles); 22180 for (var line in this.styles) { 22181 var numericLine = parseInt(line, 10); 22182 if (numericLine > lineIndex) { 22183 this.styles[numericLine + offset] = clonedStyles[numericLine]; 22184 } 22185 } 22186 }, 22187 22188 /** 22189 * Removes style object 22190 * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line 22191 * @param {Number} [index] Optional index. When not given, current selectionStart is used. 22192 */ 22193 removeStyleObject: function(isBeginningOfLine, index) { 22194 22195 var cursorLocation = this.get2DCursorLocation(index), 22196 lineIndex = cursorLocation.lineIndex, 22197 charIndex = cursorLocation.charIndex; 22198 22199 if (isBeginningOfLine) { 22200 22201 var textOnPreviousLine = this._textLines[lineIndex - 1], 22202 newCharIndexOnPrevLine = textOnPreviousLine 22203 ? textOnPreviousLine.length 22204 : 0; 22205 22206 if (!this.styles[lineIndex - 1]) { 22207 this.styles[lineIndex - 1] = { }; 22208 } 22209 22210 for (charIndex in this.styles[lineIndex]) { 22211 this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine] 22212 = this.styles[lineIndex][charIndex]; 22213 } 22214 22215 this.shiftLineStyles(lineIndex, -1); 22216 } 22217 else { 22218 var currentLineStyles = this.styles[lineIndex]; 22219 22220 if (currentLineStyles) { 22221 var offset = this.selectionStart === this.selectionEnd ? -1 : 0; 22222 delete currentLineStyles[charIndex + offset]; 22223 // console.log('deleting', lineIndex, charIndex + offset); 22224 } 22225 22226 var currentLineStylesCloned = clone(currentLineStyles); 22227 22228 // shift all styles by 1 backwards 22229 for (var i in currentLineStylesCloned) { 22230 var numericIndex = parseInt(i, 10); 22231 if (numericIndex >= charIndex && numericIndex !== 0) { 22232 currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex]; 22233 delete currentLineStyles[numericIndex]; 22234 } 22235 } 22236 } 22237 }, 22238 22239 /** 22240 * Inserts new line 22241 */ 22242 insertNewline: function() { 22243 this.insertChars('\n'); 22244 } 22245 }); 22246})(); 22247 22248 22249fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { 22250 /** 22251 * Initializes "dbclick" event handler 22252 */ 22253 initDoubleClickSimulation: function() { 22254 22255 // for double click 22256 this.__lastClickTime = +new Date(); 22257 22258 // for triple click 22259 this.__lastLastClickTime = +new Date(); 22260 22261 this.__lastPointer = { }; 22262 22263 this.on('mousedown', this.onMouseDown.bind(this)); 22264 }, 22265 22266 onMouseDown: function(options) { 22267 22268 this.__newClickTime = +new Date(); 22269 var newPointer = this.canvas.getPointer(options.e); 22270 22271 if (this.isTripleClick(newPointer)) { 22272 this.fire('tripleclick', options); 22273 this._stopEvent(options.e); 22274 } 22275 else if (this.isDoubleClick(newPointer)) { 22276 this.fire('dblclick', options); 22277 this._stopEvent(options.e); 22278 } 22279 22280 this.__lastLastClickTime = this.__lastClickTime; 22281 this.__lastClickTime = this.__newClickTime; 22282 this.__lastPointer = newPointer; 22283 this.__lastIsEditing = this.isEditing; 22284 this.__lastSelected = this.selected; 22285 }, 22286 22287 isDoubleClick: function(newPointer) { 22288 return this.__newClickTime - this.__lastClickTime < 500 && 22289 this.__lastPointer.x === newPointer.x && 22290 this.__lastPointer.y === newPointer.y && this.__lastIsEditing; 22291 }, 22292 22293 isTripleClick: function(newPointer) { 22294 return this.__newClickTime - this.__lastClickTime < 500 && 22295 this.__lastClickTime - this.__lastLastClickTime < 500 && 22296 this.__lastPointer.x === newPointer.x && 22297 this.__lastPointer.y === newPointer.y; 22298 }, 22299 22300 /** 22301 * @private 22302 */ 22303 _stopEvent: function(e) { 22304 e.preventDefault && e.preventDefault(); 22305 e.stopPropagation && e.stopPropagation(); 22306 }, 22307 22308 /** 22309 * Initializes event handlers related to cursor or selection 22310 */ 22311 initCursorSelectionHandlers: function() { 22312 this.initSelectedHandler(); 22313 this.initMousedownHandler(); 22314 this.initMouseupHandler(); 22315 this.initClicks(); 22316 }, 22317 22318 /** 22319 * Initializes double and triple click event handlers 22320 */ 22321 initClicks: function() { 22322 this.on('dblclick', function(options) { 22323 this.selectWord(this.getSelectionStartFromPointer(options.e)); 22324 }); 22325 this.on('tripleclick', function(options) { 22326 this.selectLine(this.getSelectionStartFromPointer(options.e)); 22327 }); 22328 }, 22329 22330 /** 22331 * Initializes "mousedown" event handler 22332 */ 22333 initMousedownHandler: function() { 22334 this.on('mousedown', function(options) { 22335 22336 var pointer = this.canvas.getPointer(options.e); 22337 22338 this.__mousedownX = pointer.x; 22339 this.__mousedownY = pointer.y; 22340 this.__isMousedown = true; 22341 22342 if (this.hiddenTextarea && this.canvas) { 22343 this.canvas.wrapperEl.appendChild(this.hiddenTextarea); 22344 } 22345 22346 if (this.selected) { 22347 this.setCursorByClick(options.e); 22348 } 22349 22350 if (this.isEditing) { 22351 this.__selectionStartOnMouseDown = this.selectionStart; 22352 this.initDelayedCursor(true); 22353 } 22354 }); 22355 }, 22356 22357 /** 22358 * @private 22359 */ 22360 _isObjectMoved: function(e) { 22361 var pointer = this.canvas.getPointer(e); 22362 22363 return this.__mousedownX !== pointer.x || 22364 this.__mousedownY !== pointer.y; 22365 }, 22366 22367 /** 22368 * Initializes "mouseup" event handler 22369 */ 22370 initMouseupHandler: function() { 22371 this.on('mouseup', function(options) { 22372 this.__isMousedown = false; 22373 if (this._isObjectMoved(options.e)) { 22374 return; 22375 } 22376 22377 if (this.__lastSelected) { 22378 this.enterEditing(); 22379 this.initDelayedCursor(true); 22380 } 22381 this.selected = true; 22382 }); 22383 }, 22384 22385 /** 22386 * Changes cursor location in a text depending on passed pointer (x/y) object 22387 * @param {Event} e Event object 22388 */ 22389 setCursorByClick: function(e) { 22390 var newSelectionStart = this.getSelectionStartFromPointer(e); 22391 22392 if (e.shiftKey) { 22393 if (newSelectionStart < this.selectionStart) { 22394 this.setSelectionEnd(this.selectionStart); 22395 this.setSelectionStart(newSelectionStart); 22396 } 22397 else { 22398 this.setSelectionEnd(newSelectionStart); 22399 } 22400 } 22401 else { 22402 this.setSelectionStart(newSelectionStart); 22403 this.setSelectionEnd(newSelectionStart); 22404 } 22405 }, 22406 22407 /** 22408 * @private 22409 * @param {Event} e Event object 22410 * @return {Object} Coordinates of a pointer (x, y) 22411 */ 22412 _getLocalRotatedPointer: function(e) { 22413 var pointer = this.canvas.getPointer(e), 22414 22415 pClicked = new fabric.Point(pointer.x, pointer.y), 22416 pLeftTop = new fabric.Point(this.left, this.top), 22417 22418 rotated = fabric.util.rotatePoint( 22419 pClicked, pLeftTop, fabric.util.degreesToRadians(-this.angle)); 22420 22421 return this.getLocalPointer(e, rotated); 22422 }, 22423 22424 /** 22425 * Returns index of a character corresponding to where an object was clicked 22426 * @param {Event} e Event object 22427 * @return {Number} Index of a character 22428 */ 22429 getSelectionStartFromPointer: function(e) { 22430 var mouseOffset = this._getLocalRotatedPointer(e), 22431 prevWidth = 0, 22432 width = 0, 22433 height = 0, 22434 charIndex = 0, 22435 newSelectionStart, 22436 line; 22437 22438 for (var i = 0, len = this._textLines.length; i < len; i++) { 22439 line = this._textLines[i].split(''); 22440 height += this._getHeightOfLine(this.ctx, i) * this.scaleY; 22441 22442 var widthOfLine = this._getLineWidth(this.ctx, i), 22443 lineLeftOffset = this._getLineLeftOffset(widthOfLine); 22444 22445 width = lineLeftOffset * this.scaleX; 22446 22447 if (this.flipX) { 22448 // when oject is horizontally flipped we reverse chars 22449 this._textLines[i] = line.reverse().join(''); 22450 } 22451 22452 for (var j = 0, jlen = line.length; j < jlen; j++) { 22453 22454 var _char = line[j]; 22455 prevWidth = width; 22456 22457 width += this._getWidthOfChar(this.ctx, _char, i, this.flipX ? jlen - j : j) * 22458 this.scaleX; 22459 22460 if (height <= mouseOffset.y || width <= mouseOffset.x) { 22461 charIndex++; 22462 continue; 22463 } 22464 22465 return this._getNewSelectionStartFromOffset( 22466 mouseOffset, prevWidth, width, charIndex + i, jlen); 22467 } 22468 22469 if (mouseOffset.y < height) { 22470 return this._getNewSelectionStartFromOffset( 22471 mouseOffset, prevWidth, width, charIndex + i, jlen); 22472 } 22473 } 22474 22475 // clicked somewhere after all chars, so set at the end 22476 if (typeof newSelectionStart === 'undefined') { 22477 return this.text.length; 22478 } 22479 }, 22480 22481 /** 22482 * @private 22483 */ 22484 _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) { 22485 22486 var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, 22487 distanceBtwNextCharAndCursor = width - mouseOffset.x, 22488 offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1, 22489 newSelectionStart = index + offset; 22490 22491 // if object is horizontally flipped, mirror cursor location from the end 22492 if (this.flipX) { 22493 newSelectionStart = jlen - newSelectionStart; 22494 } 22495 22496 if (newSelectionStart > this.text.length) { 22497 newSelectionStart = this.text.length; 22498 } 22499 22500 return newSelectionStart; 22501 } 22502}); 22503 22504 22505fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { 22506 22507 /** 22508 * Initializes hidden textarea (needed to bring up keyboard in iOS) 22509 */ 22510 initHiddenTextarea: function() { 22511 this.hiddenTextarea = fabric.document.createElement('textarea'); 22512 22513 this.hiddenTextarea.setAttribute('autocapitalize', 'off'); 22514 this.hiddenTextarea.style.cssText = 'position: fixed; bottom: 20px; left: 0px; opacity: 0;' 22515 + ' width: 0px; height: 0px; z-index: -999;'; 22516 fabric.document.body.appendChild(this.hiddenTextarea); 22517 22518 fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); 22519 fabric.util.addListener(this.hiddenTextarea, 'keypress', this.onKeyPress.bind(this)); 22520 fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this)); 22521 fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this)); 22522 22523 if (!this._clickHandlerInitialized && this.canvas) { 22524 fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); 22525 this._clickHandlerInitialized = true; 22526 } 22527 }, 22528 22529 /** 22530 * @private 22531 */ 22532 _keysMap: { 22533 8: 'removeChars', 22534 9: 'exitEditing', 22535 27: 'exitEditing', 22536 13: 'insertNewline', 22537 33: 'moveCursorUp', 22538 34: 'moveCursorDown', 22539 35: 'moveCursorRight', 22540 36: 'moveCursorLeft', 22541 37: 'moveCursorLeft', 22542 38: 'moveCursorUp', 22543 39: 'moveCursorRight', 22544 40: 'moveCursorDown', 22545 46: 'forwardDelete' 22546 }, 22547 22548 /** 22549 * @private 22550 */ 22551 _ctrlKeysMap: { 22552 65: 'selectAll', 22553 88: 'cut' 22554 }, 22555 22556 onClick: function() { 22557 // No need to trigger click event here, focus is enough to have the keyboard appear on Android 22558 this.hiddenTextarea && this.hiddenTextarea.focus(); 22559 }, 22560 22561 /** 22562 * Handles keyup event 22563 * @param {Event} e Event object 22564 */ 22565 onKeyDown: function(e) { 22566 if (!this.isEditing) { 22567 return; 22568 } 22569 if (e.keyCode in this._keysMap) { 22570 this[this._keysMap[e.keyCode]](e); 22571 } 22572 else if ((e.keyCode in this._ctrlKeysMap) && (e.ctrlKey || e.metaKey)) { 22573 this[this._ctrlKeysMap[e.keyCode]](e); 22574 } 22575 else { 22576 return; 22577 } 22578 e.stopImmediatePropagation(); 22579 e.preventDefault(); 22580 this.canvas && this.canvas.renderAll(); 22581 }, 22582 22583 /** 22584 * Forward delete 22585 */ 22586 forwardDelete: function(e) { 22587 if (this.selectionStart === this.selectionEnd) { 22588 this.moveCursorRight(e); 22589 } 22590 this.removeChars(e); 22591 }, 22592 22593 /** 22594 * Copies selected text 22595 * @param {Event} e Event object 22596 */ 22597 copy: function(e) { 22598 var selectedText = this.getSelectedText(), 22599 clipboardData = this._getClipboardData(e); 22600 22601 // Check for backward compatibility with old browsers 22602 if (clipboardData) { 22603 clipboardData.setData('text', selectedText); 22604 } 22605 22606 this.copiedText = selectedText; 22607 this.copiedStyles = this.getSelectionStyles( 22608 this.selectionStart, 22609 this.selectionEnd); 22610 }, 22611 22612 /** 22613 * Pastes text 22614 * @param {Event} e Event object 22615 */ 22616 paste: function(e) { 22617 var copiedText = null, 22618 clipboardData = this._getClipboardData(e); 22619 22620 // Check for backward compatibility with old browsers 22621 if (clipboardData) { 22622 copiedText = clipboardData.getData('text'); 22623 } 22624 else { 22625 copiedText = this.copiedText; 22626 } 22627 22628 if (copiedText) { 22629 this.insertChars(copiedText, true); 22630 } 22631 }, 22632 22633 /** 22634 * Cuts text 22635 * @param {Event} e Event object 22636 */ 22637 cut: function(e) { 22638 if (this.selectionStart === this.selectionEnd) { 22639 return; 22640 } 22641 22642 this.copy(); 22643 this.removeChars(e); 22644 }, 22645 22646 /** 22647 * @private 22648 * @param {Event} e Event object 22649 * @return {Object} Clipboard data object 22650 */ 22651 _getClipboardData: function(e) { 22652 return e && (e.clipboardData || fabric.window.clipboardData); 22653 }, 22654 22655 /** 22656 * Handles keypress event 22657 * @param {Event} e Event object 22658 */ 22659 onKeyPress: function(e) { 22660 if (!this.isEditing || e.metaKey || e.ctrlKey) { 22661 return; 22662 } 22663 if (e.which !== 0) { 22664 this.insertChars(String.fromCharCode(e.which)); 22665 } 22666 e.stopPropagation(); 22667 }, 22668 22669 /** 22670 * Gets start offset of a selection 22671 * @param {Event} e Event object 22672 * @param {Boolean} isRight 22673 * @return {Number} 22674 */ 22675 getDownCursorOffset: function(e, isRight) { 22676 var selectionProp = isRight ? this.selectionEnd : this.selectionStart, 22677 _char, lineLeftOffset, 22678 textBeforeCursor = this.text.slice(0, selectionProp), 22679 textAfterCursor = this.text.slice(selectionProp), 22680 22681 textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1), 22682 textOnSameLineAfterCursor = textAfterCursor.match(/(.*)\n?/)[1], 22683 textOnNextLine = (textAfterCursor.match(/.*\n(.*)\n?/) || { })[1] || '', 22684 22685 cursorLocation = this.get2DCursorLocation(selectionProp); 22686 22687 // if on last line, down cursor goes to end of line 22688 if (cursorLocation.lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) { 22689 22690 // move to the end of a text 22691 return this.text.length - selectionProp; 22692 } 22693 22694 var widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex); 22695 lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); 22696 22697 var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, 22698 lineIndex = cursorLocation.lineIndex; 22699 22700 for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { 22701 _char = textOnSameLineBeforeCursor[i]; 22702 widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); 22703 } 22704 22705 var indexOnNextLine = this._getIndexOnNextLine( 22706 cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor); 22707 22708 return textOnSameLineAfterCursor.length + 1 + indexOnNextLine; 22709 }, 22710 22711 /** 22712 * @private 22713 */ 22714 _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor) { 22715 var lineIndex = cursorLocation.lineIndex + 1, 22716 widthOfNextLine = this._getLineWidth(this.ctx, lineIndex), 22717 lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), 22718 widthOfCharsOnNextLine = lineLeftOffset, 22719 indexOnNextLine = 0, 22720 foundMatch; 22721 22722 for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { 22723 22724 var _char = textOnNextLine[j], 22725 widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); 22726 22727 widthOfCharsOnNextLine += widthOfChar; 22728 22729 if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) { 22730 22731 foundMatch = true; 22732 22733 var leftEdge = widthOfCharsOnNextLine - widthOfChar, 22734 rightEdge = widthOfCharsOnNextLine, 22735 offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), 22736 offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); 22737 22738 indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; 22739 22740 break; 22741 } 22742 } 22743 22744 // reached end 22745 if (!foundMatch) { 22746 indexOnNextLine = textOnNextLine.length; 22747 } 22748 22749 return indexOnNextLine; 22750 }, 22751 22752 /** 22753 * Moves cursor down 22754 * @param {Event} e Event object 22755 */ 22756 moveCursorDown: function(e) { 22757 this.abortCursorAnimation(); 22758 this._currentCursorOpacity = 1; 22759 22760 var offset = this.getDownCursorOffset(e, this._selectionDirection === 'right'); 22761 22762 if (e.shiftKey) { 22763 this.moveCursorDownWithShift(offset); 22764 } 22765 else { 22766 this.moveCursorDownWithoutShift(offset); 22767 } 22768 22769 this.initDelayedCursor(); 22770 }, 22771 22772 /** 22773 * Moves cursor down without keeping selection 22774 * @param {Number} offset 22775 */ 22776 moveCursorDownWithoutShift: function(offset) { 22777 this._selectionDirection = 'right'; 22778 this.setSelectionStart(this.selectionStart + offset); 22779 this.setSelectionEnd(this.selectionStart); 22780 }, 22781 22782 /** 22783 * private 22784 */ 22785 swapSelectionPoints: function() { 22786 var swapSel = this.selectionEnd; 22787 this.setSelectionEnd(this.selectionStart); 22788 this.setSelectionStart(swapSel); 22789 }, 22790 22791 /** 22792 * Moves cursor down while keeping selection 22793 * @param {Number} offset 22794 */ 22795 moveCursorDownWithShift: function(offset) { 22796 if (this.selectionEnd === this.selectionStart) { 22797 this._selectionDirection = 'right'; 22798 } 22799 if (this._selectionDirection === 'right') { 22800 this.setSelectionEnd(this.selectionEnd + offset); 22801 } 22802 else { 22803 this.setSelectionStart(this.selectionStart + offset); 22804 } 22805 if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'left') { 22806 this.swapSelectionPoints(); 22807 this._selectionDirection = 'right'; 22808 } 22809 if (this.selectionEnd > this.text.length) { 22810 this.setSelectionEnd(this.text.length); 22811 } 22812 }, 22813 22814 /** 22815 * @param {Event} e Event object 22816 * @param {Boolean} isRight 22817 * @return {Number} 22818 */ 22819 getUpCursorOffset: function(e, isRight) { 22820 var selectionProp = isRight ? this.selectionEnd : this.selectionStart, 22821 cursorLocation = this.get2DCursorLocation(selectionProp); 22822 // if on first line, up cursor goes to start of line 22823 if (cursorLocation.lineIndex === 0 || e.metaKey || e.keyCode === 33) { 22824 return selectionProp; 22825 } 22826 22827 var textBeforeCursor = this.text.slice(0, selectionProp), 22828 textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1), 22829 textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '', 22830 _char, 22831 widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex), 22832 lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), 22833 widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, 22834 lineIndex = cursorLocation.lineIndex; 22835 22836 for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { 22837 _char = textOnSameLineBeforeCursor[i]; 22838 widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i); 22839 } 22840 22841 var indexOnPrevLine = this._getIndexOnPrevLine( 22842 cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor); 22843 22844 return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length; 22845 }, 22846 22847 /** 22848 * @private 22849 */ 22850 _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor) { 22851 22852 var lineIndex = cursorLocation.lineIndex - 1, 22853 widthOfPreviousLine = this._getLineWidth(this.ctx, lineIndex), 22854 lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), 22855 widthOfCharsOnPreviousLine = lineLeftOffset, 22856 indexOnPrevLine = 0, 22857 foundMatch; 22858 22859 for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { 22860 22861 var _char = textOnPreviousLine[j], 22862 widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); 22863 22864 widthOfCharsOnPreviousLine += widthOfChar; 22865 22866 if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) { 22867 22868 foundMatch = true; 22869 22870 var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, 22871 rightEdge = widthOfCharsOnPreviousLine, 22872 offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), 22873 offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); 22874 22875 indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); 22876 22877 break; 22878 } 22879 } 22880 22881 // reached end 22882 if (!foundMatch) { 22883 indexOnPrevLine = textOnPreviousLine.length - 1; 22884 } 22885 22886 return indexOnPrevLine; 22887 }, 22888 22889 /** 22890 * Moves cursor up 22891 * @param {Event} e Event object 22892 */ 22893 moveCursorUp: function(e) { 22894 22895 this.abortCursorAnimation(); 22896 this._currentCursorOpacity = 1; 22897 22898 var offset = this.getUpCursorOffset(e, this._selectionDirection === 'right'); 22899 if (e.shiftKey) { 22900 this.moveCursorUpWithShift(offset); 22901 } 22902 else { 22903 this.moveCursorUpWithoutShift(offset); 22904 } 22905 22906 this.initDelayedCursor(); 22907 }, 22908 22909 /** 22910 * Moves cursor up with shift 22911 * @param {Number} offset 22912 */ 22913 moveCursorUpWithShift: function(offset) { 22914 if (this.selectionEnd === this.selectionStart) { 22915 this._selectionDirection = 'left'; 22916 } 22917 if (this._selectionDirection === 'right') { 22918 this.setSelectionEnd(this.selectionEnd - offset); 22919 } 22920 else { 22921 this.setSelectionStart(this.selectionStart - offset); 22922 } 22923 if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'right') { 22924 this.swapSelectionPoints(); 22925 this._selectionDirection = 'left'; 22926 } 22927 }, 22928 22929 /** 22930 * Moves cursor up without shift 22931 * @param {Number} offset 22932 */ 22933 moveCursorUpWithoutShift: function(offset) { 22934 if (this.selectionStart === this.selectionEnd) { 22935 this.setSelectionStart(this.selectionStart - offset); 22936 } 22937 this.setSelectionEnd(this.selectionStart); 22938 22939 this._selectionDirection = 'left'; 22940 }, 22941 22942 /** 22943 * Moves cursor left 22944 * @param {Event} e Event object 22945 */ 22946 moveCursorLeft: function(e) { 22947 if (this.selectionStart === 0 && this.selectionEnd === 0) { 22948 return; 22949 } 22950 22951 this.abortCursorAnimation(); 22952 this._currentCursorOpacity = 1; 22953 22954 if (e.shiftKey) { 22955 this.moveCursorLeftWithShift(e); 22956 } 22957 else { 22958 this.moveCursorLeftWithoutShift(e); 22959 } 22960 22961 this.initDelayedCursor(); 22962 }, 22963 22964 /** 22965 * @private 22966 */ 22967 _move: function(e, prop, direction) { 22968 var propMethod = (prop === 'selectionStart' ? 'setSelectionStart' : 'setSelectionEnd'); 22969 if (e.altKey) { 22970 this[propMethod](this['findWordBoundary' + direction](this[prop])); 22971 } 22972 else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) { 22973 this[propMethod](this['findLineBoundary' + direction](this[prop])); 22974 } 22975 else { 22976 this[propMethod](this[prop] + (direction === 'Left' ? -1 : 1)); 22977 } 22978 }, 22979 22980 /** 22981 * @private 22982 */ 22983 _moveLeft: function(e, prop) { 22984 this._move(e, prop, 'Left'); 22985 }, 22986 22987 /** 22988 * @private 22989 */ 22990 _moveRight: function(e, prop) { 22991 this._move(e, prop, 'Right'); 22992 }, 22993 22994 /** 22995 * Moves cursor left without keeping selection 22996 * @param {Event} e 22997 */ 22998 moveCursorLeftWithoutShift: function(e) { 22999 this._selectionDirection = 'left'; 23000 23001 // only move cursor when there is no selection, 23002 // otherwise we discard it, and leave cursor on same place 23003 if (this.selectionEnd === this.selectionStart) { 23004 this._moveLeft(e, 'selectionStart'); 23005 } 23006 this.setSelectionEnd(this.selectionStart); 23007 }, 23008 23009 /** 23010 * Moves cursor left while keeping selection 23011 * @param {Event} e 23012 */ 23013 moveCursorLeftWithShift: function(e) { 23014 if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) { 23015 this._moveLeft(e, 'selectionEnd'); 23016 } 23017 else { 23018 this._selectionDirection = 'left'; 23019 this._moveLeft(e, 'selectionStart'); 23020 23021 // increase selection by one if it's a newline 23022 if (this.text.charAt(this.selectionStart) === '\n') { 23023 this.setSelectionStart(this.selectionStart - 1); 23024 } 23025 } 23026 }, 23027 23028 /** 23029 * Moves cursor right 23030 * @param {Event} e Event object 23031 */ 23032 moveCursorRight: function(e) { 23033 if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) { 23034 return; 23035 } 23036 23037 this.abortCursorAnimation(); 23038 this._currentCursorOpacity = 1; 23039 23040 if (e.shiftKey) { 23041 this.moveCursorRightWithShift(e); 23042 } 23043 else { 23044 this.moveCursorRightWithoutShift(e); 23045 } 23046 23047 this.initDelayedCursor(); 23048 }, 23049 23050 /** 23051 * Moves cursor right while keeping selection 23052 * @param {Event} e 23053 */ 23054 moveCursorRightWithShift: function(e) { 23055 if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) { 23056 this._moveRight(e, 'selectionStart'); 23057 } 23058 else { 23059 this._selectionDirection = 'right'; 23060 this._moveRight(e, 'selectionEnd'); 23061 23062 // increase selection by one if it's a newline 23063 if (this.text.charAt(this.selectionEnd - 1) === '\n') { 23064 this.setSelectionEnd(this.selectionEnd + 1); 23065 } 23066 } 23067 }, 23068 23069 /** 23070 * Moves cursor right without keeping selection 23071 * @param {Event} e Event object 23072 */ 23073 moveCursorRightWithoutShift: function(e) { 23074 this._selectionDirection = 'right'; 23075 23076 if (this.selectionStart === this.selectionEnd) { 23077 this._moveRight(e, 'selectionStart'); 23078 this.setSelectionEnd(this.selectionStart); 23079 } 23080 else { 23081 this.setSelectionEnd(this.selectionEnd + this.getNumNewLinesInSelectedText()); 23082 this.setSelectionStart(this.selectionEnd); 23083 } 23084 }, 23085 23086 /** 23087 * Removes characters selected by selection 23088 * @param {Event} e Event object 23089 */ 23090 removeChars: function(e) { 23091 if (this.selectionStart === this.selectionEnd) { 23092 this._removeCharsNearCursor(e); 23093 } 23094 else { 23095 this._removeCharsFromTo(this.selectionStart, this.selectionEnd); 23096 } 23097 23098 this.setSelectionEnd(this.selectionStart); 23099 23100 this._removeExtraneousStyles(); 23101 23102 this._clearCache(); 23103 this.canvas && this.canvas.renderAll(); 23104 23105 this.setCoords(); 23106 this.fire('changed'); 23107 this.canvas && this.canvas.fire('text:changed', { target: this }); 23108 }, 23109 23110 /** 23111 * @private 23112 * @param {Event} e Event object 23113 */ 23114 _removeCharsNearCursor: function(e) { 23115 if (this.selectionStart !== 0) { 23116 23117 if (e.metaKey) { 23118 // remove all till the start of current line 23119 var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart); 23120 23121 this._removeCharsFromTo(leftLineBoundary, this.selectionStart); 23122 this.setSelectionStart(leftLineBoundary); 23123 } 23124 else if (e.altKey) { 23125 // remove all till the start of current word 23126 var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart); 23127 23128 this._removeCharsFromTo(leftWordBoundary, this.selectionStart); 23129 this.setSelectionStart(leftWordBoundary); 23130 } 23131 else { 23132 var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n'; 23133 this.removeStyleObject(isBeginningOfLine); 23134 this.setSelectionStart(this.selectionStart - 1); 23135 this.text = this.text.slice(0, this.selectionStart) + 23136 this.text.slice(this.selectionStart + 1); 23137 } 23138 } 23139 } 23140}); 23141 23142 23143/* _TO_SVG_START_ */ 23144fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { 23145 23146 /** 23147 * @private 23148 */ 23149 _setSVGTextLineText: function(lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) { 23150 if (!this.styles[lineIndex]) { 23151 this.callSuper('_setSVGTextLineText', 23152 lineIndex, textSpans, height, textLeftOffset, textTopOffset); 23153 } 23154 else { 23155 this._setSVGTextLineChars( 23156 lineIndex, textSpans, height, textLeftOffset, textBgRects); 23157 } 23158 }, 23159 23160 /** 23161 * @private 23162 */ 23163 _setSVGTextLineChars: function(lineIndex, textSpans, height, textLeftOffset, textBgRects) { 23164 23165 var chars = this._textLines[lineIndex].split(''), 23166 charOffset = 0, 23167 lineLeftOffset = this._getSVGLineLeftOffset(lineIndex) - this.width / 2, 23168 lineOffset = this._getSVGLineTopOffset(lineIndex), 23169 heightOfLine = this._getHeightOfLine(this.ctx, lineIndex); 23170 23171 for (var i = 0, len = chars.length; i < len; i++) { 23172 var styleDecl = this.styles[lineIndex][i] || { }; 23173 23174 textSpans.push( 23175 this._createTextCharSpan( 23176 chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset)); 23177 23178 var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i); 23179 23180 if (styleDecl.textBackgroundColor) { 23181 textBgRects.push( 23182 this._createTextCharBg( 23183 styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset)); 23184 } 23185 23186 charOffset += charWidth; 23187 } 23188 }, 23189 23190 /** 23191 * @private 23192 */ 23193 _getSVGLineLeftOffset: function(lineIndex) { 23194 return fabric.util.toFixed(this._getLineLeftOffset(this.__lineWidths[lineIndex]), 2); 23195 }, 23196 23197 /** 23198 * @private 23199 */ 23200 _getSVGLineTopOffset: function(lineIndex) { 23201 var lineTopOffset = 0, lastHeight = 0; 23202 for (var j = 0; j < lineIndex; j++) { 23203 lineTopOffset += this._getHeightOfLine(this.ctx, j); 23204 } 23205 lastHeight = this._getHeightOfLine(this.ctx, j); 23206 return { 23207 lineTop: lineTopOffset, 23208 offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult) 23209 }; 23210 }, 23211 23212 /** 23213 * @private 23214 */ 23215 _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) { 23216 return [ 23217 //jscs:disable validateIndentation 23218 '<rect fill="', styleDecl.textBackgroundColor, 23219 '" x="', lineLeftOffset + charOffset, 23220 '" y="', lineTopOffset - this.height/2, 23221 '" width="', charWidth, 23222 '" height="', heightOfLine / this.lineHeight, 23223 '"></rect>' 23224 //jscs:enable validateIndentation 23225 ].join(''); 23226 }, 23227 23228 /** 23229 * @private 23230 */ 23231 _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) { 23232 23233 var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({ 23234 visible: true, 23235 fill: this.fill, 23236 stroke: this.stroke, 23237 type: 'text' 23238 }, styleDecl)); 23239 23240 return [ 23241 //jscs:disable validateIndentation 23242 '<tspan x="', lineLeftOffset + charOffset, '" y="', 23243 lineTopOffset - this.height/2, '" ', 23244 (styleDecl.fontFamily ? 'font-family="' + styleDecl.fontFamily.replace(/"/g, '\'') + '" ': ''), 23245 (styleDecl.fontSize ? 'font-size="' + styleDecl.fontSize + '" ': ''), 23246 (styleDecl.fontStyle ? 'font-style="' + styleDecl.fontStyle + '" ': ''), 23247 (styleDecl.fontWeight ? 'font-weight="' + styleDecl.fontWeight + '" ': ''), 23248 (styleDecl.textDecoration ? 'text-decoration="' + styleDecl.textDecoration + '" ': ''), 23249 'style="', fillStyles, '">', 23250 fabric.util.string.escapeXml(_char), 23251 '</tspan>' 23252 //jscs:enable validateIndentation 23253 ].join(''); 23254 } 23255}); 23256/* _TO_SVG_END_ */ 23257 23258 23259(function() { 23260 23261 if (typeof document !== 'undefined' && typeof window !== 'undefined') { 23262 return; 23263 } 23264 23265 var DOMParser = require('xmldom').DOMParser, 23266 URL = require('url'), 23267 HTTP = require('http'), 23268 HTTPS = require('https'), 23269 23270 Canvas = require('canvas'), 23271 Image = require('canvas').Image; 23272 23273 /** @private */ 23274 function request(url, encoding, callback) { 23275 var oURL = URL.parse(url); 23276 23277 // detect if http or https is used 23278 if ( !oURL.port ) { 23279 oURL.port = ( oURL.protocol.indexOf('https:') === 0 ) ? 443 : 80; 23280 } 23281 23282 // assign request handler based on protocol 23283 var reqHandler = (oURL.protocol.indexOf('https:') === 0 ) ? HTTPS : HTTP, 23284 req = reqHandler.request({ 23285 hostname: oURL.hostname, 23286 port: oURL.port, 23287 path: oURL.path, 23288 method: 'GET' 23289 }, function(response) { 23290 var body = ''; 23291 if (encoding) { 23292 response.setEncoding(encoding); 23293 } 23294 response.on('end', function () { 23295 callback(body); 23296 }); 23297 response.on('data', function (chunk) { 23298 if (response.statusCode === 200) { 23299 body += chunk; 23300 } 23301 }); 23302 }); 23303 23304 req.on('error', function(err) { 23305 if (err.errno === process.ECONNREFUSED) { 23306 fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port); 23307 } 23308 else { 23309 fabric.log(err.message); 23310 } 23311 }); 23312 23313 req.end(); 23314 } 23315 23316 /** @private */ 23317 function requestFs(path, callback) { 23318 var fs = require('fs'); 23319 fs.readFile(path, function (err, data) { 23320 if (err) { 23321 fabric.log(err); 23322 throw err; 23323 } 23324 else { 23325 callback(data); 23326 } 23327 }); 23328 } 23329 23330 fabric.util.loadImage = function(url, callback, context) { 23331 function createImageAndCallBack(data) { 23332 img.src = new Buffer(data, 'binary'); 23333 // preserving original url, which seems to be lost in node-canvas 23334 img._src = url; 23335 callback && callback.call(context, img); 23336 } 23337 var img = new Image(); 23338 if (url && (url instanceof Buffer || url.indexOf('data') === 0)) { 23339 img.src = img._src = url; 23340 callback && callback.call(context, img); 23341 } 23342 else if (url && url.indexOf('http') !== 0) { 23343 requestFs(url, createImageAndCallBack); 23344 } 23345 else if (url) { 23346 request(url, 'binary', createImageAndCallBack); 23347 } 23348 else { 23349 callback && callback.call(context, url); 23350 } 23351 }; 23352 23353 fabric.loadSVGFromURL = function(url, callback, reviver) { 23354 url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim(); 23355 if (url.indexOf('http') !== 0) { 23356 requestFs(url, function(body) { 23357 fabric.loadSVGFromString(body.toString(), callback, reviver); 23358 }); 23359 } 23360 else { 23361 request(url, '', function(body) { 23362 fabric.loadSVGFromString(body, callback, reviver); 23363 }); 23364 } 23365 }; 23366 23367 fabric.loadSVGFromString = function(string, callback, reviver) { 23368 var doc = new DOMParser().parseFromString(string); 23369 fabric.parseSVGDocument(doc.documentElement, function(results, options) { 23370 callback && callback(results, options); 23371 }, reviver); 23372 }; 23373 23374 fabric.util.getScript = function(url, callback) { 23375 request(url, '', function(body) { 23376 eval(body); 23377 callback && callback(); 23378 }); 23379 }; 23380 23381 fabric.Image.fromObject = function(object, callback) { 23382 fabric.util.loadImage(object.src, function(img) { 23383 var oImg = new fabric.Image(img); 23384 23385 oImg._initConfig(object); 23386 oImg._initFilters(object, function(filters) { 23387 oImg.filters = filters || [ ]; 23388 callback && callback(oImg); 23389 }); 23390 }); 23391 }; 23392 23393 /** 23394 * Only available when running fabric on node.js 23395 * @param {Number} width Canvas width 23396 * @param {Number} height Canvas height 23397 * @param {Object} [options] Options to pass to FabricCanvas. 23398 * @param {Object} [nodeCanvasOptions] Options to pass to NodeCanvas. 23399 * @return {Object} wrapped canvas instance 23400 */ 23401 fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) { 23402 nodeCanvasOptions = nodeCanvasOptions || options; 23403 23404 var canvasEl = fabric.document.createElement('canvas'), 23405 nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions); 23406 23407 // jsdom doesn't create style on canvas element, so here be temp. workaround 23408 canvasEl.style = { }; 23409 23410 canvasEl.width = nodeCanvas.width; 23411 canvasEl.height = nodeCanvas.height; 23412 23413 var FabricCanvas = fabric.Canvas || fabric.StaticCanvas, 23414 fabricCanvas = new FabricCanvas(canvasEl, options); 23415 23416 fabricCanvas.contextContainer = nodeCanvas.getContext('2d'); 23417 fabricCanvas.nodeCanvas = nodeCanvas; 23418 fabricCanvas.Font = Canvas.Font; 23419 23420 return fabricCanvas; 23421 }; 23422 23423 /** @ignore */ 23424 fabric.StaticCanvas.prototype.createPNGStream = function() { 23425 return this.nodeCanvas.createPNGStream(); 23426 }; 23427 23428 fabric.StaticCanvas.prototype.createJPEGStream = function(opts) { 23429 return this.nodeCanvas.createJPEGStream(opts); 23430 }; 23431 23432 var origSetWidth = fabric.StaticCanvas.prototype.setWidth; 23433 fabric.StaticCanvas.prototype.setWidth = function(width, options) { 23434 origSetWidth.call(this, width, options); 23435 this.nodeCanvas.width = width; 23436 return this; 23437 }; 23438 if (fabric.Canvas) { 23439 fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth; 23440 } 23441 23442 var origSetHeight = fabric.StaticCanvas.prototype.setHeight; 23443 fabric.StaticCanvas.prototype.setHeight = function(height, options) { 23444 origSetHeight.call(this, height, options); 23445 this.nodeCanvas.height = height; 23446 return this; 23447 }; 23448 if (fabric.Canvas) { 23449 fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight; 23450 } 23451 23452})(); 23453