1/* eslint-disable */ 2/* build: `node build.js modules=ALL exclude=gestures,accessors,erasing requirejs minifier=uglifyjs` */ 3/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ 4 5var fabric = fabric || { version: "5.3.0" }; 6if (typeof exports !== "undefined") { 7 exports.fabric = fabric; 8} else if (typeof define === "function" && define.amd) { 9 /* _AMD_START_ */ 10 define([], function () { 11 return fabric; 12 }); 13} 14/* _AMD_END_ */ 15if (typeof document !== "undefined" && typeof window !== "undefined") { 16 if ( 17 document instanceof 18 (typeof HTMLDocument !== "undefined" ? HTMLDocument : Document) 19 ) { 20 fabric.document = document; 21 } else { 22 fabric.document = document.implementation.createHTMLDocument(""); 23 } 24 fabric.window = window; 25} else { 26 // assume we're running under node.js when document/window are not present 27 var jsdom = require("jsdom"); 28 var virtualWindow = new jsdom.JSDOM( 29 decodeURIComponent( 30 "%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E", 31 ), 32 { 33 features: { 34 FetchExternalResources: ["img"], 35 }, 36 resources: "usable", 37 }, 38 ).window; 39 fabric.document = virtualWindow.document; 40 fabric.jsdomImplForWrapper = 41 require("jsdom/lib/jsdom/living/generated/utils").implForWrapper; 42 fabric.nodeCanvas = require("jsdom/lib/jsdom/utils").Canvas; 43 fabric.window = virtualWindow; 44 DOMParser = fabric.window.DOMParser; 45} 46 47/** 48 * True when in environment that supports touch events 49 * @type boolean 50 */ 51fabric.isTouchSupported = 52 "ontouchstart" in fabric.window || 53 "ontouchstart" in fabric.document || 54 (fabric.window && 55 fabric.window.navigator && 56 fabric.window.navigator.maxTouchPoints > 0); 57 58/** 59 * True when in environment that's probably Node.js 60 * @type boolean 61 */ 62fabric.isLikelyNode = 63 typeof Buffer !== "undefined" && typeof window === "undefined"; 64 65/* _FROM_SVG_START_ */ 66/** 67 * Attributes parsed from all SVG elements 68 * @type array 69 */ 70fabric.SHARED_ATTRIBUTES = [ 71 "display", 72 "transform", 73 "fill", 74 "fill-opacity", 75 "fill-rule", 76 "opacity", 77 "stroke", 78 "stroke-dasharray", 79 "stroke-linecap", 80 "stroke-dashoffset", 81 "stroke-linejoin", 82 "stroke-miterlimit", 83 "stroke-opacity", 84 "stroke-width", 85 "id", 86 "paint-order", 87 "vector-effect", 88 "instantiated_by_use", 89 "clip-path", 90]; 91/* _FROM_SVG_END_ */ 92 93/** 94 * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. 95 */ 96fabric.DPI = 96; 97fabric.reNum = "(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:[eE][-+]?\\d+)?)"; 98fabric.commaWsp = "(?:\\s+,?\\s*|,\\s*)"; 99fabric.rePathCommand = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:[eE][-+]?\d+)?)/gi; 100fabric.reNonWord = /[ \n\.,;!\?\-]/; 101fabric.fontPaths = {}; 102fabric.iMatrix = [1, 0, 0, 1, 0, 0]; 103fabric.svgNS = "http://www.w3.org/2000/svg"; 104 105/** 106 * Pixel limit for cache canvases. 1Mpx , 4Mpx should be fine. 107 * @since 1.7.14 108 * @type Number 109 * @default 110 */ 111fabric.perfLimitSizeTotal = 2097152; 112 113/** 114 * Pixel limit for cache canvases width or height. IE fixes the maximum at 5000 115 * @since 1.7.14 116 * @type Number 117 * @default 118 */ 119fabric.maxCacheSideLimit = 4096; 120 121/** 122 * Lowest pixel limit for cache canvases, set at 256PX 123 * @since 1.7.14 124 * @type Number 125 * @default 126 */ 127fabric.minCacheSideLimit = 256; 128 129/** 130 * Cache Object for widths of chars in text rendering. 131 */ 132fabric.charWidthsCache = {}; 133 134/** 135 * if webgl is enabled and available, textureSize will determine the size 136 * of the canvas backend 137 * @since 2.0.0 138 * @type Number 139 * @default 140 */ 141fabric.textureSize = 2048; 142 143/** 144 * When 'true', style information is not retained when copy/pasting text, making 145 * pasted text use destination style. 146 * Defaults to 'false'. 147 * @type Boolean 148 * @default 149 */ 150fabric.disableStyleCopyPaste = false; 151 152/** 153 * Enable webgl for filtering picture is available 154 * A filtering backend will be initialized, this will both take memory and 155 * time since a default 2048x2048 canvas will be created for the gl context 156 * @since 2.0.0 157 * @type Boolean 158 * @default 159 */ 160fabric.enableGLFiltering = true; 161 162/** 163 * Device Pixel Ratio 164 * @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html 165 */ 166fabric.devicePixelRatio = 167 fabric.window.devicePixelRatio || 168 fabric.window.webkitDevicePixelRatio || 169 fabric.window.mozDevicePixelRatio || 170 1; 171/** 172 * Browser-specific constant to adjust CanvasRenderingContext2D.shadowBlur value, 173 * which is unitless and not rendered equally across browsers. 174 * 175 * Values that work quite well (as of October 2017) are: 176 * - Chrome: 1.5 177 * - Edge: 1.75 178 * - Firefox: 0.9 179 * - Safari: 0.95 180 * 181 * @since 2.0.0 182 * @type Number 183 * @default 1 184 */ 185fabric.browserShadowBlurConstant = 1; 186 187/**
vendor: 12,779 bytes, lines 188-593
188 * This object contains the result of arc to bezier conversion for faster retrieving if the same arc needs to be converted again. 189 * It was an internal variable, is accessible since version 2.3.4 190 */ 191fabric.arcToSegmentsCache = {}; 192 193/** 194 * This object keeps the results of the boundsOfCurve calculation mapped by the joined arguments necessary to calculate it. 195 * It does speed up calculation, if you parse and add always the same paths, but in case of heavy usage of freedrawing 196 * you do not get any speed benefit and you get a big object in memory. 197 * The object was a private variable before, while now is appended to the lib so that you have access to it and you 198 * can eventually clear it. 199 * It was an internal variable, is accessible since version 2.3.4 200 */ 201fabric.boundsOfCurveCache = {}; 202 203/** 204 * If disabled boundsOfCurveCache is not used. For apps that make heavy usage of pencil drawing probably disabling it is better 205 * @default true 206 */ 207fabric.cachesBoundsOfCurve = true; 208 209/** 210 * Skip performance testing of setupGLContext and force the use of putImageData that seems to be the one that works best on 211 * Chrome + old hardware. if your users are experiencing empty images after filtering you may try to force this to true 212 * this has to be set before instantiating the filtering backend ( before filtering the first image ) 213 * @type Boolean 214 * @default false 215 */ 216fabric.forceGLPutImageData = false; 217 218fabric.initFilterBackend = function () { 219 if ( 220 fabric.enableGLFiltering && 221 fabric.isWebglSupported && 222 fabric.isWebglSupported(fabric.textureSize) 223 ) { 224 console.log("max texture size: " + fabric.maxTextureSize); 225 return new fabric.WebglFilterBackend({ tileSize: fabric.textureSize }); 226 } else if (fabric.Canvas2dFilterBackend) { 227 return new fabric.Canvas2dFilterBackend(); 228 } 229}; 230 231if (typeof document !== "undefined" && typeof window !== "undefined") { 232 // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system) 233 window.fabric = fabric; 234} 235 236(function () { 237 /** 238 * @private 239 * @param {String} eventName 240 * @param {Function} handler 241 */ 242 function _removeEventListener(eventName, handler) { 243 if (!this.__eventListeners[eventName]) { 244 return; 245 } 246 var eventListener = this.__eventListeners[eventName]; 247 if (handler) { 248 eventListener[eventListener.indexOf(handler)] = false; 249 } else { 250 fabric.util.array.fill(eventListener, false); 251 } 252 } 253 254 /** 255 * Observes specified event 256 * @memberOf fabric.Observable 257 * @alias on 258 * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) 259 * @param {Function} handler Function that receives a notification when an event of the specified type occurs 260 * @return {Self} thisArg 261 * @chainable 262 */ 263 function on(eventName, handler) { 264 if (!this.__eventListeners) { 265 this.__eventListeners = {}; 266 } 267 // one object with key/value pairs was passed 268 if (arguments.length === 1) { 269 for (var prop in eventName) { 270 this.on(prop, eventName[prop]); 271 } 272 } else { 273 if (!this.__eventListeners[eventName]) { 274 this.__eventListeners[eventName] = []; 275 } 276 this.__eventListeners[eventName].push(handler); 277 } 278 return this; 279 } 280 281 function _once(eventName, handler) { 282 var _handler = function () { 283 handler.apply(this, arguments); 284 this.off(eventName, _handler); 285 }.bind(this); 286 this.on(eventName, _handler); 287 } 288 289 function once(eventName, handler) { 290 // one object with key/value pairs was passed 291 if (arguments.length === 1) { 292 for (var prop in eventName) { 293 _once.call(this, prop, eventName[prop]); 294 } 295 } else { 296 _once.call(this, eventName, handler); 297 } 298 return this; 299 } 300 301 /** 302 * Stops event observing for a particular event handler. Calling this method 303 * without arguments removes all handlers for all events 304 * @memberOf fabric.Observable 305 * @alias off 306 * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) 307 * @param {Function} handler Function to be deleted from EventListeners 308 * @return {Self} thisArg 309 * @chainable 310 */ 311 function off(eventName, handler) { 312 if (!this.__eventListeners) { 313 return this; 314 } 315 316 // remove all key/value pairs (event name -> event handler) 317 if (arguments.length === 0) { 318 for (eventName in this.__eventListeners) { 319 _removeEventListener.call(this, eventName); 320 } 321 } 322 // one object with key/value pairs was passed 323 else if (arguments.length === 1 && typeof arguments[0] === "object") { 324 for (var prop in eventName) { 325 _removeEventListener.call(this, prop, eventName[prop]); 326 } 327 } else { 328 _removeEventListener.call(this, eventName, handler); 329 } 330 return this; 331 } 332 333 /** 334 * Fires event with an optional options object 335 * @memberOf fabric.Observable 336 * @param {String} eventName Event name to fire 337 * @param {Object} [options] Options object 338 * @return {Self} thisArg 339 * @chainable 340 */ 341 function fire(eventName, options) { 342 if (!this.__eventListeners) { 343 return this; 344 } 345 346 var listenersForEvent = this.__eventListeners[eventName]; 347 if (!listenersForEvent) { 348 return this; 349 } 350 351 for (var i = 0, len = listenersForEvent.length; i < len; i++) { 352 listenersForEvent[i] && listenersForEvent[i].call(this, options || {}); 353 } 354 this.__eventListeners[eventName] = listenersForEvent.filter( 355 function (value) { 356 return value !== false; 357 }, 358 ); 359 return this; 360 } 361 362 /** 363 * @namespace fabric.Observable 364 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#events} 365 * @see {@link http://fabricjs.com/events|Events demo} 366 */ 367 fabric.Observable = { 368 fire: fire, 369 on: on, 370 once: once, 371 off: off, 372 }; 373})(); 374 375/** 376 * @namespace fabric.Collection 377 */ 378fabric.Collection = { 379 _objects: [], 380 381 /** 382 * Adds objects to collection, Canvas or Group, then renders canvas 383 * (if `renderOnAddRemove` is not `false`). 384 * in case of Group no changes to bounding box are made. 385 * Objects should be instances of (or inherit from) fabric.Object 386 * Use of this function is highly discouraged for groups. 387 * you can add a bunch of objects with the add method but then you NEED 388 * to run a addWithUpdate call for the Group class or position/bbox will be wrong. 389 * @param {...fabric.Object} object Zero or more fabric instances 390 * @return {Self} thisArg 391 * @chainable 392 */ 393 add: function () { 394 this._objects.push.apply(this._objects, arguments); 395 if (this._onObjectAdded) { 396 for (var i = 0, length = arguments.length; i < length; i++) { 397 this._onObjectAdded(arguments[i]); 398 } 399 } 400 this.renderOnAddRemove && this.requestRenderAll(); 401 return this; 402 }, 403 404 /** 405 * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) 406 * An object should be an instance of (or inherit from) fabric.Object 407 * Use of this function is highly discouraged for groups. 408 * you can add a bunch of objects with the insertAt method but then you NEED 409 * to run a addWithUpdate call for the Group class or position/bbox will be wrong. 410 * @param {Object} object Object to insert 411 * @param {Number} index Index to insert object at 412 * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs 413 * @return {Self} thisArg 414 * @chainable 415 */ 416 insertAt: function (object, index, nonSplicing) { 417 var objects = this._objects; 418 if (nonSplicing) { 419 objects[index] = object; 420 } else { 421 objects.splice(index, 0, object); 422 } 423 this._onObjectAdded && this._onObjectAdded(object); 424 this.renderOnAddRemove && this.requestRenderAll(); 425 return this; 426 }, 427 428 /** 429 * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) 430 * @param {...fabric.Object} object Zero or more fabric instances 431 * @return {Self} thisArg 432 * @chainable 433 */ 434 remove: function () { 435 var objects = this._objects, 436 index, 437 somethingRemoved = false; 438 439 for (var i = 0, length = arguments.length; i < length; i++) { 440 index = objects.indexOf(arguments[i]); 441 442 // only call onObjectRemoved if an object was actually removed 443 if (index !== -1) { 444 somethingRemoved = true; 445 objects.splice(index, 1); 446 this._onObjectRemoved && this._onObjectRemoved(arguments[i]); 447 } 448 } 449 450 this.renderOnAddRemove && somethingRemoved && this.requestRenderAll(); 451 return this; 452 }, 453 454 /** 455 * Executes given function for each object in this group 456 * @param {Function} callback 457 * Callback invoked with current object as first argument, 458 * index - as second and an array of all objects - as third. 459 * Callback is invoked in a context of Global Object (e.g. `window`) 460 * when no `context` argument is given 461 * 462 * @param {Object} context Context (aka thisObject) 463 * @return {Self} thisArg 464 * @chainable 465 */ 466 forEachObject: function (callback, context) { 467 var objects = this.getObjects(); 468 for (var i = 0, len = objects.length; i < len; i++) { 469 callback.call(context, objects[i], i, objects); 470 } 471 return this; 472 }, 473 474 /** 475 * Returns an array of children objects of this instance 476 * Type parameter introduced in 1.3.10 477 * since 2.3.5 this method return always a COPY of the array; 478 * @param {String} [type] When specified, only objects of this type are returned 479 * @return {Array} 480 */ 481 getObjects: function (type) { 482 if (typeof type === "undefined") { 483 return this._objects.concat(); 484 } 485 return this._objects.filter(function (o) { 486 return o.type === type; 487 }); 488 }, 489 490 /** 491 * Returns object at specified index 492 * @param {Number} index 493 * @return {Self} thisArg 494 */ 495 item: function (index) { 496 return this._objects[index]; 497 }, 498 499 /** 500 * Returns true if collection contains no objects 501 * @return {Boolean} true if collection is empty 502 */ 503 isEmpty: function () { 504 return this._objects.length === 0; 505 }, 506 507 /** 508 * Returns a size of a collection (i.e: length of an array containing its objects) 509 * @return {Number} Collection size 510 */ 511 size: function () { 512 return this._objects.length; 513 }, 514 515 /** 516 * Returns true if collection contains an object 517 * @param {Object} object Object to check against 518 * @param {Boolean} [deep=false] `true` to check all descendants, `false` to check only `_objects` 519 * @return {Boolean} `true` if collection contains an object 520 */ 521 contains: function (object, deep) { 522 if (this._objects.indexOf(object) > -1) { 523 return true; 524 } else if (deep) { 525 return this._objects.some(function (obj) { 526 return typeof obj.contains === "function" && obj.contains(object, true); 527 }); 528 } 529 return false; 530 }, 531 532 /** 533 * Returns number representation of a collection complexity 534 * @return {Number} complexity 535 */ 536 complexity: function () { 537 return this._objects.reduce(function (memo, current) { 538 memo += current.complexity ? current.complexity() : 0; 539 return memo; 540 }, 0); 541 }, 542}; 543 544/** 545 * @namespace fabric.CommonMethods 546 */ 547fabric.CommonMethods = { 548 /** 549 * Sets object's properties from options 550 * @param {Object} [options] Options object 551 */ 552 _setOptions: function (options) { 553 for (var prop in options) { 554 this.set(prop, options[prop]); 555 } 556 }, 557 558 /** 559 * @private 560 * @param {Object} [filler] Options object 561 * @param {String} [property] property to set the Gradient to 562 */ 563 _initGradient: function (filler, property) { 564 if (filler && filler.colorStops && !(filler instanceof fabric.Gradient)) { 565 this.set(property, new fabric.Gradient(filler)); 566 } 567 }, 568 569 /** 570 * @private 571 * @param {Object} [filler] Options object 572 * @param {String} [property] property to set the Pattern to 573 * @param {Function} [callback] callback to invoke after pattern load 574 */ 575 _initPattern: function (filler, property, callback) { 576 if (filler && filler.source && !(filler instanceof fabric.Pattern)) { 577 this.set(property, new fabric.Pattern(filler, callback)); 578 } else { 579 callback && callback(); 580 } 581 }, 582 583 /** 584 * @private 585 */ 586 _setObject: function (obj) { 587 for (var prop in obj) { 588 this._set(prop, obj[prop]); 589 } 590 }, 591 592 /** 593 * 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/control
593s. If you need to update those, call `setCoords()`. 594 * @param {String|Object} key Property name or object (if object, iterate over the object properties) 595 * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) 596 * @return {fabric.Object} thisArg 597 * @chainable 598 */ 599 set: function (key, value) { 600 if (typeof key === "object") { 601 this._setObject(key); 602 } else { 603 this._set(key, value); 604 } 605 return this; 606 }, 607 608 _set: function (key, value) { 609 this[key] = value; 610 }, 611 612 /** 613 * Toggles specified property from `true` to `false` or from `false` to `true` 614 * @param {String} property Property to toggle 615 * @return {fabric.Object} thisArg 616 * @chainable 617 */ 618 toggle: function (property) { 619 var value = this.get(property); 620 if (typeof value === "boolean") { 621 this.set(property, !value); 622 } 623 return this; 624 }, 625 626 /** 627 * Basic getter 628 * @param {String} property Property name 629 * @return {*} value of a property 630 */ 631 get: function (property) { 632 return this[property]; 633 }, 634}; 635 636(function (global) { 637 var sqrt = Math.sqrt, 638 atan2 = Math.atan2, 639 pow = Math.pow, 640 PiBy180 = Math.PI / 180, 641 PiBy2 = Math.PI / 2; 642 643 /** 644 * @namespace fabric.util 645 */ 646 fabric.util = { 647 /** 648 * Calculate the cos of an angle, avoiding returning floats for known results 649 * @static 650 * @memberOf fabric.util 651 * @param {Number} angle the angle in radians or in degree 652 * @return {Number} 653 */ 654 cos: function (angle) { 655 if (angle === 0) { 656 return 1; 657 } 658 if (angle < 0) { 659 // cos(a) = cos(-a) 660 angle = -angle; 661 } 662 var angleSlice = angle / PiBy2; 663 switch (angleSlice) { 664 case 1: 665 case 3: 666 return 0; 667 case 2: 668 return -1; 669 } 670 return Math.cos(angle); 671 }, 672 673 /** 674 * Calculate the sin of an angle, avoiding returning floats for known results 675 * @static 676 * @memberOf fabric.util 677 * @param {Number} angle the angle in radians or in degree 678 * @return {Number} 679 */ 680 sin: function (angle) { 681 if (angle === 0) { 682 return 0; 683 } 684 var angleSlice = angle / PiBy2, 685 sign = 1; 686 if (angle < 0) { 687 // sin(-a) = -sin(a) 688 sign = -1; 689 } 690 switch (angleSlice) { 691 case 1: 692 return sign; 693 case 2: 694 return 0; 695 case 3: 696 return -sign; 697 } 698 return Math.sin(angle); 699 }, 700 701 /** 702 * Removes value from an array. 703 * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` 704 * @static 705 * @memberOf fabric.util 706 * @param {Array} array 707 * @param {*} value 708 * @return {Array} original array 709 */ 710 removeFromArray: function (array, value) { 711 var idx = array.indexOf(value); 712 if (idx !== -1) { 713 array.splice(idx, 1); 714 } 715 return array; 716 }, 717 718 /** 719 * Returns random number between 2 specified ones. 720 * @static 721 * @memberOf fabric.util 722 * @param {Number} min lower limit 723 * @param {Number} max upper limit 724 * @return {Number} random value (between min and max) 725 */ 726 getRandomInt: function (min, max) { 727 return Math.floor(Math.random() * (max - min + 1)) + min; 728 }, 729 730 /** 731 * Transforms degrees to radians. 732 * @static 733 * @memberOf fabric.util 734 * @param {Number} degrees value in degrees 735 * @return {Number} value in radians 736 */ 737 degreesToRadians: function (degrees) { 738 return degrees * PiBy180; 739 }, 740 741 /** 742 * Transforms radians to degrees. 743 * @static 744 * @memberOf fabric.util 745 * @param {Number} radians value in radians 746 * @return {Number} value in degrees 747 */ 748 radiansToDegrees: function (radians) { 749 return radians / PiBy180; 750 }, 751 752 /** 753 * Rotates `point` around `origin` with `radians` 754 * @static 755 * @memberOf fabric.util 756 * @param {fabric.Point} point The point to rotate 757 * @param {fabric.Point} origin The origin of the rotation 758 * @param {Number} radians The radians of the angle for the rotation 759 * @return {fabric.Point} The new rotated point 760 */ 761 rotatePoint: function (point, origin, radians) { 762 var newPoint = new fabric.Point(point.x - origin.x, point.y - origin.y), 763 v = fabric.util.rotateVector(newPoint, radians); 764 return new fabric.Point(v.x, v.y).addEquals(origin); 765 }, 766 767 /** 768 * Rotates `vector` with `radians` 769 * @static 770 * @memberOf fabric.util 771 * @param {Object} vector The vector to rotate (x and y) 772 * @param {Number} radians The radians of the angle for the rotation 773 * @return {Object} The new rotated point 774 */ 775 rotateVector: function (vector, radians) { 776 var sin = fabric.util.sin(radians), 777 cos = fabric.util.cos(radians), 778 rx = vector.x * cos - vector.y * sin, 779 ry = vector.x * sin + vector.y * cos; 780 return { 781 x: rx, 782 y: ry, 783 }; 784 }, 785 786 /** 787 * Creates a vetor from points represented as a point 788 * @static 789 * @memberOf fabric.util 790 * 791 * @typedef {Object} Point 792 * @property {number} x 793 * @property {number} y 794 * 795 * @param {Point} from 796 * @param {Point} to 797 * @returns {Point} vector 798 */ 799 createVector: function (from, to) { 800 return new fabric.Point(to.x - from.x, to.y - from.y); 801 }, 802 803 /** 804 * Calculates angle between 2 vectors using dot product 805 * @static 806 * @memberOf fabric.util 807 * @param {Point} a 808 * @param {Point} b 809 * @returns the angle in radian between the vectors 810 */ 811 calcAngleBetweenVectors: function (a, b) { 812 return Math.acos( 813 (a.x * b.x + a.y * b.y) / (Math.hypot(a.x, a.y) * Math.hypot(b.x, b.y)), 814 ); 815 }, 816 817 /** 818 * @static 819 * @memberOf fabric.util 820 * @param {Point} v 821 * @returns {Point} vector representing the unit vector of pointing to the direction of `v` 822 */ 823 getHatVector: function (v) { 824 return new fabric.Point(v.x, v.y).multiply(1 / Math.hypot(v.x, v.y)); 825 }, 826 827 /** 828 * @static 829 * @memberOf fabric.util 830 * @param {Point} A 831 * @param {Point} B 832 * @param {Point} C 833 * @returns {{ vector: Point, angle: number }} vector representing the bisector of A and A's angle 834 */ 835 getBisector: function (A, B, C) { 836 var AB = fabric.util.createVector(A, B), 837 AC = fabric.util.createVector(A, C); 838 var alpha = fabric.util.calcAngleBetweenVectors(AB, AC); 839 // check if alpha is relative to AB->BC 840 var ro = fabric.util.calcAngleBetweenVectors( 841 fabric.util.rotateVector(AB, alpha), 842 AC, 843 ); 844 var phi = (alpha * (ro === 0 ? 1 : -1)) / 2; 845 return { 846 vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, phi)), 847 angle: alpha, 848 }; 849 }, 850 851 /** 852 * Project stroke width on points returning 2 projections for each point as follows: 853 * - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke. 854 * - `bevel`: 2 points corresponding to the bevel boundaries, tangent to the bisector. 855 * - `round`: same as `bevel` 856 * Used to calculate object's bounding box 857 * @static 858 * @memberOf fabric.util 859 * @param {Point[]} points 860 * @param {Object} options 861 * @param {number} options.strokeWidth 862 * @param {'miter'|'bevel'|'round'} options.strokeLineJoin 863 * @param {number} options.strokeMiterLimit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit 864 * @param {boolean} options.strokeUniform 865 * @param {number} options.scaleX 866 * @param {number} options.scaleY 867 * @param {boolean} [openPath] whether the shape is open or not, affects the calculations of the first and last points 868 * @returns {fabric.Point[]} array of size 2n/4n of all suspected points 869 */ 870 projectStrokeOnPoints: function (points, options, openPath) { 871 var coords = [], 872 s = options.strokeWidth / 2, 873 strokeUniformScalar = options.strokeUniform 874 ? new fabric.Point(1 / options.scaleX, 1 / options.scaleY) 875 : new fabric.Point(1, 1), 876 getStrokeHatVector = function (v) { 877 var scalar = s / Math.hypot(v.x, v.y); 878 return new fabric.Point( 879 v.x * scalar * strokeUniformScalar.x, 880 v.y * scalar * strokeUniformScalar.y, 881 ); 882 }; 883 if (points.length <= 1) { 884 return coords; 885 }
886 points.forEach(function (p, index) { 887 var A = new fabric.Point(p.x, p.y), 888 B, 889 C; 890 if (index === 0) { 891 C = points[index + 1]; 892 B = openPath 893 ? getStrokeHatVector(fabric.util.createVector(C, A)).addEquals(A) 894 : points[points.length - 1]; 895 } else if (index === points.length - 1) { 896 B = points[index - 1]; 897 C = openPath 898 ? getStrokeHatVector(fabric.util.createVector(B, A)).addEquals(A) 899 : points[0]; 900 } else { 901 B = points[index - 1]; 902 C = points[index + 1]; 903 } 904 var bisector = fabric.util.getBisector(A, B, C), 905 bisectorVector = bisector.vector, 906 alpha = bisector.angle, 907 scalar, 908 miterVector; 909 if (options.strokeLineJoin === "miter") { 910 scalar = -s / Math.sin(alpha / 2); 911 miterVector = new fabric.Point( 912 bisectorVector.x * scalar * strokeUniformScalar.x, 913 bisectorVector.y * scalar * strokeUniformScalar.y, 914 ); 915 if ( 916 Math.hypot(miterVector.x, miterVector.y) / s <= 917 options.strokeMiterLimit 918 ) { 919 coords.push(A.add(miterVector)); 920 coords.push(A.subtract(miterVector)); 921 return; 922 } 923 } 924 scalar = -s * Math.SQRT2; 925 miterVector = new fabric.Point( 926 bisectorVector.x * scalar * strokeUniformScalar.x, 927 bisectorVector.y * scalar * strokeUniformScalar.y, 928 ); 929 coords.push(A.add(miterVector)); 930 coords.push(A.subtract(miterVector)); 931 }); 932 return coords; 933 }, 934 935 /** 936 * Apply transform t to point p 937 * @static 938 * @memberOf fabric.util 939 * @param {fabric.Point} p The point to transform 940 * @param {Array} t The transform 941 * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied 942 * @return {fabric.Point} The transformed point 943 */ 944 transformPoint: function (p, t, ignoreOffset) { 945 if (ignoreOffset) { 946 return new fabric.Point( 947 t[0] * p.x + t[2] * p.y, 948 t[1] * p.x + t[3] * p.y, 949 ); 950 } 951 return new fabric.Point( 952 t[0] * p.x + t[2] * p.y + t[4], 953 t[1] * p.x + t[3] * p.y + t[5], 954 ); 955 }, 956 957 /** 958 * Returns coordinates of points's bounding rectangle (left, top, width, height) 959 * @param {Array} points 4 points array 960 * @param {Array} [transform] an array of 6 numbers representing a 2x3 transform matrix 961 * @return {Object} Object with left, top, width, height properties 962 */ 963 makeBoundingBoxFromPoints: function (points, transform) { 964 if (transform) { 965 for (var i = 0; i < points.length; i++) { 966 points[i] = fabric.util.transformPoint(points[i], transform); 967 } 968 } 969 var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x], 970 minX = fabric.util.array.min(xPoints), 971 maxX = fabric.util.array.max(xPoints), 972 width = maxX - minX, 973 yPoints = [points[0].y, points[1].y, points[2].y, points[3].y], 974 minY = fabric.util.array.min(yPoints), 975 maxY = fabric.util.array.max(yPoints), 976 height = maxY - minY; 977 978 return { 979 left: minX, 980 top: minY, 981 width: width, 982 height: height, 983 }; 984 }, 985 986 /** 987 * Invert transformation t 988 * @static 989 * @memberOf fabric.util 990 * @param {Array} t The transform 991 * @return {Array} The inverted transform 992 */ 993 invertTransform: function (t) { 994 var a = 1 / (t[0] * t[3] - t[1] * t[2]), 995 r = [a * t[3], -a * t[1], -a * t[2], a * t[0]], 996 o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r, true); 997 r[4] = -o.x; 998 r[5] = -o.y; 999 return r; 1000 }, 1001 1002 /** 1003 * A wrapper around Number#toFixed, which contrary to native method returns number, not string. 1004 * @static 1005 * @memberOf fabric.util 1006 * @param {Number|String} number number to operate on 1007 * @param {Number} fractionDigits number of fraction digits to "leave" 1008 * @return {Number} 1009 */ 1010 toFixed: function (number, fractionDigits) { 1011 return parseFloat(Number(number).toFixed(fractionDigits)); 1012 }, 1013 1014 /** 1015 * Converts from attribute value to pixel value if applicable. 1016 * Returns converted pixels or original value not converted. 1017 * @param {Number|String} value number to operate on 1018 * @param {Number} fontSize 1019 * @return {Number|String} 1020 */ 1021 parseUnit: function (value, fontSize) { 1022 var unit = /\D{0,2}$/.exec(value),
1023 number = parseFloat(value); 1024 if (!fontSize) { 1025 fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; 1026 } 1027 switch (unit[0]) { 1028 case "mm": 1029 return (number * fabric.DPI) / 25.4; 1030 1031 case "cm": 1032 return (number * fabric.DPI) / 2.54; 1033 1034 case "in": 1035 return number * fabric.DPI; 1036 1037 case "pt": 1038 return (number * fabric.DPI) / 72; // or * 4 / 3 1039 1040 case "pc": 1041 return ((number * fabric.DPI) / 72) * 12; // or * 16 1042 1043 case "em": 1044 return number * fontSize; 1045 1046 default: 1047 return number; 1048 } 1049 }, 1050 1051 /** 1052 * Function which always returns `false`. 1053 * @static 1054 * @memberOf fabric.util 1055 * @return {Boolean} 1056 */ 1057 falseFunction: function () { 1058 return false; 1059 }, 1060 1061 /** 1062 * Returns klass "Class" object of given namespace 1063 * @memberOf fabric.util 1064 * @param {String} type Type of object (eg. 'circle') 1065 * @param {String} namespace Namespace to get klass "Class" object from 1066 * @return {Object} klass "Class" 1067 */ 1068 getKlass: function (type, namespace) { 1069 // capitalize first letter only 1070 type = fabric.util.string.camelize( 1071 type.charAt(0).toUpperCase() + type.slice(1), 1072 ); 1073 return fabric.util.resolveNamespace(namespace)[type]; 1074 }, 1075 1076 /** 1077 * Returns array of attributes for given svg that fabric parses 1078 * @memberOf fabric.util 1079 * @param {String} type Type of svg element (eg. 'circle') 1080 * @return {Array} string names of supported attributes 1081 */ 1082 getSvgAttributes: function (type) { 1083 var attributes = ["instantiated_by_use", "style", "id", "class"]; 1084 switch (type) { 1085 case "linearGradient": 1086 attributes = attributes.concat([ 1087 "x1", 1088 "y1", 1089 "x2", 1090 "y2", 1091 "gradientUnits", 1092 "gradientTransform", 1093 ]); 1094 break; 1095 case "radialGradient": 1096 attributes = attributes.concat([ 1097 "gradientUnits", 1098 "gradientTransform", 1099 "cx", 1100 "cy", 1101 "r", 1102 "fx", 1103 "fy", 1104 "fr", 1105 ]); 1106 break; 1107 case "stop": 1108 attributes = attributes.concat([ 1109 "offset", 1110 "stop-color", 1111 "stop-opacity", 1112 ]); 1113 break; 1114 } 1115 return attributes; 1116 }, 1117 1118 /** 1119 * Returns object of given namespace 1120 * @memberOf fabric.util 1121 * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' 1122 * @return {Object} Object for given namespace (default fabric) 1123 */ 1124 resolveNamespace: function (namespace) { 1125 if (!namespace) { 1126 return fabric; 1127 } 1128 1129 var parts = namespace.split("."), 1130 len = parts.length, 1131 i, 1132 obj = global || fabric.window; 1133 1134 for (i = 0; i < len; ++i) { 1135 obj = obj[parts[i]]; 1136 } 1137 1138 return obj; 1139 }, 1140 1141 /** 1142 * Loads image element from given url and passes it to a callback 1143 * @memberOf fabric.util 1144 * @param {String} url URL representing an image 1145 * @param {Function} callback Callback; invoked with loaded image 1146 * @param {*} [context] Context to invoke callback in 1147 * @param {Object} [crossOrigin] crossOrigin value to set image element to 1148 */ 1149 loadImage: function (url, callback, context, crossOrigin) { 1150 if (!url) { 1151 callback && callback.call(context, url); 1152 return; 1153 } 1154 url = replaceCdnUrl(url); 1155 var img = fabric.util.createImage(); 1156 1157 /** @ignore */ 1158 var onLoadCallback = function () { 1159 callback && callback.call(context, img, false); 1160 img = img.onload = img.onerror = null; 1161 }; 1162 1163 img.onload = onLoadCallback; 1164 /** @ignore */ 1165 img.onerror = function () { 1166 fabric.log("Error loading " + img.src); 1167 callback && callback.call(context, null, true); 1168 img = img.onload = img.onerror = null; 1169 }; 1170 1171 // data-urls appear to be buggy with crossOrigin 1172 // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 1173 // see https://code.google.com/p/chromium/issues/detail?id=315152 1174 // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 1175 // crossOrigin null is the same as not set. 1176 if ( 1177 url.indexOf("data") !== 0 && 1178 crossOrigin !== undefined && 1179 crossOrigin !== null 1180 ) { 1181 img.crossOrigin = crossOrigin; 1182 } 1183 1184 // IE10 / IE11-Fix: SVG contents from data: URI 1185 // will only be available if the IMG is present 1186 // in the DOM (and visible) 1187 if (url.substring(0, 14) === "data:image/svg") { 1188 img.onload = null; 1189 fabric.util.loadImageInDom(img, onLoadCallback); 1190 } 1191 1192 img.src = url; 1193 }, 1194 1195 /** 1196 * Attaches SVG image with data: URL to the dom 1197 * @memberOf fabric.util 1198 * @param {Object} img Image object with data:image/svg src 1199 * @param {Function} callback Callback; invoked with loaded image 1200 * @return {Object} DOM element (div containing the SVG image) 1201 */ 1202 loadImageInDom: function (img, onLoadCallback) { 1203 var div = fabric.document.createElement("div"); 1204 div.style.width = div.style.height = "1px"; 1205 div.style.left = div.style.top = "-100%"; 1206 div.style.position = "absolute"; 1207 div.appendChild(img); 1208 fabric.document.querySelector("body").appendChild(div); 1209 /** 1210 * Wrap in function to: 1211 * 1. Call existing callback 1212 * 2. Cleanup DOM 1213 */ 1214 img.onload = function () { 1215 onLoadCallback(); 1216 div.parentNode.removeChild(div); 1217 div = null; 1218 }; 1219 }, 1220 1221 /** 1222 * Creates corresponding fabric instances from their object representations 1223 * @static 1224 * @memberOf fabric.util 1225 * @param {Array} objects Objects to enliven 1226 * @param {Function} callback Callback to invoke when all objects are created 1227 * @param {String} namespace Namespace to get klass "Class" object from 1228 * @param {Function} reviver Method for further parsing of object elements, 1229 * called after each fabric object created. 1230 */ 1231 enlivenObjects: function (objects, callback, namespace, reviver) { 1232 objects = objects || []; 1233 1234 var enlivenedObjects = [], 1235 numLoadedObjects = 0, 1236 numTotalObjects = objects.length; 1237 1238 function onLoaded() { 1239 if (++numLoadedObjects === numTotalObjects) { 1240 callback && 1241 callback( 1242 enlivenedObjects.filter(function (obj) { 1243 // filter out undefined objects (objects that gave error) 1244 return obj; 1245 }), 1246 ); 1247 } 1248 } 1249 1250 if (!numTotalObjects) { 1251 callback && callback(enlivenedObjects); 1252 return; 1253 } 1254
1255 objects.forEach(function (o, index) { 1256 // if sparse array 1257 if (!o || !o.type) { 1258 onLoaded(); 1259 return; 1260 } 1261 var klass = fabric.util.getKlass(o.type, namespace); 1262 klass.fromObject(o, function (obj, error) { 1263 error || (enlivenedObjects[index] = obj); 1264 reviver && reviver(o, obj, error); 1265 onLoaded(); 1266 }); 1267 }); 1268 }, 1269 1270 /** 1271 * Creates corresponding fabric instances residing in an object, e.g. `clipPath` 1272 * @see {@link fabric.Object.ENLIVEN_PROPS} 1273 * @param {Object} object 1274 * @param {Object} [context] assign enlived props to this object (pass null to skip this) 1275 * @param {(objects:fabric.Object[]) => void} callback 1276 */ 1277 enlivenObjectEnlivables: function (object, context, callback) { 1278 var enlivenProps = fabric.Object.ENLIVEN_PROPS.filter(function (key) { 1279 return !!object[key]; 1280 }); 1281 fabric.util.enlivenObjects( 1282 enlivenProps.map(function (key) { 1283 return object[key]; 1284 }), 1285 function (enlivedProps) { 1286 var objects = {}; 1287 enlivenProps.forEach(function (key, index) { 1288 objects[key] = enlivedProps[index]; 1289 context && (context[key] = enlivedProps[index]); 1290 }); 1291 callback && callback(objects); 1292 }, 1293 ); 1294 }, 1295 1296 /** 1297 * Create and wait for loading of patterns 1298 * @static 1299 * @memberOf fabric.util 1300 * @param {Array} patterns Objects to enliven 1301 * @param {Function} callback Callback to invoke when all objects are created 1302 * called after each fabric object created. 1303 */ 1304 enlivenPatterns: function (patterns, callback) { 1305 patterns = patterns || []; 1306 1307 function onLoaded() { 1308 if (++numLoadedPatterns === numPatterns) { 1309 callback && callback(enlivenedPatterns); 1310 } 1311 } 1312 1313 var enlivenedPatterns = [], 1314 numLoadedPatterns = 0, 1315 numPatterns = patterns.length; 1316 1317 if (!numPatterns) { 1318 callback && callback(enlivenedPatterns); 1319 return; 1320 } 1321 1322 patterns.forEach(function (p, index) { 1323 if (p && p.source) { 1324 new fabric.Pattern(p, function (pattern) { 1325 enlivenedPatterns[index] = pattern; 1326 onLoaded(); 1327 }); 1328 } else { 1329 enlivenedPatterns[index] = p; 1330 onLoaded(); 1331 } 1332 }); 1333 }, 1334 1335 /** 1336 * Groups SVG elements (usually those retrieved from SVG document) 1337 * @static 1338 * @memberOf fabric.util 1339 * @param {Array} elements SVG elements to group 1340 * @param {Object} [options] Options object 1341 * @param {String} path Value to set sourcePath to 1342 * @return {fabric.Object|fabric.Group} 1343 */ 1344 groupSVGElements: function (elements, options, path) { 1345 var object; 1346 if (elements && elements.length === 1) { 1347 if (typeof path !== "undefined") { 1348 elements[0].sourcePath = path; 1349 } 1350 return elements[0]; 1351 } 1352 if (options) { 1353 if (options.width && options.height) { 1354 options.centerPoint = { 1355 x: options.width / 2, 1356 y: options.height / 2, 1357 }; 1358 } else { 1359 delete options.width; 1360 delete options.height; 1361 } 1362 } 1363 object = new fabric.Group(elements, options); 1364 if (typeof path !== "undefined") { 1365 object.sourcePath = path; 1366 } 1367 return object; 1368 }, 1369 1370 /** 1371 * Populates an object with properties of another object 1372 * @static 1373 * @memberOf fabric.util 1374 * @param {Object} source Source object 1375 * @param {Object} destination Destination object 1376 * @return {Array} properties Properties names to include 1377 */ 1378 populateWithProperties: function (source, destination, properties) { 1379 if (properties && Array.isArray(properties)) { 1380 for (var i = 0, len = properties.length; i < len; i++) { 1381 if (properties[i] in source) { 1382 destination[properties[i]] = source[properties[i]]; 1383 } 1384 } 1385 } 1386 }, 1387 1388 /** 1389 * Creates canvas element 1390 * @static 1391 * @memberOf fabric.util 1392 * @return {CanvasElement} initialized canvas element 1393 */ 1394 createCanvasElement: function () { 1395 return fabric.document.createElement("canvas"); 1396 }, 1397 1398 /** 1399 * Creates a canvas element that is a copy of another and is also painted 1400 * @param {CanvasElement} canvas to copy size and content of 1401 * @static 1402 * @memberOf fabric.util 1403 * @return {CanvasElement} initialized canvas element 1404 */ 1405 copyCanvasElement: function (canvas) { 1406 var newCanvas = fabric.util.createCanvasElement(); 1407 newCanvas.width = canvas.width; 1408 newCanvas.height = canvas.height; 1409 newCanvas.getContext("2d").drawImage(canvas, 0, 0); 1410 return newCanvas; 1411 }, 1412 1413 /** 1414 * since 2.6.0 moved from canvas instance to utility. 1415 * @param {CanvasElement} canvasEl to copy size and content of 1416 * @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too 1417 * @param {Number} quality <= 1 and > 0 1418 * @static 1419 * @memberOf fabric.util 1420 * @return {String} data url 1421 */ 1422 toDataURL: function (canvasEl, format, quality) { 1423 return canvasEl.toDataURL("image/" + format, quality); 1424 }, 1425 1426 /** 1427 * Creates image element (works on client and node) 1428 * @static 1429 * @memberOf fabric.util 1430 * @return {HTMLImageElement} HTML image element 1431 */ 1432 createImage: function () { 1433 return fabric.document.createElement("img"); 1434 }, 1435 1436 /** 1437 * Multiply matrix A by matrix B to nest transformations 1438 * @static 1439 * @memberOf fabric.util 1440 * @param {Array} a First transformMatrix 1441 * @param {Array} b Second transformMatrix 1442 * @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices 1443 * @return {Array} The product of the two transform matrices 1444 */ 1445 multiplyTransformMatrices: function (a, b, is2x2) { 1446 // Matrix multiply a * b 1447 return [ 1448 a[0] * b[0] + a[2] * b[1], 1449 a[1] * b[0] + a[3] * b[1], 1450 a[0] * b[2] + a[2] * b[3], 1451 a[1] * b[2] + a[3] * b[3], 1452 is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4], 1453 is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5], 1454 ]; 1455 }, 1456 1457 /** 1458 * Decomposes standard 2x3 matrix into transform components 1459 * @static 1460 * @memberOf fabric.util 1461 * @param {Array} a transformMatrix 1462 * @return {Object} Components of transform 1463 */ 1464 qrDecompose: function (a) { 1465 var angle = atan2(a[1], a[0]), 1466 denom = pow(a[0], 2) + pow(a[1], 2), 1467 scaleX = sqrt(denom), 1468 scaleY = (a[0] * a[3] - a[2] * a[1]) / scaleX, 1469 skewX = atan2(a[0] * a[2] + a[1] * a[3], denom); 1470 return { 1471 angle: angle / PiBy180, 1472 scaleX: scaleX, 1473 scaleY: scaleY, 1474 skewX: skewX / PiBy180, 1475 skewY: 0, 1476 translateX: a[4], 1477 translateY: a[5], 1478 }; 1479 }, 1480 1481 /** 1482 * Returns a transform matrix starting from an object of the same kind of 1483 * the one returned from qrDecompose, useful also if you want to calculate some 1484 * transformations from an object that is not enlived yet 1485 * @static 1486 * @memberOf fabric.util 1487 * @param {Object} options 1488 * @param {Number} [options.angle] angle in degrees 1489 * @return {Number[]} transform matrix 1490 */ 1491 calcRotateMatrix: function (options) { 1492 if (!options.angle) { 1493 return fabric.iMatrix.concat(); 1494 } 1495 var theta = fabric.util.degreesToRadians(options.angle), 1496 cos = fabric.util.cos(theta), 1497 sin = fabric.util.sin(theta); 1498 return [cos, sin, -sin, cos, 0, 0]; 1499 }, 1500 1501 /** 1502 * Returns a transform matrix starting from an object of the same kind of 1503 * the one returned from qrDecompose, useful also if you want to calculate some 1504 * transformations from an object that is not enlived yet. 1505 * is called DimensionsTransformMatrix because those properties are the one that influence 1506 * the size of the resulting box of the object. 1507 * @static 1508 * @memberOf fabric.util 1509 * @param {Object} options 1510 * @param {Number} [options.scaleX] 1511 * @param {Number} [options.scaleY] 1512 * @param {Boolean} [options.flipX] 1513 * @param {Boolean} [options.flipY] 1514 * @param {Number} [options.skewX] 1515 * @param {Number} [options.skewY] 1516 * @return {Number[]} transform matrix 1517 */ 1518 calcDimensionsMatrix: function (options) { 1519 var scaleX = typeof options.scaleX === "undefined" ? 1 : options.scaleX, 1520 scaleY = typeof options.scaleY === "undefined" ? 1 : options.scaleY, 1521 scaleMatrix = [ 1522 options.flipX ? -scaleX : scaleX, 1523 0, 1524 0, 1525 options.flipY ? -scaleY : scaleY, 1526 0, 1527 0, 1528 ], 1529 multiply = fabric.util.multiplyTransformMatrices, 1530 degreesToRadians = fabric.util.degreesToRadians; 1531 if (options.skewX) { 1532 scaleMatrix = multiply( 1533 scaleMatrix, 1534 [1, 0, Math.tan(degreesToRadians(options.skewX)), 1], 1535 true, 1536 ); 1537 } 1538 if (options.skewY) { 1539 scaleMatrix = multiply( 1540 scaleMatrix, 1541 [1, Math.tan(degreesToRadians(options.skewY)), 0, 1], 1542 true, 1543 ); 1544 } 1545 return scaleMatrix; 1546 }, 1547 1548 /** 1549 * Returns a transform matrix starting from an object of the same kind of 1550 * the one returned from qrDecompose, useful also if you want to calculate some 1551 * transformations from an object that is not enlived yet 1552 * @static 1553 * @memberOf fabric.util 1554 * @param {Object} options 1555 * @param {Number} [options.angle] 1556 * @param {Number} [options.scaleX] 1557 * @param {Number} [options.scaleY] 1558 * @param {Boolean} [options.flipX] 1559 * @param {Boolean} [options.flipY] 1560 * @param {Number} [options.skewX] 1561 * @param {Number} [options.skewX] 1562 * @param {Number} [options.translateX] 1563 * @param {Number} [options.translateY] 1564 * @return {Number[]} transform matrix 1565 */ 1566 composeMatrix: function (options) { 1567 var matrix = [ 1568 1, 1569 0, 1570 0, 1571 1, 1572 options.translateX || 0, 1573 options.translateY || 0, 1574 ], 1575 multiply = fabric.util.multiplyTransformMatrices; 1576 if (options.angle) { 1577 matrix = multiply(matrix, fabric.util.calcRotateMatrix(options)); 1578 } 1579 if ( 1580 options.scaleX !== 1 || 1581 options.scaleY !== 1 || 1582 options.skewX || 1583 options.skewY || 1584 options.flipX || 1585 options.flipY 1586 ) { 1587 matrix = multiply(matrix, fabric.util.calcDimensionsMatrix(options)); 1588 } 1589 return matrix; 1590 }, 1591 1592 /** 1593 * reset an object transform state to neutral. Top and left are not accounted for 1594 * @static 1595 * @memberOf fabric.util 1596 * @param {fabric.Object} target object to transform 1597 */ 1598 resetObjectTransform: function (target) { 1599 target.scaleX = 1; 1600 target.scaleY = 1;
1601 target.skewX = 0; 1602 target.skewY = 0; 1603 target.flipX = false; 1604 target.flipY = false; 1605 target.rotate(0); 1606 }, 1607 1608 /** 1609 * Extract Object transform values 1610 * @static 1611 * @memberOf fabric.util 1612 * @param {fabric.Object} target object to read from 1613 * @return {Object} Components of transform 1614 */ 1615 saveObjectTransform: function (target) { 1616 return { 1617 scaleX: target.scaleX, 1618 scaleY: target.scaleY, 1619 skewX: target.skewX, 1620 skewY: target.skewY, 1621 angle: target.angle, 1622 left: target.left, 1623 flipX: target.flipX, 1624 flipY: target.flipY, 1625 top: target.top, 1626 }; 1627 }, 1628 1629 /** 1630 * Returns true if context has transparent pixel 1631 * at specified location (taking tolerance into account) 1632 * @param {CanvasRenderingContext2D} ctx context 1633 * @param {Number} x x coordinate 1634 * @param {Number} y y coordinate 1635 * @param {Number} tolerance Tolerance 1636 */ 1637 isTransparent: function (ctx, x, y, tolerance) { 1638 // If tolerance is > 0 adjust start coords to take into account. 1639 // If moves off Canvas fix to 0 1640 if (tolerance > 0) { 1641 if (x > tolerance) { 1642 x -= tolerance; 1643 } else { 1644 x = 0; 1645 } 1646 if (y > tolerance) { 1647 y -= tolerance; 1648 } else { 1649 y = 0; 1650 } 1651 } 1652 1653 var _isTransparent = true, 1654 i, 1655 temp, 1656 imageData = ctx.getImageData( 1657 x, 1658 y, 1659 tolerance * 2 || 1, 1660 tolerance * 2 || 1, 1661 ), 1662 l = imageData.data.length; 1663 1664 // Split image data - for tolerance > 1, pixelDataSize = 4; 1665 for (i = 3; i < l; i += 4) { 1666 temp = imageData.data[i]; 1667 _isTransparent = temp <= 0; 1668 if (_isTransparent === false) { 1669 break; // Stop if colour found 1670 } 1671 } 1672 1673 imageData = null; 1674 1675 return _isTransparent; 1676 }, 1677 1678 /** 1679 * Parse preserveAspectRatio attribute from element 1680 * @param {string} attribute to be parsed 1681 * @return {Object}
1681 an object containing align and meetOrSlice attribute 1682 */ 1683 parsePreserveAspectRatioAttribute: function (attribute) { 1684 var meetOrSlice = "meet", 1685 alignX = "Mid", 1686 alignY = "Mid", 1687 aspectRatioAttrs = attribute.split(" "), 1688 align; 1689 1690 if (aspectRatioAttrs && aspectRatioAttrs.length) { 1691 meetOrSlice = aspectRatioAttrs.pop(); 1692 if (meetOrSlice !== "meet" && meetOrSlice !== "slice") { 1693 align = meetOrSlice; 1694 meetOrSlice = "meet"; 1695 } else if (aspectRatioAttrs.length) { 1696 align = aspectRatioAttrs.pop(); 1697 } 1698 } 1699 //divide align in alignX and alignY 1700 alignX = align !== "none" ? align.slice(1, 4) : "none"; 1701 alignY = align !== "none" ? align.slice(5, 8) : "none"; 1702 return { 1703 meetOrSlice: meetOrSlice, 1704 alignX: alignX, 1705 alignY: alignY, 1706 }; 1707 }, 1708 1709 /** 1710 * Clear char widths cache for the given font family or all the cache if no 1711 * fontFamily is specified. 1712 * Use it if you know you are loading fonts in a lazy way and you are not waiting 1713 * for custom fonts to load properly when adding text objects to the canvas. 1714 * If a text object is added when its own font is not loaded yet, you will get wrong 1715 * measurement and so wrong bounding boxes. 1716 * After the font cache is cleared, either change the textObject text content or call 1717 * initDimensions() to trigger a recalculation 1718 * @memberOf fabric.util 1719 * @param {String} [fontFamily] font family to clear 1720 */ 1721 clearFabricFontCache: function (fontFamily) { 1722 fontFamily = (fontFamily || "").toLowerCase(); 1723 if (!fontFamily) { 1724 fabric.charWidthsCache = {}; 1725 } else if (fabric.charWidthsCache[fontFamily]) { 1726 delete fabric.charWidthsCache[fontFamily]; 1727 } 1728 }, 1729 1730 /** 1731 * Given current aspect ratio, determines the max width and height that can 1732 * respect the total allowed area for the cache. 1733 * @memberOf fabric.util 1734 * @param {Number} ar aspect ratio 1735 * @param {Number} maximumArea Maximum area you want to achieve 1736 * @return {Object.x} Limited dimensions by X 1737 * @return {Object.y} Limited dimensions by Y 1738 */ 1739 limitDimsByArea: function (ar, maximumArea) { 1740 var roughWidth = Math.sqrt(maximumArea * ar), 1741 perfLimitSizeY = Math.floor(maximumArea / roughWidth); 1742 return { x: Math.floor(roughWidth), y: perfLimitSizeY }; 1743 }, 1744 1745 capValue: function (min, value, max) { 1746 return Math.max(min, Math.min(value, max)); 1747 }, 1748 1749 /** 1750 * Finds the scale for the object source to fit inside the object destination, 1751 * keeping aspect ratio intact. 1752 * respect the total allowed area for the cache. 1753 * @memberOf fabric.util 1754 * @param {Object | fabric.Object} source 1755 * @param {Number} source.height natural unscaled height of the object 1756 * @param {Number} source.width natural unscaled width of the object 1757 * @param {Object | fabric.Object} destination 1758 * @param {Number} destination.height natural unscaled height of the object 1759 * @param {Number} destination.width natural unscaled width of the object 1760 * @return {Number} scale factor to apply to source to fit into destination 1761 */ 1762 findScaleToFit: function (source, destination) { 1763 return Math.min( 1764 destination.width / source.width, 1765 destination.height / source.height, 1766 ); 1767 }, 1768 1769 /** 1770 * Finds the scale for the object source to cover entirely the object destination, 1771 * keeping aspect ratio intact. 1772 * respect the total allowed area for the cache. 1773 * @memberOf fabric.util 1774 * @param {Object | fabric.Object} source 1775 * @param {Number} source.height natural unscaled height of the object 1776 * @param {Number} source.width natural unscaled width of the object 1777 * @param {Object | fabric.Object} destination 1778 * @param {Number} destination.height natural unscaled height of the object 1779 * @param {Number} destination.width natural unscaled width of the object 1780 * @return {Number} scale factor to apply to source to cover destination 1781 */ 1782 findScaleToCover: function (source, destination) { 1783 return Math.max( 1784 destination.width / source.width, 1785 destination.height / source.height, 1786 ); 1787 }, 1788 1789 /** 1790 * given an array of 6 number returns something like `"matrix(...numbers)"` 1791 * @memberOf fabric.util 1792 * @param {Array} transform an array with 6 numbers 1793 * @return {String} transform matrix for svg 1794 * @return {Object.y} Limited dimensions by Y 1795 */ 1796 matrixToSVG: function (transform) { 1797 return (
1798 "matrix(" + 1799 transform 1800 .map(function (value) { 1801 return fabric.util.toFixed( 1802 value, 1803 fabric.Object.NUM_FRACTION_DIGITS, 1804 ); 1805 }) 1806 .join(" ") + 1807 ")" 1808 ); 1809 }, 1810 1811 /** 1812 * given an object and a transform, apply the inverse transform to the object, 1813 * this is equivalent to remove from that object that transformation, so that 1814 * added in a space with the removed transform, the object will be the same as before. 1815 * Removing from an object a transform that scale by 2 is like scaling it by 1/2. 1816 * Removing from an object a transfrom that rotate by 30deg is like rotating by 30deg 1817 * in the opposite direction. 1818 * This util is used to add objects inside transformed groups or nested groups. 1819 * @memberOf fabric.util 1820 * @param {fabric.Object} object the object you want to transform 1821 * @param {Array} transform the destination transform 1822 */ 1823 removeTransformFromObject: function (object, transform) { 1824 var inverted = fabric.util.invertTransform(transform), 1825 finalTransform = fabric.util.multiplyTransformMatrices( 1826 inverted, 1827 object.calcOwnMatrix(), 1828 ); 1829 fabric.util.applyTransformToObject(object, finalTransform); 1830 }, 1831 1832 /** 1833 * given an object and a transform, apply the transform to the object. 1834 * this is equivalent to change the space where the object is drawn. 1835 * Adding to an object a transform that scale by 2 is like scaling it by 2. 1836 * This is used when removing an object from an active selection for example. 1837 * @memberOf fabric.util 1838 * @param {fabric.Object} object the object you want to transform 1839 * @param {Array} transform the destination transform 1840 */ 1841 addTransformToObject: function (object, transform) { 1842 fabric.util.applyTransformToObject( 1843 object, 1844 fabric.util.multiplyTransformMatrices( 1845 transform, 1846 object.calcOwnMatrix(), 1847 ), 1848 ); 1849 }, 1850 1851 /** 1852 * discard an object transform state and apply the one from the matrix. 1853 * @memberOf fabric.util 1854 * @param {fabric.Object} object the object you want to transform 1855 * @param {Array} transform the destination transform 1856 */ 1857 applyTransformToObject: function (object, transform) { 1858 var options = fabric.util.qrDecompose(transform), 1859 center = new fabric.Point(options.translateX, options.translateY); 1860 object.flipX = false; 1861 object.flipY = false; 1862 object.set("scaleX", options.scaleX); 1863 object.set("scaleY", options.scaleY); 1864 object.skewX = options.skewX; 1865 object.skewY = options.skewY; 1866 object.angle = options.angle; 1867 object.setPositionByOrigin(center, "center", "center"); 1868 }, 1869 1870 /** 1871 * given a width and height, return the size of the bounding box 1872 * that can contains the box with width/height with applied transform 1873 * described in options. 1874 * Use to calculate the boxes around objects for controls. 1875 * @memberOf fabric.util 1876 * @param {Number} width 1877 * @param {Number} height 1878 * @param {Object} options 1879 * @param {Number} options.scaleX 1880 * @param {Number} options.scaleY 1881 * @param {Number} options.skewX 1882 * @param {Number} options.skewY 1883 * @return {Object.x} width of containing 1884 * @return {Object.y} height of containing 1885 */ 1886 sizeAfterTransform: function (width, height, options) { 1887 var dimX = width / 2, 1888 dimY = height / 2, 1889 points = [ 1890 { 1891 x: -dimX, 1892 y: -dimY, 1893 }, 1894 { 1895 x: dimX, 1896 y: -dimY, 1897 }, 1898 { 1899 x: -dimX, 1900 y: dimY, 1901 }, 1902 { 1903 x: dimX, 1904 y: dimY, 1905 }, 1906 ], 1907 transformMatrix = fabric.util.calcDimensionsMatrix(options), 1908 bbox = fabric.util.makeBoundingBoxFromPoints(points, transformMatrix); 1909 return { 1910 x: bbox.width, 1911 y: bbox.height, 1912 }; 1913 }, 1914 1915 /** 1916 * Merges 2 clip paths into one visually equal clip path 1917 * 1918 * **IMPORTANT**:\ 1919 * Does **NOT** clone the arguments, clone them proir if necessary. 1920 * 1921 * Creates a wrapper (group) that contains one clip path and is clipped by the other so content is kept where both overlap. 1922 * Use this method if both the clip paths may have nested clip paths of their own, so assigning one to the other's clip path property is not possible. 1923 * 1924 * In order to handle the `inverted` property we follow logic described
1924in the following cases:\ 1925 * **(1)** both clip paths are inverted - the clip paths pass the inverted prop to the wrapper and loose it themselves.\ 1926 * **(2)** one is inverted and the other isn't - the wrapper shouldn't become inverted and the inverted clip path must clip the non inverted one to produce an identical visual effect.\ 1927 * **(3)** both clip paths are not inverted - wrapper and clip paths remain unchanged. 1928 * 1929 * @memberOf fabric.util 1930 * @param {fabric.Object} c1 1931 * @param {fabric.Object} c2 1932 * @returns {fabric.Object} merged clip path 1933 */ 1934 mergeClipPaths: function (c1, c2) { 1935 var a = c1, 1936 b = c2; 1937 if (a.inverted && !b.inverted) { 1938 // case (2) 1939 a = c2; 1940 b = c1; 1941 } 1942 // `b` becomes `a`'s clip path so we transform `b` to `a` coordinate plane 1943 fabric.util.applyTransformToObject( 1944 b, 1945 fabric.util.multiplyTransformMatrices( 1946 fabric.util.invertTransform(a.calcTransformMatrix()), 1947 b.calcTransformMatrix(), 1948 ), 1949 ); 1950 // assign the `inverted` prop to the wrapping group 1951 var inverted = a.inverted && b.inverted; 1952 if (inverted) { 1953 // case (1) 1954 a.inverted = b.inverted = false; 1955 } 1956 return new fabric.Group([a], { clipPath: b, inverted: inverted }); 1957 }, 1958 1959 /** 1960 * @memberOf fabric.util 1961 * @param {Object} prevStyle first style to compare 1962 * @param {Object} thisStyle second style to compare 1963 * @param {boolean} forTextSpans whether to check overline, underline, and line-through properties 1964 * @return {boolean} true if the style changed 1965 */ 1966 hasStyleChanged: function (prevStyle, thisStyle, forTextSpans) { 1967 forTextSpans = forTextSpans || false; 1968 return ( 1969 prevStyle.fill !== thisStyle.fill || 1970 prevStyle.stroke !== thisStyle.stroke || 1971 prevStyle.strokeWidth !== thisStyle.strokeWidth || 1972 prevStyle.fontSize !== thisStyle.fontSize || 1973 prevStyle.fontFamily !== thisStyle.fontFamily || 1974 prevStyle.fontWeight !== thisStyle.fontWeight || 1975 prevStyle.fontStyle !== thisStyle.fontStyle || 1976 prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || 1977 prevStyle.deltaY !== thisStyle.deltaY || 1978 (forTextSpans && 1979 (prevStyle.overline !== thisStyle.overline || 1980 prevStyle.underline !== thisStyle.underline || 1981 prevStyle.linethrough !== thisStyle.linethrough)) 1982 ); 1983 }, 1984 1985 /** 1986 * Returns the array form of a text object's inline styles property with styles grouped in ranges 1987 * rather than per character. This format is less verbose, and is better suited for storage 1988 * so it is used in serialization (not during runtime). 1989 * @memberOf fabric.util 1990 * @param {object} styles per character styles for a text object 1991 * @param {String} text the text string that the styles are applied to 1992 * @return {{start: number, end: number, style: object}[]} 1993 */ 1994 stylesToArray: function (styles, text) { 1995 // clone style structure to prevent mutation 1996 var styles = fabric.util.object.clone(styles, true), 1997 textLines = text.split("\n"), 1998 charIndex = -1, 1999 prevStyle = {}, 2000 stylesArray = []; 2001 //loop through each textLine 2002 for (var i = 0; i < textLines.length; i++) { 2003 if (!styles[i]) { 2004 //no styles exist for this line, so add the line's length to the charIndex total 2005 charIndex += textLines[i].length; 2006 continue; 2007 } 2008 //loop through each character of the current line 2009 for (var c = 0; c < textLines[i].length; c++) { 2010 charIndex++; 2011 var thisStyle = styles[i][c]; 2012 //check if style exists for this character 2013 if (thisStyle && Object.keys(thisStyle).length > 0) { 2014 var styleChanged = fabric.util.hasStyleChanged( 2015 prevStyle, 2016 thisStyle, 2017 true, 2018 ); 2019 if (styleChanged) { 2020 stylesArray.push({ 2021 start: charIndex, 2022 end: charIndex + 1, 2023 style: thisStyle, 2024 }); 2025 } else { 2026 //if style is the same as previous character, increase end index 2027 stylesArray[stylesArray.length - 1].end++; 2028 } 2029 } 2030 prevStyle = thisStyle || {}; 2031 } 2032 } 2033 return stylesArray; 2034 }, 2035 2036 /** 2037 * Returns the object form of the styles property with styles that are assigned per 2038 * character rather than grouped by range. This format is more verbose, and is 2039 * only used during runtime (not for serialization/storage) 2040 * @memberOf fabric.util 2041 * @param {Array} styles the serialized form of a text object's styles 2042 * @param {String}
2042 text the text string that the styles are applied to 2043 * @return {Object} 2044 */ 2045 stylesFromArray: function (styles, text) { 2046 if (!Array.isArray(styles)) { 2047 return styles; 2048 } 2049 var textLines = text.split("\n"), 2050 charIndex = -1, 2051 styleIndex = 0, 2052 stylesObject = {}; 2053 //loop through each textLine 2054 for (var i = 0; i < textLines.length; i++) { 2055 //loop through each character of the current line 2056 for (var c = 0; c < textLines[i].length; c++) { 2057 charIndex++; 2058 //check if there's a style collection that includes the current character 2059 if ( 2060 styles[styleIndex] && 2061 styles[styleIndex].start <= charIndex && 2062 charIndex < styles[styleIndex].end 2063 ) { 2064 //create object for line index if it doesn't exist 2065 stylesObject[i] = stylesObject[i] || {}; 2066 //assign a style at this character's index 2067 stylesObject[i][c] = Object.assign({}, styles[styleIndex].style); 2068 //if character is at the end of the current style collection, move to the next 2069 if (charIndex === styles[styleIndex].end - 1) { 2070 styleIndex++; 2071 } 2072 } 2073 } 2074 } 2075 return stylesObject; 2076 }, 2077 }; 2078})(typeof exports !== "undefined" ? exports : this); 2079 2080(function () { 2081 var _join = Array.prototype.join, 2082 commandLengths = { 2083 m: 2, 2084 l: 2, 2085 h: 1, 2086 v: 1, 2087 c: 6, 2088 s: 4, 2089 q: 4, 2090 t: 2, 2091 a: 7, 2092 }, 2093 repeatedCommands = { 2094 m: "l", 2095 M: "L", 2096 }; 2097 function segmentToBezier( 2098 th2, 2099 th3, 2100 cosTh, 2101 sinTh, 2102 rx, 2103 ry, 2104 cx1, 2105 cy1, 2106 mT, 2107 fromX, 2108 fromY, 2109 ) { 2110 var costh2 = fabric.util.cos(th2), 2111 sinth2 = fabric.util.sin(th2), 2112 costh3 = fabric.util.cos(th3), 2113 sinth3 = fabric.util.sin(th3), 2114 toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, 2115 toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, 2116 cp1X = fromX + mT * (-cosTh * rx * sinth2 - sinTh * ry * costh2), 2117 cp1Y = fromY + mT * (-sinTh * rx * sinth2 + cosTh * ry * costh2), 2118 cp2X = toX + mT * (cosTh * rx * sinth3 + sinTh * ry * costh3), 2119 cp2Y = toY + mT * (sinTh * rx * sinth3 - cosTh * ry * costh3); 2120 2121 return ["C", cp1X, cp1Y, cp2X, cp2Y, toX, toY]; 2122 } 2123 2124 /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp 2125 * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here 2126 * http://mozilla.org/MPL/2.0/ 2127 */ 2128 function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { 2129 var PI = Math.PI, 2130 th = (rotateX * PI) / 180, 2131 sinTh = fabric.util.sin(th), 2132 cosTh = fabric.util.cos(th), 2133 fromX = 0, 2134 fromY = 0; 2135 2136 rx = Math.abs(rx); 2137 ry = Math.abs(ry); 2138 2139 var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5, 2140 py = -cosTh * toY * 0.5 + sinTh * toX * 0.5, 2141 rx2 = rx * rx, 2142 ry2 = ry * ry, 2143 py2 = py * py, 2144 px2 = px * px, 2145 pl = rx2 * ry2 - rx2 * py2 - ry2 * px2, 2146 root = 0; 2147 2148 if (pl < 0) { 2149 var s = Math.sqrt(1 - pl / (rx2 * ry2)); 2150 rx *= s; 2151 ry *= s; 2152 } else { 2153 root = 2154 (large === sweep ? -1.0 : 1.0) * 2155 Math.sqrt(pl / (rx2 * py2 + ry2 * px2)); 2156 } 2157 2158 var cx = (root * rx * py) / ry, 2159 cy = (-root * ry * px) / rx, 2160 cx1 = cosTh * cx - sinTh * cy + toX * 0.5, 2161 cy1 = sinTh * cx + cosTh * cy + toY * 0.5, 2162 mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), 2163 dtheta = calcVectorAngle( 2164 (px - cx) / rx, 2165 (py - cy) / ry, 2166 (-px - cx) / rx, 2167 (-py - cy) / ry, 2168 ); 2169 2170 if (sweep === 0 && dtheta > 0) { 2171 dtheta -= 2 * PI; 2172 } else if (sweep === 1 && dtheta < 0) { 2173 dtheta += 2 * PI; 2174 } 2175 2176 // Convert into cubic bezier segments <= 90deg 2177 var segments = Math.ceil(Math.abs((dtheta / PI) * 2)), 2178 result = [], 2179 mDelta = dtheta / segments, 2180 mT = 2181 ((8 / 3) * Math.sin(mDelta / 4) * Math.sin(mDelta / 4)) / 2182 Math.sin(mDelta / 2), 2183 th3 = mTheta + mDelta; 2184 2185 for (var i = 0; i < segments; i++) { 2186 result[i] = segmentToBezier( 2187 mTheta, 2188 th3, 2189 cosTh, 2190 sinTh, 2191 rx, 2192 ry, 2193 cx1, 2194 cy1, 2195 mT, 2196 fromX, 2197 fromY, 2198 ); 2199 fromX = result[i][5]; 2200 fromY = result[i][6]; 2201 mTheta = th3; 2202 th3 += mDelta; 2203 } 2204 return result; 2205 } 2206 2207 /* 2208 * Private 2209 */ 2210 function calcVectorAngle(ux, uy, vx, vy) {
2211 var ta = Math.atan2(uy, ux), 2212 tb = Math.atan2(vy, vx); 2213 if (tb >= ta) { 2214 return tb - ta; 2215 } else { 2216 return 2 * Math.PI - (ta - tb); 2217 } 2218 } 2219 2220 /** 2221 * Calculate bounding box of a beziercurve 2222 * @param {Number} x0 starting point 2223 * @param {Number} y0 2224 * @param {Number} x1 first control point 2225 * @param {Number} y1 2226 * @param {Number} x2 secondo control point 2227 * @param {Number} y2 2228 * @param {Number} x3 end of bezier 2229 * @param {Number} y3 2230 */ 2231 // taken from http://jsbin.com/ivomiq/56/edit no credits available for that. 2232 // TODO: can we normalize this with the starting points set at 0 and then translated the bbox? 2233 function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { 2234 var argsString; 2235 if (fabric.cachesBoundsOfCurve) { 2236 argsString = _join.call(arguments); 2237 if (fabric.boundsOfCurveCache[argsString]) { 2238 return fabric.boundsOfCurveCache[argsString]; 2239 } 2240 } 2241 2242 var sqrt = Math.sqrt, 2243 min = Math.min, 2244 max = Math.max, 2245 abs = Math.abs, 2246 tvalues = [], 2247 bounds = [[], []], 2248 a, 2249 b, 2250 c, 2251 t, 2252 t1, 2253 t2, 2254 b2ac, 2255 sqrtb2ac; 2256 2257 b = 6 * x0 - 12 * x1 + 6 * x2; 2258 a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; 2259 c = 3 * x1 - 3 * x0; 2260 2261 for (var i = 0; i < 2; ++i) { 2262 if (i > 0) { 2263 b = 6 * y0 - 12 * y1 + 6 * y2; 2264 a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; 2265 c = 3 * y1 - 3 * y0; 2266 } 2267 2268 if (abs(a) < 1e-12) { 2269 if (abs(b) < 1e-12) { 2270 continue; 2271 } 2272 t = -c / b; 2273 if (0 < t && t < 1) { 2274 tvalues.push(t); 2275 } 2276 continue; 2277 } 2278 b2ac = b * b - 4 * c * a; 2279 if (b2ac < 0) { 2280 continue; 2281 } 2282 sqrtb2ac = sqrt(b2ac); 2283 t1 = (-b + sqrtb2ac) / (2 * a); 2284 if (0 < t1 && t1 < 1) { 2285 tvalues.push(t1); 2286 } 2287 t2 = (-b - sqrtb2ac) / (2 * a); 2288 if (0 < t2 && t2 < 1) { 2289 tvalues.push(t2); 2290 } 2291 } 2292 2293 var x, 2294 y, 2295 j = tvalues.length, 2296 jlen = j, 2297 mt; 2298 while (j--) { 2299 t = tvalues[j]; 2300 mt = 1 - t; 2301 x = 2302 mt * mt * mt * x0 + 2303 3 * mt * mt * t * x1 + 2304 3 * mt * t * t * x2 + 2305 t * t * t * x3; 2306 bounds[0][j] = x; 2307 2308 y = 2309 mt * mt * mt * y0 + 2310 3 * mt * mt * t * y1 + 2311 3 * mt * t * t * y2 + 2312 t * t * t * y3; 2313 bounds[1][j] = y; 2314 } 2315 2316 bounds[0][jlen] = x0; 2317 bounds[1][jlen] = y0; 2318 bounds[0][jlen + 1] = x3; 2319 bounds[1][jlen + 1] = y3; 2320 var result = [ 2321 { 2322 x: min.apply(null, bounds[0]), 2323 y: min.apply(null, bounds[1]), 2324 }, 2325 { 2326 x: max.apply(null, bounds[0]), 2327 y: max.apply(null, bounds[1]), 2328 }, 2329 ]; 2330 if (fabric.cachesBoundsOfCurve) { 2331 fabric.boundsOfCurveCache[argsString] = result; 2332 } 2333 return result; 2334 } 2335 2336 /** 2337 * Converts arc to a bunch of bezier curves 2338 * @param {Number} fx starting point x 2339 * @param {Number} fy starting point y 2340 * @param {Array} coords Arc command 2341 */ 2342 function fromArcToBeziers(fx, fy, coords) { 2343 var rx = coords[1], 2344 ry = coords[2], 2345 rot = coords[3], 2346 large = coords[4], 2347 sweep = coords[5], 2348 tx = coords[6], 2349 ty = coords[7], 2350 segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); 2351 2352 for (var i = 0, len = segsNorm.length; i < len; i++) { 2353 segsNorm[i][1] += fx; 2354 segsNorm[i][2] += fy; 2355 segsNorm[i][3] += fx; 2356 segsNorm[i][4] += fy; 2357 segsNorm[i][5] += fx; 2358 segsNorm[i][6] += fy; 2359 } 2360 return segsNorm; 2361 } 2362 2363 /** 2364 * This function take a parsed SVG path and make it simpler for fabricJS logic. 2365 * simplification consist of: only UPPERCASE absolute commands ( relative converted to absolute ) 2366 * S converted in C, T converted in Q, A converted in C. 2367 * @param {Array} path the array of commands of a parsed svg path for fabric.Path 2368 * @return {Array} the simplified array of commands of a parsed svg path for fabric.Path 2369 */ 2370 function makePathSimpler(path) { 2371 // x and y represent the last point of the path. the previous command point. 2372 // we add them to each relative command to make it an absolute comment. 2373 // we also swap the v V h H with L, because are easier to transform. 2374 var x = 0, 2375 y = 0, 2376 len = path.length, 2377 // x1 and y1 represent the last point of the subpath. the subpath is started with 2378 // m or M command. When a z or Z command is drawn, x and y need to be resetted to 2379 // the last x1 and y1. 2380 x1 = 0, 2381 y1 = 0, 2382 current, 2383 i, 2384 converted, 2385 // previous will host the letter of the previous command, to handle S and T. 2386 // controlX and controlY will host the previous reflected control point 2387 destinationPath = [], 2388 previous, 2389 controlX, 2390 controlY; 2391 for (i = 0; i < len; ++i) { 2392 converted = false;
vendor: 4,829 bytes, lines 2393-2555
2393 current = path[i].slice(0); 2394 switch ( 2395 current[0] // first letter 2396 ) { 2397 case "l": // lineto, relative 2398 current[0] = "L"; 2399 current[1] += x; 2400 current[2] += y; 2401 // falls through 2402 case "L": 2403 x = current[1]; 2404 y = current[2]; 2405 break; 2406 case "h": // horizontal lineto, relative 2407 current[1] += x; 2408 // falls through 2409 case "H": 2410 current[0] = "L"; 2411 current[2] = y; 2412 x = current[1]; 2413 break; 2414 case "v": // vertical lineto, relative 2415 current[1] += y; 2416 // falls through 2417 case "V": 2418 current[0] = "L"; 2419 y = current[1]; 2420 current[1] = x; 2421 current[2] = y; 2422 break; 2423 case "m": // moveTo, relative 2424 current[0] = "M"; 2425 current[1] += x; 2426 current[2] += y; 2427 // falls through 2428 case "M": 2429 x = current[1]; 2430 y = current[2]; 2431 x1 = current[1]; 2432 y1 = current[2]; 2433 break; 2434 case "c": // bezierCurveTo, relative 2435 current[0] = "C"; 2436 current[1] += x; 2437 current[2] += y; 2438 current[3] += x; 2439 current[4] += y; 2440 current[5] += x; 2441 current[6] += y; 2442 // falls through 2443 case "C": 2444 controlX = current[3]; 2445 controlY = current[4]; 2446 x = current[5]; 2447 y = current[6]; 2448 break; 2449 case "s": // shorthand cubic bezierCurveTo, relative 2450 current[0] = "S"; 2451 current[1] += x; 2452 current[2] += y; 2453 current[3] += x; 2454 current[4] += y; 2455 // falls through 2456 case "S": 2457 // would be sScC but since we are swapping sSc for C, we check just that. 2458 if (previous === "C") { 2459 // calculate reflection of previous control points 2460 controlX = 2 * x - controlX; 2461 controlY = 2 * y - controlY; 2462 } else { 2463 // If there is no previous command or if the previous command was not a C, c, S, or s, 2464 // the control point is coincident with the current point 2465 controlX = x; 2466 controlY = y; 2467 } 2468 x = current[3]; 2469 y = current[4]; 2470 current[0] = "C"; 2471 current[5] = current[3]; 2472 current[6] = current[4]; 2473 current[3] = current[1]; 2474 current[4] = current[2]; 2475 current[1] = controlX; 2476 current[2] = controlY; 2477 // current[3] and current[4] are NOW the second control point. 2478 // we keep it for the next reflection. 2479 controlX = current[3]; 2480 controlY = current[4]; 2481 break; 2482 case "q": // quadraticCurveTo, relative 2483 current[0] = "Q"; 2484 current[1] += x; 2485 current[2] += y; 2486 current[3] += x; 2487 current[4] += y; 2488 // falls through 2489 case "Q": 2490 controlX = current[1]; 2491 controlY = current[2]; 2492 x = current[3]; 2493 y = current[4]; 2494 break; 2495 case "t": // shorthand quadraticCurveTo, relative 2496 current[0] = "T"; 2497 current[1] += x; 2498 current[2] += y; 2499 // falls through 2500 case "T": 2501 if (previous === "Q") { 2502 // calculate reflection of previous control point 2503 controlX = 2 * x - controlX; 2504 controlY = 2 * y - controlY; 2505 } else { 2506 // If there is no previous command or if the previous command was not a Q, q, T or t, 2507 // assume the control point is coincident with the current point 2508 controlX = x; 2509 controlY = y; 2510 } 2511 current[0] = "Q"; 2512 x = current[1]; 2513 y = current[2]; 2514 current[1] = controlX; 2515 current[2] = controlY; 2516 current[3] = x; 2517 current[4] = y; 2518 break; 2519 case "a": 2520 current[0] = "A"; 2521 current[6] += x; 2522 current[7] += y; 2523 // falls through 2524 case "A": 2525 converted = true; 2526 destinationPath = destinationPath.concat( 2527 fromArcToBeziers(x, y, current), 2528 ); 2529 x = current[6]; 2530 y = current[7]; 2531 break; 2532 case "z": 2533 case "Z": 2534 x = x1; 2535 y = y1; 2536 break; 2537 default: 2538 } 2539 if (!converted) { 2540 destinationPath.push(current); 2541 } 2542 previous = current[0]; 2543 } 2544 return destinationPath; 2545 } 2546 2547 /** 2548 * Calc length from point x1,y1 to x2,y2 2549 * @param {Number} x1 starting point x 2550 * @param {Number} y1 starting point y 2551 * @param {Number} x2 starting point x 2552 * @param {Number} y2 starting point y 2553 * @return {Number} length of segment 2554 */ 2555 function calcLineLength(x1, y1, x2, y2) {
2556 return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); 2557 } 2558 2559 // functions for the Cubic beizer 2560 // taken from: https://github.com/konvajs/konva/blob/7.0.5/src/shapes/Path.ts#L350 2561 function CB1(t) { 2562 return t * t * t; 2563 } 2564 function CB2(t) { 2565 return 3 * t * t * (1 - t); 2566 } 2567 function CB3(t) { 2568 return 3 * t * (1 - t) * (1 - t); 2569 } 2570 function CB4(t) { 2571 return (1 - t) * (1 - t) * (1 - t); 2572 } 2573 2574 function getPointOnCubicBezierIterator( 2575 p1x, 2576 p1y, 2577 p2x, 2578 p2y, 2579 p3x, 2580 p3y, 2581 p4x, 2582 p4y, 2583 ) { 2584 return function (pct) { 2585 var c1 = CB1(pct), 2586 c2 = CB2(pct), 2587 c3 = CB3(pct), 2588 c4 = CB4(pct); 2589 return { 2590 x: p4x * c1 + p3x * c2 + p2x * c3 + p1x * c4, 2591 y: p4y * c1 + p3y * c2 + p2y * c3 + p1y * c4, 2592 }; 2593 }; 2594 } 2595 2596 function getTangentCubicIterator(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) { 2597 return function (pct) { 2598 var invT = 1 - pct, 2599 tangentX = 2600 3 * invT * invT * (p2x - p1x) + 2601 6 * invT * pct * (p3x - p2x) + 2602 3 * pct * pct * (p4x - p3x), 2603 tangentY = 2604 3 * invT * invT * (p2y - p1y) + 2605 6 * invT * pct * (p3y - p2y) + 2606 3 * pct * pct * (p4y - p3y); 2607 return Math.atan2(tangentY, tangentX); 2608 }; 2609 } 2610 2611 function QB1(t) { 2612 return t * t; 2613 } 2614 2615 function QB2(t) { 2616 return 2 * t * (1 - t); 2617 } 2618 2619 function QB3(t) { 2620 return (1 - t) * (1 - t); 2621 } 2622 2623 function getPointOnQuadraticBezierIterator(p1x, p1y, p2x, p2y, p3x, p3y) { 2624 return function (pct) { 2625 var c1 = QB1(pct), 2626 c2 = QB2(pct), 2627 c3 = QB3(pct); 2628 return { 2629 x: p3x * c1 + p2x * c2 + p1x * c3, 2630 y: p3y * c1 + p2y * c2 + p1y * c3, 2631 }; 2632 }; 2633 } 2634 2635 function getTangentQuadraticIterator(p1x, p1y, p2x, p2y, p3x, p3y) { 2636 return function (pct) { 2637 var invT = 1 - pct, 2638 tangentX = 2 * invT * (p2x - p1x) + 2 * pct * (p3x - p2x), 2639 tangentY = 2 * invT * (p2y - p1y) + 2 * pct * (p3y - p2y); 2640 return Math.atan2(tangentY, tangentX); 2641 }; 2642 } 2643 2644 // this will run over a path segment ( a cubic or quadratic segment) and approximate it 2645 // with 100 segemnts. This will good enough to calculate the length of the curve 2646 function pathIterator(iterator, x1, y1) { 2647 var tempP = { x: x1, y: y1 }, 2648 p, 2649 tmpLen = 0, 2650 perc; 2651 for (perc = 1; perc <= 100; perc += 1) { 2652 p = iterator(perc / 100); 2653 tmpLen += calcLineLength(tempP.x, tempP.y, p.x, p.y); 2654 tempP = p; 2655 } 2656 return tmpLen; 2657 } 2658 2659 /** 2660 * Given a pathInfo, and a distance in pixels, find the percentage from 0 to 1 2661 * that correspond to that pixels run over the path. 2662 * The percentage will be then used to find the correct point on the canvas for the path. 2663 * @param {Array} segInfo fabricJS collection of information on a parsed path 2664 * @param {Number} distance from starting point, in pixels. 2665 * @return {Object} info object with x and y ( the point on canvas ) and angle, the tangent on that point; 2666 */ 2667 function findPercentageForDistance(segInfo, distance) { 2668 var perc = 0, 2669 tmpLen = 0, 2670 iterator = segInfo.iterator, 2671 tempP = { x: segInfo.x, y: segInfo.y }, 2672 p, 2673 nextLen, 2674 nextStep = 0.01, 2675 angleFinder = segInfo.angleFinder, 2676 lastPerc; 2677 // nextStep > 0.0001 covers 0.00015625 that 1/64th of 1/100 2678 // the path 2679 while (tmpLen < distance && nextStep > 0.0001) { 2680 p = iterator(perc); 2681 lastPerc = perc; 2682 nextLen = calcLineLength(tempP.x, tempP.y, p.x, p.y); 2683 // compare tmpLen each cycle with distance, decide next perc to test. 2684 if (nextLen + tmpLen > distance) { 2685 // we discard this step and we make smaller steps. 2686 perc -= nextStep; 2687 nextStep /= 2; 2688 } else { 2689 tempP = p; 2690 perc += nextStep; 2691 tmpLen += nextLen; 2692 } 2693 } 2694 p.angle = angleFinder(lastPerc); 2695 return p; 2696 } 2697 2698 /** 2699 * Run over a parsed and simplifed path and extrac some informations. 2700 * informations are length of each command and starting point 2701 * @param {Array} path fabricJS parsed path commands 2702 * @return {Array} path commands informations 2703 */ 2704 function getPathSegmentsInfo(path) { 2705 var totalLength = 0, 2706 len = path.length, 2707 current,
2708 //x2 and y2 are the coords of segment start 2709 //x1 and y1 are the coords of the current point 2710 x1 = 0, 2711 y1 = 0, 2712 x2 = 0, 2713 y2 = 0, 2714 info = [], 2715 iterator, 2716 tempInfo, 2717 angleFinder; 2718 for (var i = 0; i < len; i++) { 2719 current = path[i]; 2720 tempInfo = { 2721 x: x1, 2722 y: y1, 2723 command: current[0], 2724 }; 2725 switch ( 2726 current[0] //first letter 2727 ) { 2728 case "M": 2729 tempInfo.length = 0; 2730 x2 = x1 = current[1]; 2731 y2 = y1 = current[2]; 2732 break; 2733 case "L": 2734 tempInfo.length = calcLineLength(x1, y1, current[1], current[2]); 2735 x1 = current[1]; 2736 y1 = current[2]; 2737 break; 2738 case "C": 2739 iterator = getPointOnCubicBezierIterator( 2740 x1, 2741 y1, 2742 current[1], 2743 current[2], 2744 current[3], 2745 current[4], 2746 current[5], 2747 current[6], 2748 ); 2749 angleFinder = getTangentCubicIterator( 2750 x1, 2751 y1, 2752 current[1], 2753 current[2], 2754 current[3], 2755 current[4], 2756 current[5], 2757 current[6], 2758 ); 2759 tempInfo.iterator = iterator; 2760 tempInfo.angleFinder = angleFinder; 2761 tempInfo.length = pathIterator(iterator, x1, y1); 2762 x1 = current[5]; 2763 y1 = current[6]; 2764 break; 2765 case "Q": 2766 iterator = getPointOnQuadraticBezierIterator( 2767 x1, 2768 y1, 2769 current[1], 2770 current[2], 2771 current[3], 2772 current[4], 2773 ); 2774 angleFinder = getTangentQuadraticIterator( 2775 x1, 2776 y1, 2777 current[1], 2778 current[2], 2779 current[3], 2780 current[4], 2781 ); 2782 tempInfo.iterator = iterator; 2783 tempInfo.angleFinder = angleFinder; 2784 tempInfo.length = pathIterator(iterator, x1, y1); 2785 x1 = current[3]; 2786 y1 = current[4]; 2787 break; 2788 case "Z": 2789 case "z": 2790 // we add those in order to ease calculations later 2791 tempInfo.destX = x2; 2792 tempInfo.destY = y2; 2793 tempInfo.length = calcLineLength(x1, y1, x2, y2); 2794 x1 = x2; 2795 y1 = y2; 2796 break; 2797 } 2798 totalLength += tempInfo.length; 2799 info.push(tempInfo); 2800 } 2801 info.push({ length: totalLength, x: x1, y: y1 }); 2802 return info; 2803 } 2804 2805 function getPointOnPath(path, distance, infos) { 2806 if (!infos) { 2807 infos = getPathSegmentsInfo(path); 2808 } 2809 var i = 0; 2810 while (distance - infos[i].length > 0 && i < infos.length - 2) { 2811 distance -= infos[i].length; 2812 i++; 2813 } 2814 // var distance = infos[infos.length - 1] * perc; 2815 var segInfo = infos[i], 2816 segPercent = distance / segInfo.length, 2817 command = segInfo.command, 2818 segment = path[i], 2819 info; 2820 2821 switch (command) { 2822 case "M": 2823 return { x: segInfo.x, y: segInfo.y, angle: 0 }; 2824 case "Z": 2825 case "z": 2826 info = new fabric.Point(segInfo.x, segInfo.y).lerp( 2827 new fabric.Point(segInfo.destX, segInfo.destY), 2828 segPercent, 2829 ); 2830 info.angle = Math.atan2( 2831 segInfo.destY - segInfo.y, 2832 segInfo.destX - segInfo.x, 2833 ); 2834 return info; 2835 case "L": 2836 info = new fabric.Point(segInfo.x, segInfo.y).lerp( 2837 new fabric.Point(segment[1], segment[2]), 2838 segPercent, 2839 ); 2840 info.angle = Math.atan2(segment[2] - segInfo.y, segment[1] - segInfo.x); 2841 return info; 2842 case "C": 2843 return findPercentageForDistance(segInfo, distance); 2844 case "Q": 2845 return findPercentageForDistance(segInfo, distance); 2846 } 2847 } 2848 2849 /** 2850 * 2851 * @param {string} pathString 2852 * @return {(string|number)[][]} An array of SVG path commands 2853 * @example <caption>Usage</caption> 2854 * parsePath('M 3 4 Q 3 5 2 1 4 0 Q 9 12 2 1 4 0') === [ 2855 * ['M', 3, 4], 2856 * ['Q', 3, 5, 2, 1, 4, 0], 2857 * ['Q', 9, 12, 2, 1, 4, 0], 2858 * ]; 2859 * 2860 */ 2861 function parsePath(pathString) { 2862 var result = [], 2863 coords = [], 2864 currentPath, 2865 parsed, 2866 re = fabric.rePathCommand, 2867 rNumber = "[-+]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][-+]?\\d+)?\\s*", 2868 rNumberCommaWsp = "(" + rNumber + ")" + fabric.commaWsp, 2869 rFlagCommaWsp = "([01])" + fabric.commaWsp + "?", 2870 rArcSeq = 2871 rNumberCommaWsp + 2872 "?" + 2873 rNumberCommaWsp + 2874 "?" + 2875 rNumberCommaWsp + 2876 rFlagCommaWsp + 2877 rFlagCommaWsp + 2878 rNumberCommaWsp + 2879 "?(" + 2880 rNumber + 2881 ")", 2882 regArcArgumentSequence = new RegExp(rArcSeq, "g"), 2883 match, 2884 coordsStr,
vendor: 6,859 bytes, lines 2885-3120
2885 // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) 2886 path; 2887 if (!pathString || !pathString.match) { 2888 return result; 2889 } 2890 path = pathString.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); 2891 2892 for (var i = 0, coordsParsed, len = path.length; i < len; i++) { 2893 currentPath = path[i]; 2894 2895 coordsStr = currentPath.slice(1).trim(); 2896 coords.length = 0; 2897 2898 var command = currentPath.charAt(0); 2899 coordsParsed = [command]; 2900 2901 if (command.toLowerCase() === "a") { 2902 // arcs have special flags that apparently don't require spaces so handle special 2903 for (var args; (args = regArcArgumentSequence.exec(coordsStr)); ) { 2904 for (var j = 1; j < args.length; j++) { 2905 coords.push(args[j]); 2906 } 2907 } 2908 } else { 2909 while ((match = re.exec(coordsStr))) { 2910 coords.push(match[0]); 2911 } 2912 } 2913 2914 for (var j = 0, jlen = coords.length; j < jlen; j++) { 2915 parsed = parseFloat(coords[j]); 2916 if (!isNaN(parsed)) { 2917 coordsParsed.push(parsed); 2918 } 2919 } 2920 2921 var commandLength = commandLengths[command.toLowerCase()], 2922 repeatedCommand = repeatedCommands[command] || command; 2923 2924 if (coordsParsed.length - 1 > commandLength) { 2925 for ( 2926 var k = 1, klen = coordsParsed.length; 2927 k < klen; 2928 k += commandLength 2929 ) { 2930 result.push( 2931 [command].concat(coordsParsed.slice(k, k + commandLength)), 2932 ); 2933 command = repeatedCommand; 2934 } 2935 } else { 2936 result.push(coordsParsed); 2937 } 2938 } 2939 2940 return result; 2941 } 2942 2943 /** 2944 * 2945 * Converts points to a smooth SVG path 2946 * @param {{ x: number,y: number }[]} points Array of points 2947 * @param {number} [correction] Apply a correction to the path (usually we use `width / 1000`). If value is undefined 0 is used as the correction value. 2948 * @return {(string|number)[][]} An array of SVG path commands 2949 */ 2950 function getSmoothPathFromPoints(points, correction) { 2951 var path = [], 2952 i, 2953 p1 = new fabric.Point(points[0].x, points[0].y), 2954 p2 = new fabric.Point(points[1].x, points[1].y), 2955 len = points.length, 2956 multSignX = 1, 2957 multSignY = 0, 2958 manyPoints = len > 2; 2959 correction = correction || 0; 2960 2961 if (manyPoints) { 2962 multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1; 2963 multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1; 2964 } 2965 path.push([ 2966 "M", 2967 p1.x - multSignX * correction, 2968 p1.y - multSignY * correction, 2969 ]); 2970 for (i = 1; i < len; i++) { 2971 if (!p1.eq(p2)) { 2972 var midPoint = p1.midPointFrom(p2); 2973 // p1 is our bezier control point 2974 // midpoint is our endpoint 2975 // start point is p(i-1) value. 2976 path.push(["Q", p1.x, p1.y, midPoint.x, midPoint.y]); 2977 } 2978 p1 = points[i]; 2979 if (i + 1 < points.length) { 2980 p2 = points[i + 1]; 2981 } 2982 } 2983 if (manyPoints) { 2984 multSignX = 2985 p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1; 2986 multSignY = 2987 p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1; 2988 } 2989 path.push([ 2990 "L", 2991 p1.x + multSignX * correction, 2992 p1.y + multSignY * correction, 2993 ]); 2994 return path; 2995 } 2996 /** 2997 * Transform a path by transforming each segment. 2998 * it has to be a simplified path or it won't work. 2999 * WARNING: this depends from pathOffset for correct operation 3000 * @param {Array} path fabricJS parsed and simplified path commands 3001 * @param {Array} transform matrix that represent the transformation 3002 * @param {Object} [pathOffset] the fabric.Path pathOffset 3003 * @param {Number} pathOffset.x 3004 * @param {Number} pathOffset.y 3005 * @returns {Array} the transformed path 3006 */ 3007 function transformPath(path, transform, pathOffset) { 3008 if (pathOffset) { 3009 transform = fabric.util.multiplyTransformMatrices(transform, [ 3010 1, 3011 0, 3012 0, 3013 1, 3014 -pathOffset.x, 3015 -pathOffset.y, 3016 ]); 3017 } 3018 return path.map(function (pathSegment) { 3019 var newSegment = pathSegment.slice(0), 3020 point = {}; 3021 for (var i = 1; i < pathSegment.length - 1; i += 2) { 3022 point.x = pathSegment[i]; 3023 point.y = pathSegment[i + 1]; 3024 point = fabric.util.transformPoint(point, transform); 3025 newSegment[i] = point.x; 3026 newSegment[i + 1] = point.y; 3027 } 3028 return newSegment; 3029 }); 3030 } 3031 3032 /** 3033 * Join path commands to go back to svg format 3034 * @param {Array} pathData fabricJS parsed path commands 3035 * @return {String} joined path 'M 0 0 L 20 30' 3036 */ 3037 fabric.util.joinPath = function (pathData) { 3038 return pathData 3039 .map(function (segment) { 3040 return segment.join(" "); 3041 }) 3042 .join(" "); 3043 }; 3044 fabric.util.parsePath = parsePath; 3045 fabric.util.makePathSimpler = makePathSimpler; 3046 fabric.util.getSmoothPathFromPoints = getSmoothPathFromPoints; 3047 fabric.util.getPathSegmentsInfo = getPathSegmentsInfo; 3048 fabric.util.getBoundsOfCurve = getBoundsOfCurve; 3049 fabric.util.getPointOnPath = getPointOnPath; 3050 fabric.util.transformPath = transformPath; 3051})(); 3052 3053(function () { 3054 var slice = Array.prototype.slice; 3055 3056 /** 3057 * Invokes method on all items in a given array 3058 * @memberOf fabric.util.array 3059 * @param {Array} array Array to iterate over 3060 * @param {String} method Name of a method to invoke 3061 * @return {Array} 3062 */ 3063 function invoke(array, method) { 3064 var args = slice.call(arguments, 2), 3065 result = []; 3066 for (var i = 0, len = array.length; i < len; i++) { 3067 result[i] = args.length 3068 ? array[i][method].apply(array[i], args) 3069 : array[i][method].call(array[i]); 3070 } 3071 return result; 3072 } 3073 3074 /** 3075 * Finds maximum value in array (not necessarily "first" one) 3076 * @memberOf fabric.util.array 3077 * @param {Array} array Array to iterate over 3078 * @param {String} byProperty 3079 * @return {*} 3080 */ 3081 function max(array, byProperty) { 3082 return find(array, byProperty, function (value1, value2) { 3083 return value1 >= value2; 3084 }); 3085 } 3086 3087 /** 3088 * Finds minimum value in array (not necessarily "first" one) 3089 * @memberOf fabric.util.array 3090 * @param {Array} array Array to iterate over 3091 * @param {String} byProperty 3092 * @return {*} 3093 */ 3094 function min(array, byProperty) { 3095 return find(array, byProperty, function (value1, value2) { 3096 return value1 < value2; 3097 }); 3098 } 3099 3100 /** 3101 * @private 3102 */ 3103 function fill(array, value) { 3104 var k = array.length; 3105 while (k--) { 3106 array[k] = value; 3107 } 3108 return array; 3109 } 3110 3111 /** 3112 * @private 3113 */ 3114 function find(array, byProperty, condition) { 3115 if (!array || array.length === 0) { 3116 return; 3117 } 3118 3119 var i = array.length - 1, 3120 result = byProperty ? array[i][byProperty] : array[i];
3121 if (byProperty) { 3122 while (i--) { 3123 if (condition(array[i][byProperty], result)) { 3124 result = array[i][byProperty]; 3125 } 3126 } 3127 } else { 3128 while (i--) { 3129 if (condition(array[i], result)) { 3130 result = array[i]; 3131 } 3132 } 3133 } 3134 return result; 3135 } 3136 3137 /** 3138 * @namespace fabric.util.array 3139 */ 3140 fabric.util.array = { 3141 fill: fill, 3142 invoke: invoke, 3143 min: min, 3144 max: max, 3145 }; 3146})(); 3147 3148(function () { 3149 /** 3150 * Copies all enumerable properties of one js object to another 3151 * this does not and cannot compete with generic utils. 3152 * Does not clone or extend fabric.Object subclasses. 3153 * This is mostly for internal use and has extra handling for fabricJS objects 3154 * it skips the canvas and group properties in deep cloning. 3155 * @memberOf fabric.util.object 3156 * @param {Object} destination Where to copy to 3157 * @param {Object} source Where to copy from 3158 * @param {Boolean} [deep] Whether to extend nested objects 3159 * @return {Object} 3160 */ 3161 3162 function extend(destination, source, deep) { 3163 // JScript DontEnum bug is not taken care of 3164 // the deep clone is for internal use, is not meant to avoid 3165 // javascript traps or cloning html element or self referenced objects. 3166 if (deep) { 3167 if (!fabric.isLikelyNode && source instanceof Element) { 3168 // avoid cloning deep images, canvases, 3169 destination = source; 3170 } else if (source instanceof Array) { 3171 destination = []; 3172 for (var i = 0, len = source.length; i < len; i++) { 3173 destination[i] = extend({}, source[i], deep); 3174 } 3175 } else if (source && typeof source === "object") { 3176 for (var property in source) { 3177 if (property === "canvas" || property === "group") { 3178 // we do not want to clone this props at all. 3179 // we want to keep the keys in the copy 3180 destination[property] = null; 3181 } else if (source.hasOwnProperty(property)) { 3182 destination[property] = extend({}, source[property], deep); 3183 } 3184 } 3185 } else { 3186 // this sounds odd for an extend but is ok for recursive use 3187 destination = source; 3188 } 3189 } else { 3190 for (var property in source) { 3191 destination[property] = source[property]; 3192 } 3193 } 3194 return destination; 3195 } 3196 3197 /** 3198 * Creates an empty object and copies all enumerable properties of another object to it 3199 * This method is mostly for internal use, and not intended for duplicating shapes in canvas. 3200 * @memberOf fabric.util.object 3201 * @param {Object} object Object to clone 3202 * @param {Boolean} [deep] Whether to clone nested objects 3203 * @return {Object} 3204 */ 3205 3206 //TODO: this function return an empty object if you try to clone null 3207 function clone(object, deep) { 3208 return extend({}, object, deep); 3209 } 3210 3211 /** @namespace fabric.util.object */ 3212 fabric.util.object = { 3213 extend: extend, 3214 clone: clone, 3215 }; 3216 fabric.util.object.extend(fabric.util, fabric.Observable); 3217})(); 3218 3219(function () { 3220 /** 3221 * Camelizes a string 3222 * @memberOf fabric.util.string 3223 * @param {String} string String to camelize 3224 * @return {String} Camelized version of a string 3225 */ 3226 function camelize(string) { 3227 return string.replace(/-+(.)?/g, function (match, character) { 3228 return character ? character.toUpperCase() : ""; 3229 }); 3230 } 3231 3232 /** 3233 * Capitalizes a string 3234 * @memberOf fabric.util.string 3235 * @param {String} string String to capitalize 3236 * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized 3237 * and other letters stay untouched, if false first letter is capitalized 3238 * and other letters are converted to lowercase. 3239 * @return {String} Capitalized version of a string 3240 */ 3241 function capitalize(string, firstLetterOnly) { 3242 return ( 3243 string.charAt(0).toUpperCase() + 3244 (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()) 3245 ); 3246 } 3247 3248 /** 3249 * Escapes XML in a string 3250 * @memberOf fabric.util.string 3251 * @param {String} string String to escape 3252 * @return {String} Escaped version of a string 3253 */ 3254 function escapeXml(string) { 3255 return string 3256 .replace(/&/g, "&") 3257 .replace(/"/g, """) 3258 .replace(/'/g, "'") 3259 .replace(/</g, "<") 3260 .replace(/>/g, ">"); 3261 } 3262 3263 /** 3264 * Divide a string in the user perceived single units 3265 * @memberOf fabric.util.string 3266 * @param {String}
3266 textstring String to escape 3267 * @return {Array} array containing the graphemes 3268 */ 3269 function graphemeSplit(textstring) { 3270 var i = 0, 3271 chr, 3272 graphemes = []; 3273 for (i = 0, chr; i < textstring.length; i++) { 3274 if ((chr = getWholeChar(textstring, i)) === false) { 3275 continue; 3276 } 3277 graphemes.push(chr); 3278 } 3279 return graphemes; 3280 } 3281 3282 // taken from mdn in the charAt doc page. 3283 function getWholeChar(str, i) { 3284 var code = str.charCodeAt(i); 3285 3286 if (isNaN(code)) { 3287 return ""; // Position not found 3288 } 3289 if (code < 0xd800 || code > 0xdfff) { 3290 return str.charAt(i); 3291 } 3292 3293 // High surrogate (could change last hex to 0xDB7F to treat high private 3294 // surrogates as single characters) 3295 if (0xd800 <= code && code <= 0xdbff) { 3296 if (str.length <= i + 1) { 3297 throw "High surrogate without following low surrogate"; 3298 } 3299 var next = str.charCodeAt(i + 1); 3300 if (0xdc00 > next || next > 0xdfff) { 3301 throw "High surrogate without following low surrogate"; 3302 } 3303 return str.charAt(i) + str.charAt(i + 1); 3304 } 3305 // Low surrogate (0xDC00 <= code && code <= 0xDFFF) 3306 if (i === 0) { 3307 throw "Low surrogate without preceding high surrogate"; 3308 } 3309 var prev = str.charCodeAt(i - 1); 3310 3311 // (could change last hex to 0xDB7F to treat high private 3312 // surrogates as single characters) 3313 if (0xd800 > prev || prev > 0xdbff) { 3314 throw "Low surrogate without preceding high surrogate"; 3315 } 3316 // We can pass over low surrogates now as the second component 3317 // in a pair which we have already processed 3318 return false; 3319 } 3320 3321 /** 3322 * String utilities 3323 * @namespace fabric.util.string 3324 */ 3325 fabric.util.string = { 3326 camelize: camelize, 3327 capitalize: capitalize, 3328 escapeXml: escapeXml, 3329 graphemeSplit: graphemeSplit, 3330 }; 3331})(); 3332 3333(function () { 3334 var slice = Array.prototype.slice, 3335 emptyFunction = function () {}, 3336 IS_DONTENUM_BUGGY = (function () { 3337 for (var p in { toString: 1 }) { 3338 if (p === "toString") { 3339 return false; 3340 } 3341 } 3342 return true; 3343 })(), 3344 /** @ignore */ 3345 addMethods = function (klass, source, parent) { 3346 for (var property in source) { 3347 if ( 3348 property in klass.prototype && 3349 typeof klass.prototype[property] === "function" && 3350 (source[property] + "").indexOf("callSuper") > -1 3351 ) { 3352 klass.prototype[property] = (function (property) { 3353 return function () { 3354 var superclass = this.constructor.superclass; 3355 this.constructor.superclass = parent; 3356 var returnValue = source[property].apply(this, arguments); 3357 this.constructor.superclass = superclass; 3358 3359 if (property !== "initialize") { 3360 return returnValue; 3361 } 3362 }; 3363 })(property); 3364 } else { 3365 klass.prototype[property] = source[property]; 3366 } 3367 3368 if (IS_DONTENUM_BUGGY) { 3369 if (source.toString !== Object.prototype.toString) { 3370 klass.prototype.toString = source.toString; 3371 } 3372 if (source.valueOf !== Object.prototype.valueOf) { 3373 klass.prototype.valueOf = source.valueOf; 3374 } 3375 } 3376 } 3377 }; 3378 3379 function Subclass() {} 3380 3381 function callSuper(methodName) { 3382 var parentMethod = null, 3383 _this = this; 3384 3385 // climb prototype chain to find method not equal to callee's method 3386 while (_this.constructor.superclass) { 3387 var superClassMethod = _this.constructor.superclass.prototype[methodName]; 3388 if (_this[methodName] !== superClassMethod) { 3389 parentMethod = superClassMethod; 3390 break; 3391 } 3392 // eslint-disable-next-line 3393 _this = _this.constructor.superclass.prototype; 3394 } 3395 3396 if (!parentMethod) { 3397 return console.log( 3398 "tried to callSuper " + 3399 methodName + 3400 ", method not found in prototype chain", 3401 this, 3402 ); 3403 } 3404 3405 return arguments.length > 1 3406 ? parentMethod.apply(this, slice.call(arguments, 1)) 3407 : parentMethod.call(this); 3408 } 3409 3410 /** 3411 * Helper for creation of "classes". 3412 * @memberOf fabric.util 3413 * @param {Function} [parent] optional "Class" to inherit from 3414 * @param {Object} [properties] Properties shared by all instances of this class 3415 * (be careful modifying objects defined here as this would affect all instances) 3416 */ 3417 function createClass() { 3418 var parent = null, 3419 properties = slice.call(arguments, 0); 3420 3421 if (typeof properties[0] === "function") { 3422 parent = properties.shift(); 3423 } 3424 function klass() { 3425 this.initialize.apply(this, arguments); 3426 } 3427 3428 klass.superclass = parent; 3429 klass.subclasses = []; 3430 3431 if (parent) { 3432 Subclass.prototype = parent.prototype; 3433 klass.prototype = new Subclass(); 3434 parent.subclasses.push(klass); 3435 } 3436 for (var i = 0, length = properties.length; i < length; i++) { 3437 addMethods(klass, properties[i], parent); 3438 } 3439 if (!klass.prototype.initialize) { 3440 klass.prototype.initialize = emptyFunction; 3441 } 3442 klass.prototype.constructor = klass; 3443 klass.prototype.callSuper = callSuper; 3444 return klass; 3445 } 3446 3447 fabric.util.createClass = createClass; 3448})(); 3449 3450(function () { 3451 // since ie11 can use addEventListener but they do not support options, i need to check 3452 var couldUseAttachEvent = !!fabric.document.createElement("div").attachEvent, 3453 touchEvents = ["touchstart", "touchmove", "touchend"]; 3454 /** 3455 * Adds an event listener to an element 3456 * @function 3457 * @memberOf fabric.util 3458 * @param {HTMLElement} element 3459 * @param {String} eventName 3460 * @param {Function} handler 3461 */ 3462 fabric.util.addListener = function (element, eventName, handler, options) { 3463 element && 3464 element.addEventListener( 3465 eventName, 3466 handler, 3467 couldUseAttachEvent ? false : options, 3468 ); 3469 }; 3470 3471 /** 3472 * Removes an event listener from an element 3473 * @function 3474 * @memberOf fabric.util 3475 * @param {HTMLElement} element 3476 * @param {String} eventName 3477 * @param {Function} handler 3478 */ 3479 fabric.util.removeListener = function (element, eventName, handler, options) { 3480 element && 3481 element.removeEventListener( 3482 eventName, 3483 handler, 3484 couldUseAttachEvent ? false : options, 3485 ); 3486 }; 3487 3488 function getTouchInfo(event) { 3489 var touchProp = event.changedTouches; 3490 if (touchProp && touchProp[0]) { 3491 return touchProp[0]; 3492 } 3493 return event; 3494 } 3495 3496 fabric.util.getPointer = function (event) { 3497 var element = event.target, 3498 scroll = fabric.util.getScrollLeftTop(element), 3499 _evt = getTouchInfo(event); 3500 return { 3501 x: _evt.clientX + scroll.left, 3502 y: _evt.clientY + scroll.top, 3503 }; 3504 }; 3505 3506 fabric.util.isTouchEvent = function (event) { 3507 return ( 3508 touchEvents.indexOf(event.type) > -1 || event.pointerType === "touch" 3509 ); 3510 }; 3511})(); 3512 3513(function () { 3514 /** 3515 * Cross-browser wrapper for setting element's style 3516 * @memberOf fabric.util 3517 * @param {HTMLElement} element 3518 * @param {Object} styles 3519 * @return {HTMLElement} Element that was passed as a first argument 3520 */ 3521 function setStyle(element, styles) { 3522 var elementStyle = element.style; 3523 if (!elementStyle) { 3524 return element; 3525 } 3526 if (typeof styles === "string") { 3527 element.style.cssText += ";" + styles; 3528 return styles.indexOf("opacity") > -1 3529 ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) 3530 : element; 3531 } 3532 for (var property in styles) { 3533 if (property === "opacity") { 3534 setOpacity(element, styles[property]); 3535 } else { 3536 var normalizedProperty = 3537 property === "float" || property === "cssFloat" 3538 ? typeof elementStyle.styleFloat === "undefined" 3539 ? "cssFloat" 3540 : "styleFloat" 3541 : property; 3542 elementStyle.setProperty(normalizedProperty, styles[property]); 3543 } 3544 } 3545 return element; 3546 } 3547 3548 var parseEl = fabric.document.createElement("div"), 3549 supportsOpacity = typeof parseEl.style.opacity === "string", 3550 supportsFilters = typeof parseEl.style.filter === "string", 3551 reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, 3552 /** @ignore */ 3553 setOpacity = function (element) { 3554 return element; 3555 }; 3556 3557 if (supportsOpacity) { 3558 /** @ignore */ 3559 setOpacity = function (element, value) { 3560 element.style.opacity = value; 3561 return element; 3562 }; 3563 } else if (supportsFilters) { 3564 /** @ignore */ 3565 setOpacity = function (element, value) { 3566 var es = element.style; 3567 if (element.currentStyle && !element.currentStyle.hasLayout) { 3568 es.zoom = 1; 3569 } 3570 if (reOpacity.test(es.filter)) { 3571 value = value >= 0.9999 ? "" : "alpha(opacity=" + value * 100 + ")"; 3572 es.filter = es.filter.replace(reOpacity, value); 3573 } else { 3574 es.filter += " alpha(opacity=" + value * 100 + ")"; 3575 } 3576 return element; 3577 }; 3578 } 3579 3580 fabric.util.setStyle = setStyle; 3581})(); 3582 3583(function () { 3584 var _slice = Array.prototype.slice; 3585 3586 /** 3587 * Takes id and returns an element with that id (if one exists in a document) 3588 * @memberOf fabric.util 3589 * @param {String|HTMLElement}
vendor: 8,881 bytes, lines 3589-3886
3589 id 3590 * @return {HTMLElement|null} 3591 */ 3592 function getById(id) { 3593 return typeof id === "string" ? fabric.document.getElementById(id) : id; 3594 } 3595 3596 var sliceCanConvertNodelists, 3597 /** 3598 * Converts an array-like object (e.g. arguments or NodeList) to an array 3599 * @memberOf fabric.util 3600 * @param {Object} arrayLike 3601 * @return {Array} 3602 */ 3603 toArray = function (arrayLike) { 3604 return _slice.call(arrayLike, 0); 3605 }; 3606 3607 try { 3608 sliceCanConvertNodelists = 3609 toArray(fabric.document.childNodes) instanceof Array; 3610 } catch (err) {} 3611 3612 if (!sliceCanConvertNodelists) { 3613 toArray = function (arrayLike) { 3614 var arr = new Array(arrayLike.length), 3615 i = arrayLike.length; 3616 while (i--) { 3617 arr[i] = arrayLike[i]; 3618 } 3619 return arr; 3620 }; 3621 } 3622 3623 /** 3624 * Creates specified element with specified attributes 3625 * @memberOf fabric.util 3626 * @param {String} tagName Type of an element to create 3627 * @param {Object} [attributes] Attributes to set on an element 3628 * @return {HTMLElement} Newly created element 3629 */ 3630 function makeElement(tagName, attributes) { 3631 var el = fabric.document.createElement(tagName); 3632 for (var prop in attributes) { 3633 if (prop === "class") { 3634 el.className = attributes[prop]; 3635 } else if (prop === "for") { 3636 el.htmlFor = attributes[prop]; 3637 } else { 3638 el.setAttribute(prop, attributes[prop]); 3639 } 3640 } 3641 return el; 3642 } 3643 3644 /** 3645 * Adds class to an element 3646 * @memberOf fabric.util 3647 * @param {HTMLElement} element Element to add class to 3648 * @param {String} className Class to add to an element 3649 */ 3650 function addClass(element, className) { 3651 if ( 3652 element && 3653 (" " + element.className + " ").indexOf(" " + className + " ") === -1 3654 ) { 3655 element.className += (element.className ? " " : "") + className; 3656 } 3657 } 3658 3659 /** 3660 * Wraps element with another element 3661 * @memberOf fabric.util 3662 * @param {HTMLElement} element Element to wrap 3663 * @param {HTMLElement|String} wrapper Element to wrap with 3664 * @param {Object} [attributes] Attributes to set on a wrapper 3665 * @return {HTMLElement} wrapper 3666 */ 3667 function wrapElement(element, wrapper, attributes) { 3668 if (typeof wrapper === "string") { 3669 wrapper = makeElement(wrapper, attributes); 3670 } 3671 if (element.parentNode) { 3672 element.parentNode.replaceChild(wrapper, element); 3673 } 3674 wrapper.appendChild(element); 3675 return wrapper; 3676 } 3677 3678 /** 3679 * Returns element scroll offsets 3680 * @memberOf fabric.util 3681 * @param {HTMLElement} element Element to operate on 3682 * @return {Object} Object with left/top values 3683 */ 3684 function getScrollLeftTop(element) { 3685 var left = 0, 3686 top = 0, 3687 docElement = fabric.document.documentElement, 3688 body = fabric.document.body || { 3689 scrollLeft: 0, 3690 scrollTop: 0, 3691 }; 3692 3693 // While loop checks (and then sets element to) .parentNode OR .host 3694 // to account for ShadowDOM. We still want to traverse up out of ShadowDOM, 3695 // but the .parentNode of a root ShadowDOM node will always be null, instead 3696 // it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938 3697 while (element && (element.parentNode || element.host)) { 3698 // Set element to element parent, or 'host' in case of ShadowDOM 3699 element = element.parentNode || element.host; 3700 3701 if (element === fabric.document) { 3702 left = body.scrollLeft || docElement.scrollLeft || 0; 3703 top = body.scrollTop || docElement.scrollTop || 0; 3704 } else { 3705 left += element.scrollLeft || 0; 3706 top += element.scrollTop || 0; 3707 } 3708 3709 if (element.nodeType === 1 && element.style.position === "fixed") { 3710 break; 3711 } 3712 } 3713 3714 return { left: left, top: top }; 3715 } 3716 3717 /** 3718 * Returns offset for a given element 3719 * @function 3720 * @memberOf fabric.util 3721 * @param {HTMLElement} element Element to get offset for 3722 * @return {Object} Object with "left" and "top" properties 3723 */ 3724 function getElementOffset(element) { 3725 var docElem, 3726 doc = element && element.ownerDocument, 3727 box = { left: 0, top: 0 }, 3728 offset = { left: 0, top: 0 }, 3729 scrollLeftTop, 3730 offsetAttributes = { 3731 borderLeftWidth: "left", 3732 borderTopWidth: "top", 3733 paddingLeft: "left", 3734 paddingTop: "top", 3735 }; 3736 3737 if (!doc) { 3738 return offset; 3739 } 3740 3741 for (var attr in offsetAttributes) { 3742 offset[offsetAttributes[attr]] += 3743 parseInt(getElementStyle(element, attr), 10) || 0; 3744 } 3745 3746 docElem = doc.documentElement; 3747 if (typeof element.getBoundingClientRect !== "undefined") { 3748 box = element.getBoundingClientRect(); 3749 } 3750 3751 scrollLeftTop = getScrollLeftTop(element); 3752 3753 return { 3754 left: 3755 box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, 3756 top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top, 3757 }; 3758 } 3759 3760 /** 3761 * Returns style attribute value of a given element 3762 * @memberOf fabric.util 3763 * @param {HTMLElement} element Element to get style attribute for 3764 * @param {String} attr Style attribute to get for element 3765 * @return {String} Style attribute value of the given element. 3766 */ 3767 var getElementStyle; 3768 if ( 3769 fabric.document.defaultView && 3770 fabric.document.defaultView.getComputedStyle 3771 ) { 3772 getElementStyle = function (element, attr) { 3773 var style = fabric.document.defaultView.getComputedStyle(element, null); 3774 return style ? style[attr] : undefined; 3775 }; 3776 } else { 3777 getElementStyle = function (element, attr) { 3778 var value = element.style[attr]; 3779 if (!value && element.currentStyle) { 3780 value = element.currentStyle[attr]; 3781 } 3782 return value; 3783 }; 3784 } 3785 3786 (function () { 3787 var style = fabric.document.documentElement.style, 3788 selectProp = 3789 "userSelect" in style 3790 ? "userSelect" 3791 : "MozUserSelect" in style 3792 ? "MozUserSelect" 3793 : "WebkitUserSelect" in style 3794 ? "WebkitUserSelect" 3795 : "KhtmlUserSelect" in style 3796 ? "KhtmlUserSelect" 3797 : ""; 3798 3799 /** 3800 * Makes element unselectable 3801 * @memberOf fabric.util 3802 * @param {HTMLElement} element Element to make unselectable 3803 * @return {HTMLElement} Element that was passed in 3804 */ 3805 function makeElementUnselectable(element) { 3806 if (typeof element.onselectstart !== "undefined") { 3807 element.onselectstart = fabric.util.falseFunction; 3808 } 3809 if (selectProp) { 3810 element.style[selectProp] = "none"; 3811 } else if (typeof element.unselectable === "string") { 3812 element.unselectable = "on"; 3813 } 3814 return element; 3815 } 3816 3817 /** 3818 * Makes element selectable 3819 * @memberOf fabric.util 3820 * @param {HTMLElement} element Element to make selectable 3821 * @return {HTMLElement} Element that was passed in 3822 */ 3823 function makeElementSelectable(element) { 3824 if (typeof element.onselectstart !== "undefined") { 3825 element.onselectstart = null; 3826 } 3827 if (selectProp) { 3828 element.style[selectProp] = ""; 3829 } else if (typeof element.unselectable === "string") { 3830 element.unselectable = ""; 3831 } 3832 return element; 3833 } 3834 3835 fabric.util.makeElementUnselectable = makeElementUnselectable; 3836 fabric.util.makeElementSelectable = makeElementSelectable; 3837 })(); 3838 3839 function getNodeCanvas(element) { 3840 var impl = fabric.jsdomImplForWrapper(element); 3841 return impl._canvas || impl._image; 3842 } 3843 3844 function cleanUpJsdomNode(element) { 3845 if (!fabric.isLikelyNode) { 3846 return; 3847 } 3848 var impl = fabric.jsdomImplForWrapper(element); 3849 if (impl) { 3850 impl._image = null; 3851 impl._canvas = null; 3852 // unsure if necessary 3853 impl._currentSrc = null; 3854 impl._attributes = null; 3855 impl._classList = null; 3856 } 3857 } 3858 3859 function setImageSmoothing(ctx, value) { 3860 ctx.imageSmoothingEnabled = 3861 ctx.imageSmoothingEnabled || 3862 ctx.webkitImageSmoothingEnabled || 3863 ctx.mozImageSmoothingEnabled || 3864 ctx.msImageSmoothingEnabled || 3865 ctx.oImageSmoothingEnabled; 3866 ctx.imageSmoothingEnabled = value; 3867 } 3868 3869 /** 3870 * setImageSmoothing sets the context imageSmoothingEnabled property. 3871 * Used by canvas and by ImageObject. 3872 * @memberOf fabric.util 3873 * @since 4.0.0 3874 * @param {HTMLRenderingContext2D} ctx to set on 3875 * @param {Boolean} value true or false 3876 */ 3877 fabric.util.setImageSmoothing = setImageSmoothing; 3878 fabric.util.getById = getById; 3879 fabric.util.toArray = toArray; 3880 fabric.util.addClass = addClass; 3881 fabric.util.makeElement = makeElement; 3882 fabric.util.wrapElement = wrapElement; 3883 fabric.util.getScrollLeftTop = getScrollLeftTop; 3884 fabric.util.getElementOffset = getElementOffset; 3885 fabric.util.getNodeCanvas = getNodeCanvas; 3886 fabric.util.cleanUpJsdomNode = cleanUpJsdomNode;
3887})(); 3888 3889(function () { 3890 function addParamToUrl(url, param) { 3891 return url + (/\?/.test(url) ? "&" : "?") + param; 3892 } 3893 3894 function emptyFn() {} 3895 3896 /** 3897 * Cross-browser abstraction for sending XMLHttpRequest 3898 * @memberOf fabric.util 3899 * @param {String} url URL to send XMLHttpRequest to 3900 * @param {Object} [options] Options object 3901 * @param {String} [options.method="GET"] 3902 * @param {String} [options.parameters] parameters to append to url in GET or in body 3903 * @param {String} [options.body] body to send with POST or PUT request 3904 * @param {Function} options.onComplete Callback to invoke when request is completed 3905 * @return {XMLHttpRequest} request 3906 */ 3907 function request(url, options) { 3908 options || (options = {}); 3909 var method = options.method ? options.method.toUpperCase() : "GET", 3910 onComplete = options.onComplete || function () {}, 3911 xhr = new fabric.window.XMLHttpRequest(), 3912 body = options.body || options.parameters; 3913 3914 /** @ignore */ 3915 xhr.onreadystatechange = function () { 3916 if (xhr.readyState === 4) { 3917 onComplete(xhr); 3918 xhr.onreadystatechange = emptyFn; 3919 } 3920 }; 3921 3922 if (method === "GET") { 3923 body = null; 3924 if (typeof options.parameters === "string") { 3925 url = addParamToUrl(url, options.parameters); 3926 } 3927 } 3928 3929 xhr.open(method, url, true); 3930 3931 if (method === "POST" || method === "PUT") { 3932 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 3933 } 3934 3935 xhr.send(body); 3936 return xhr; 3937 } 3938 3939 fabric.util.request = request; 3940})(); 3941 3942/** 3943 * Wrapper around `console.log` (when available) 3944 * @param {*} [values] Values to log 3945 */ 3946fabric.log = console.log; 3947 3948/** 3949 * Wrapper around `console.warn` (when available) 3950 * @param {*} [values] Values to log as a warning 3951 */ 3952fabric.warn = console.warn; 3953 3954(function () { 3955 var extend = fabric.util.object.extend, 3956 clone = fabric.util.object.clone; 3957 3958 /** 3959 * @typedef {Object} AnimationOptions 3960 * Animation of a value or list of values. 3961 * When using lists, think of something like this: 3962 * fabric.util.animate({ 3963 * startValue: [1, 2, 3], 3964 * endValue: [2, 4, 6], 3965 * onChange: function([a, b, c]) { 3966 * canvas.zoomToPoint({x: b, y: c}, a) 3967 * canvas.renderAll() 3968 * } 3969 * }); 3970 * @example 3971 * @property {Function} [onChange] Callback; invoked on every value change 3972 * @property {Function} [onComplete] Callback; invoked when value change is completed 3973 * @example 3974 * // Note: startValue, endValue, and byValue must match the type 3975 * var animationOptions = { startValue: 0, endValue: 1, byValue: 0.25 } 3976 * var animationOptions = { startValue: [0, 1], endValue: [1, 2], byValue: [0.25, 0.25] } 3977 * @property {number | number[]} [startValue=0] Starting value 3978 * @property {number | number[]} [endValue=100] Ending value 3979 * @property {number | number[]} [byValue=100] Value to modify the property by 3980 * @property {Function} [easing] Easing function 3981 * @property {Number} [duration=500] Duration of change (in ms) 3982 * @property {Function} [abort] Additional function with logic. If returns true, animation aborts. 3983 * 3984 * @typedef {() => void} CancelFunction 3985 * 3986 * @typedef {Object} AnimationCurrentState 3987 * @property {number | number[]} currentValue value in range [`startValue`, `endValue`] 3988 * @property {number} completionRate value in range [0, 1] 3989 * @property {number} durationRate value in range [0, 1] 3990 * 3991 * @typedef {(AnimationOptions & AnimationCurrentState & { cancel: CancelFunction }} AnimationContext 3992 */ 3993 3994 /** 3995 * Array holding all running animations 3996 * @memberof fabric 3997 * @type {AnimationContext[]} 3998 */ 3999 var RUNNING_ANIMATIONS = []; 4000 fabric.util.object.extend(RUNNING_ANIMATIONS, { 4001 /** 4002 * cancel all running animations at the next requestAnimFrame 4003 * @returns {AnimationContext[]} 4004 */ 4005 cancelAll: function () { 4006 var animations = this.splice(0); 4007 animations.forEach(function (animation) { 4008 animation.cancel(); 4009 }); 4010 return animations; 4011 }, 4012 4013 /** 4014 * cancel all running animations attached to canvas at the next requestAnimFrame 4015 * @param {fabric.Canvas} canvas 4016 * @returns {AnimationContext[]} 4017 */ 4018 cancelByCanvas: function (canvas) { 4019 if (!canvas) { 4020 return []; 4021 } 4022 var cancelled = this.filter(function (animation) { 4023 return ( 4024 typeof animation.target === "object" && 4025 animation.target.canvas === canvas 4026 ); 4027 }); 4028 cancelled.forEach(function (animation) { 4029 animation.cancel(); 4030 }); 4031 return cancelled; 4032 }, 4033 4034 /** 4035 * cancel all running animations for target at the next requestAnimFrame 4036 * @param {*} target 4037 * @returns {AnimationContext[]} 4038 */ 4039 cancelByTarget: function (target) { 4040 var cancelled = this.findAnimationsByTarget(target); 4041 cancelled.forEach(function (animation) { 4042 animation.cancel(); 4043 }); 4044 return cancelled; 4045 }, 4046 4047 /** 4048 * 4049 * @param {CancelFunction} cancelFunc the function returned by animate 4050 * @returns {number} 4051 */ 4052 findAnimationIndex: function (cancelFunc) { 4053 return this.indexOf(this.findAnimation(cancelFunc)); 4054 }, 4055 4056 /** 4057 * 4058 * @param {CancelFunction} cancelFunc the function returned by animate 4059 * @returns {AnimationContext | undefined} animation's options object 4060 */ 4061 findAnimation: function (cancelFunc) { 4062 return this.find(function (animation) { 4063 return animation.cancel === cancelFunc; 4064 }); 4065 }, 4066 4067 /** 4068 * 4069 * @param {*} target the object that is assigned to the target property of the animation context 4070 * @returns {AnimationContext[]} array of animation options object associated with target 4071 */ 4072 findAnimationsByTarget: function (target) { 4073 if (!target) { 4074 return []; 4075 } 4076 return this.filter(function (animation) { 4077 return animation.target === target; 4078 }); 4079 }, 4080 }); 4081 4082 function noop() { 4083 return false;
4084 } 4085 4086 function defaultEasing(t, b, c, d) { 4087 return -c * Math.cos((t / d) * (Math.PI / 2)) + c + b; 4088 } 4089 4090 /** 4091 * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. 4092 * @memberOf fabric.util 4093 * @param {AnimationOptions} [options] Animation options 4094 * @example 4095 * // Note: startValue, endValue, and byValue must match the type 4096 * fabric.util.animate({ startValue: 0, endValue: 1, byValue: 0.25 }) 4097 * fabric.util.animate({ startValue: [0, 1], endValue: [1, 2], byValue: [0.25, 0.25] }) 4098 * @returns {CancelFunction} cancel function 4099 */ 4100 function animate(options) { 4101 options || (options = {}); 4102 var cancel = false, 4103 context, 4104 removeFromRegistry = function () { 4105 var index = fabric.runningAnimations.indexOf(context); 4106 return index > -1 && fabric.runningAnimations.splice(index, 1)[0]; 4107 }; 4108 4109 context = extend(clone(options), { 4110 cancel: function () { 4111 cancel = true; 4112 return removeFromRegistry(); 4113 }, 4114 currentValue: "startValue" in options ? options.startValue : 0, 4115 completionRate: 0, 4116 durationRate: 0, 4117 }); 4118 fabric.runningAnimations.push(context); 4119 4120 requestAnimFrame(function (timestamp) { 4121 var start = timestamp || +new Date(), 4122 duration = options.duration || 500, 4123 finish = start + duration, 4124 time, 4125 onChange = options.onChange || noop, 4126 abort = options.abort || noop, 4127 onComplete = options.onComplete || noop, 4128 easing = options.easing || defaultEasing, 4129 isMany = 4130 "startValue" in options ? options.startValue.length > 0 : false, 4131 startValue = "startValue" in options ? options.startValue : 0, 4132 endValue = "endValue" in options ? options.endValue : 100, 4133 byValue = 4134 options.byValue || 4135 (isMany 4136 ? startValue.map(function (value, i) { 4137 return endValue[i] - startValue[i]; 4138 }) 4139 : endValue - startValue); 4140 4141 options.onStart && options.onStart(); 4142 4143 (function tick(ticktime) { 4144 time = ticktime || +new Date(); 4145 var currentTime = time > finish ? duration : time - start, 4146 timePerc = currentTime / duration, 4147 current = isMany 4148 ? startValue.map(function (_value, i) { 4149 return easing(currentTime, startValue[i], byValue[i], duration); 4150 }) 4151 : easing(currentTime, startValue, byValue, duration), 4152 valuePerc = isMany 4153 ? Math.abs((current[0] - startValue[0]) / byValue[0]) 4154 : Math.abs((current - startValue) / byValue); 4155 // update context 4156 context.currentValue = isMany ? current.slice() : current; 4157 context.completionRate = valuePerc; 4158 context.durationRate = timePerc; 4159 if (cancel) { 4160 return; 4161 } 4162 if (abort(current, valuePerc, timePerc)) { 4163 removeFromRegistry(); 4164 return; 4165 } 4166 if (time > finish) { 4167 // update context 4168 context.currentValue = isMany ? endValue.slice() : endValue; 4169 context.completionRate = 1; 4170 context.durationRate = 1; 4171 // execute callbacks 4172 onChange(isMany ? endValue.slice() : endValue, 1, 1); 4173 onComplete(endValue, 1, 1); 4174 removeFromRegistry(); 4175 return; 4176 } else { 4177 onChange(current, valuePerc, timePerc); 4178 requestAnimFrame(tick); 4179 } 4180 })(start); 4181 }); 4182 4183 return context.cancel; 4184 } 4185 4186 var _requestAnimFrame = 4187 fabric.window.requestAnimationFrame || 4188 fabric.window.webkitRequestAnimationFrame || 4189 fabric.window.mozRequestAnimationFrame || 4190 fabric.window.oRequestAnimationFrame || 4191 fabric.window.msRequestAnimationFrame || 4192 function (callback) { 4193 return fabric.window.setTimeout(callback, 1000 / 60); 4194 }; 4195 4196 var _cancelAnimFrame = 4197 fabric.window.cancelAnimationFrame || fabric.window.clearTimeout; 4198 4199 /** 4200 * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 4201 * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method 4202 * @memberOf fabric.util 4203 * @param {Function} callback Callback to invoke 4204 * @param {DOMElement} element optional Element to associate with animation 4205 */
4206 function requestAnimFrame() { 4207 return _requestAnimFrame.apply(fabric.window, arguments); 4208 } 4209 4210 function cancelAnimFrame() { 4211 return _cancelAnimFrame.apply(fabric.window, arguments); 4212 } 4213 4214 fabric.util.animate = animate; 4215 fabric.util.requestAnimFrame = requestAnimFrame; 4216 fabric.util.cancelAnimFrame = cancelAnimFrame; 4217 fabric.runningAnimations = RUNNING_ANIMATIONS; 4218})(); 4219 4220(function () { 4221 // Calculate an in-between color. Returns a "rgba()" string. 4222 // Credit: Edwin Martin <[email protected]> 4223 // http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js 4224 function calculateColor(begin, end, pos) { 4225 var color = 4226 "rgba(" + 4227 parseInt(begin[0] + pos * (end[0] - begin[0]), 10) + 4228 "," + 4229 parseInt(begin[1] + pos * (end[1] - begin[1]), 10) + 4230 "," + 4231 parseInt(begin[2] + pos * (end[2] - begin[2]), 10); 4232 4233 color += 4234 "," + 4235 (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); 4236 color += ")"; 4237 return color; 4238 } 4239 4240 /** 4241 * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed. 4242 * @memberOf fabric.util 4243 * @param {String} fromColor The starting color in hex or rgb(a) format. 4244 * @param {String} toColor The starting color in hex or rgb(a) format. 4245 * @param {Number} [duration] Duration of change (in ms). 4246 * @param {Object} [options] Animation options 4247 * @param {Function} [options.onChange] Callback; invoked on every value change 4248 * @param {Function} [options.onComplete] Callback; invoked when value change is completed 4249 * @param {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used. 4250 * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called. 4251 * @returns {Function} abort function 4252 */ 4253 function animateColor(fromColor, toColor, duration, options) { 4254 var startColor = new fabric.Color(fromColor).getSource(), 4255 endColor = new fabric.Color(toColor).getSource(), 4256 originalOnComplete = options.onComplete, 4257 originalOnChange = options.onChange; 4258 options = options || {}; 4259 4260 return fabric.util.animate( 4261 fabric.util.object.extend(options, { 4262 duration: duration || 500, 4263 startValue: startColor, 4264 endValue: endColor, 4265 byValue: endColor, 4266 easing: function (currentTime, startValue, byValue, duration) { 4267 var posValue = options.colorEasing 4268 ? options.colorEasing(currentTime, duration) 4269 : 1 - Math.cos((currentTime / duration) * (Math.PI / 2)); 4270 return calculateColor(startValue, byValue, posValue); 4271 }, 4272 // has to take in account for color restoring; 4273 onComplete: function (current, valuePerc, timePerc) { 4274 if (originalOnComplete) { 4275 return originalOnComplete( 4276 calculateColor(endColor, endColor, 0), 4277 valuePerc, 4278 timePerc, 4279 ); 4280 } 4281 }, 4282 onChange: function (current, valuePerc, timePerc) { 4283 if (originalOnChange) { 4284 if (Array.isArray(current)) { 4285 return originalOnChange( 4286 calculateColor(current, current, 0), 4287 valuePerc, 4288 timePerc, 4289 ); 4290 } 4291 originalOnChange(current, valuePerc, timePerc); 4292 } 4293 }, 4294 }), 4295 ); 4296 } 4297 4298 fabric.util.animateColor = animateColor; 4299})(); 4300 4301(function () { 4302 function normalize(a, c, p, s) { 4303 if (a < Math.abs(c)) { 4304 a = c; 4305 s = p / 4; 4306 } else { 4307 //handle the 0/0 case: 4308 if (c === 0 && a === 0) { 4309 s = (p / (2 * Math.PI)) * Math.asin(1); 4310 } else { 4311 s = (p / (2 * Math.PI)) * Math.asin(c / a); 4312 } 4313 } 4314 return { a: a, c: c, p: p, s: s }; 4315 } 4316 4317 function elastic(opts, t, d) { 4318 return ( 4319 opts.a * 4320 Math.pow(2, 10 * (t -= 1)) * 4321 Math.sin(((t * d - opts.s) * (2 * Math.PI)) / opts.p) 4322 ); 4323 } 4324 4325 /** 4326 * Cubic easing out 4327 * @memberOf fabric.util.ease 4328 */ 4329 function easeOutCubic(t, b, c, d) { 4330 return c * ((t = t / d - 1) * t * t + 1) + b; 4331 } 4332 4333 /** 4334 * Cubic easing in and out 4335 * @memberOf fabric.util.ease 4336 */ 4337 function easeInOutCubic(t, b, c, d) { 4338 t /= d / 2; 4339 if (t < 1) { 4340 return (c / 2) * t * t * t + b; 4341 } 4342 return (c / 2) * ((t -= 2) * t * t + 2) + b; 4343 } 4344 4345 /** 4346 * Quartic easing in 4347 * @memberOf fabric.util.ease 4348 */ 4349 function easeInQuart(t, b, c, d) { 4350 return c * (t /= d) * t * t * t + b; 4351 } 4352 4353 /** 4354 * Quartic easing out 4355 * @memberOf fabric.util.ease 4356 */ 4357 function easeOutQuart(t, b, c, d) { 4358 return -c * ((t = t / d - 1) * t * t * t - 1) + b; 4359 } 4360 4361 /** 4362 * Quartic easing in and out 4363 * @memberOf fabric.util.ease 4364 */ 4365 function easeInOutQuart(t, b, c, d) { 4366 t /= d / 2; 4367 if (t < 1) { 4368 return (c / 2) * t * t * t * t + b; 4369 } 4370 return (-c / 2) * ((t -= 2) * t * t * t - 2) + b; 4371 } 4372 4373 /** 4374 * Quintic easing in 4375 * @memberOf fabric.util.ease 4376 */ 4377 function easeInQuint(t, b, c, d) { 4378 return c * (t /= d) * t * t * t * t + b; 4379 } 4380 4381 /** 4382 * Quintic easing out 4383 * @memberOf fabric.util.ease 4384 */ 4385 function easeOutQuint(t, b, c, d) { 4386 return c * ((t = t / d - 1) * t * t * t * t + 1) + b; 4387 } 4388 4389 /** 4390 * Quintic easing in and out 4391 * @memberOf fabric.util.ease 4392 */ 4393 function easeInOutQuint(t, b, c, d) { 4394 t /= d / 2; 4395 if (t < 1) { 4396 return (c / 2) * t * t * t * t * t + b; 4397 } 4398 return (c / 2) * ((t -= 2) * t * t * t * t + 2) + b; 4399 } 4400 4401 /** 4402 * Sinusoidal easing in 4403 * @memberOf fabric.util.ease 4404 */ 4405 function easeInSine(t, b, c, d) { 4406 return -c * Math.cos((t / d) * (Math.PI / 2)) + c + b; 4407 } 4408 4409 /** 4410 * Sinusoidal easing out 4411 * @memberOf fabric.util.ease 4412 */ 4413 function easeOutSine(t, b, c, d) { 4414 return c * Math.sin((t / d) * (Math.PI / 2)) + b; 4415 } 4416 4417 /** 4418 * Sinusoidal easing in and out 4419 * @memberOf fabric.util.ease 4420 */ 4421 function easeInOutSine(t, b, c, d) { 4422 return (-c / 2) * (Math.cos((Math.PI * t) / d) - 1) + b; 4423 } 4424 4425 /** 4426 * Exponential easing in 4427 * @memberOf fabric.util.ease 4428 */ 4429 function easeInExpo(t, b, c, d) { 4430 return t === 0 ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; 4431 } 4432 4433 /** 4434 * Exponential easing out 4435 * @memberOf fabric.util.ease 4436 */ 4437 function easeOutExpo(t, b, c, d) { 4438 return t === d ? b + c : c * (-Math.pow(2, (-10 * t) / d) + 1) + b; 4439 } 4440 4441 /** 4442 * Exponential easing in and out 4443 * @memberOf fabric.util.ease 4444 */ 4445 function easeInOutExpo(t, b, c, d) { 4446 if (t === 0) { 4447 return b; 4448 } 4449 if (t === d) { 4450 return b + c; 4451 } 4452 t /= d / 2; 4453 if (t < 1) { 4454 return (c / 2) * Math.pow(2, 10 * (t - 1)) + b; 4455 } 4456 return (c / 2) * (-Math.pow(2, -10 * --t) + 2) + b; 4457 } 4458 4459 /** 4460 * Circular easing in 4461 * @memberOf fabric.util.ease 4462 */ 4463 function easeInCirc(t, b, c, d) { 4464 return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; 4465 } 4466 4467 /** 4468 * Circular easing out 4469 * @memberOf fabric.util.ease 4470 */ 4471 function easeOutCirc(t, b, c, d) { 4472 return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; 4473 } 4474 4475 /** 4476 * Circular easing in and out 4477 * @memberOf fabric.util.ease 4478 */ 4479 function easeInOutCirc(t, b, c, d) { 4480 t /= d / 2; 4481 if (t < 1) { 4482 return (-c / 2) * (Math.sqrt(1 - t * t) - 1) + b; 4483 } 4484 return (c / 2) * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; 4485 } 4486 4487 /** 4488 * Elastic easing in 4489 * @memberOf fabric.util.ease 4490 */ 4491 function easeInElastic(t, b, c, d) { 4492 var s = 1.70158, 4493 p = 0, 4494 a = c; 4495 if (t === 0) { 4496 return b; 4497 } 4498 t /= d; 4499 if (t === 1) { 4500 return b + c; 4501 } 4502 if (!p) { 4503 p = d * 0.3; 4504 } 4505 var opts = normalize(a, c, p, s); 4506 return -elastic(opts, t, d) + b; 4507 } 4508 4509 /** 4510 * Elastic easing out 4511 * @memberOf fabric.util.ease 4512 */ 4513 function easeOutElastic(t, b, c, d) { 4514 var s = 1.70158, 4515 p = 0, 4516 a = c; 4517 if (t === 0) { 4518 return b; 4519 } 4520 t /= d; 4521 if (t === 1) { 4522 return b + c; 4523 } 4524 if (!p) { 4525 p = d * 0.3; 4526 } 4527 var opts = normalize(a, c, p, s); 4528 return ( 4529 opts.a * 4530 Math.pow(2, -10 * t) * 4531 Math.sin(((t * d - opts.s) * (2 * Math.PI)) / opts.p) + 4532 opts.c + 4533 b 4534 ); 4535 } 4536 4537 /** 4538 * Elastic easing in and out 4539 * @memberOf fabric.util.ease 4540 */ 4541 function easeInOutElastic(t, b, c, d) { 4542 var s = 1.70158, 4543 p = 0, 4544 a = c; 4545 if (t === 0) { 4546 return b; 4547 } 4548 t /= d / 2; 4549 if (t === 2) { 4550 return b + c; 4551 } 4552 if (!p) { 4553 p = d * (0.3 * 1.5); 4554 } 4555 var opts = normalize(a, c, p, s); 4556 if (t < 1) { 4557 return -0.5 * elastic(opts, t, d) + b; 4558 } 4559 return ( 4560 opts.a * 4561 Math.pow(2, -10 * (t -= 1)) * 4562 Math.sin(((t * d - opts.s) * (2 * Math.PI)) / opts.p) * 4563 0.5 + 4564 opts.c + 4565 b 4566 ); 4567 } 4568 4569 /** 4570 * Backwards easing in 4571 * @memberOf fabric.util.ease 4572 */ 4573 function easeInBack(t, b, c, d, s) { 4574 if (s === undefined) { 4575 s = 1.70158; 4576 } 4577 return c * (t /= d) * t * ((s + 1) * t - s) + b; 4578 } 4579 4580 /** 4581 * Backwards easing out 4582 * @memberOf fabric.util.ease 4583 */ 4584 function easeOutBack(t, b, c, d, s) { 4585 if (s === undefined) { 4586 s = 1.70158; 4587 }
4588 return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; 4589 } 4590 4591 /** 4592 * Backwards easing in and out 4593 * @memberOf fabric.util.ease 4594 */ 4595 function easeInOutBack(t, b, c, d, s) { 4596 if (s === undefined) { 4597 s = 1.70158; 4598 } 4599 t /= d / 2; 4600 if (t < 1) { 4601 return (c / 2) * (t * t * (((s *= 1.525) + 1) * t - s)) + b; 4602 } 4603 return (c / 2) * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b; 4604 } 4605 4606 /** 4607 * Bouncing easing in 4608 * @memberOf fabric.util.ease 4609 */ 4610 function easeInBounce(t, b, c, d) { 4611 return c - easeOutBounce(d - t, 0, c, d) + b; 4612 } 4613 4614 /** 4615 * Bouncing easing out 4616 * @memberOf fabric.util.ease 4617 */ 4618 function easeOutBounce(t, b, c, d) { 4619 if ((t /= d) < 1 / 2.75) { 4620 return c * (7.5625 * t * t) + b; 4621 } else if (t < 2 / 2.75) { 4622 return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b; 4623 } else if (t < 2.5 / 2.75) { 4624 return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b; 4625 } else { 4626 return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b; 4627 } 4628 } 4629 4630 /** 4631 * Bouncing easing in and out 4632 * @memberOf fabric.util.ease 4633 */ 4634 function easeInOutBounce(t, b, c, d) { 4635 if (t < d / 2) { 4636 return easeInBounce(t * 2, 0, c, d) * 0.5 + b; 4637 } 4638 return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; 4639 } 4640 4641 /** 4642 * Easing functions 4643 * See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a> 4644 * @namespace fabric.util.ease 4645 */ 4646 fabric.util.ease = { 4647 /** 4648 * Quadratic easing in 4649 * @memberOf fabric.util.ease 4650 */ 4651 easeInQuad: function (t, b, c, d) { 4652 return c * (t /= d) * t + b; 4653 }, 4654 4655 /** 4656 * Quadratic easing out 4657 * @memberOf fabric.util.ease 4658 */ 4659 easeOutQuad: function (t, b, c, d) { 4660 return -c * (t /= d) * (t - 2) + b; 4661 }, 4662 4663 /** 4664 * Quadratic easing in and out 4665 * @memberOf fabric.util.ease 4666 */ 4667 easeInOutQuad: function (t, b, c, d) { 4668 t /= d / 2; 4669 if (t < 1) { 4670 return (c / 2) * t * t + b; 4671 } 4672 return (-c / 2) * (--t * (t - 2) - 1) + b; 4673 }, 4674 4675 /** 4676 * Cubic easing in 4677 * @memberOf fabric.util.ease 4678 */ 4679 easeInCubic: function (t, b, c, d) { 4680 return c * (t /= d) * t * t + b; 4681 }, 4682 4683 easeOutCubic: easeOutCubic, 4684 easeInOutCubic: easeInOutCubic, 4685 easeInQuart: easeInQuart, 4686 easeOutQuart: easeOutQuart, 4687 easeInOutQuart: easeInOutQuart, 4688 easeInQuint: easeInQuint, 4689 easeOutQuint: easeOutQuint, 4690 easeInOutQuint: easeInOutQuint, 4691 easeInSine: easeInSine, 4692 easeOutSine: easeOutSine, 4693 easeInOutSine: easeInOutSine, 4694 easeInExpo: easeInExpo, 4695 easeOutExpo: easeOutExpo, 4696 easeInOutExpo: easeInOutExpo, 4697 easeInCirc: easeInCirc, 4698 easeOutCirc: easeOutCirc, 4699 easeInOutCirc: easeInOutCirc, 4700 easeInElastic: easeInElastic, 4701 easeOutElastic: easeOutElastic, 4702 easeInOutElastic: easeInOutElastic, 4703 easeInBack: easeInBack, 4704 easeOutBack: easeOutBack, 4705 easeInOutBack: easeInOutBack, 4706 easeInBounce: easeInBounce, 4707 easeOutBounce: easeOutBounce, 4708 easeInOutBounce: easeInOutBounce, 4709 }; 4710})(); 4711 4712(function (global) { 4713 "use strict"; 4714 4715 /** 4716 * @name fabric 4717 * @namespace 4718 */ 4719 4720 var fabric = global.fabric || (global.fabric = {}), 4721 extend = fabric.util.object.extend, 4722 clone = fabric.util.object.clone, 4723 toFixed = fabric.util.toFixed, 4724 parseUnit = fabric.util.parseUnit, 4725 multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, 4726 svgValidTagNames = [ 4727 "path", 4728 "circle", 4729 "polygon", 4730 "polyline", 4731 "ellipse", 4732 "rect", 4733 "line", 4734 "image", 4735 "text", 4736 ], 4737 svgViewBoxElements = [ 4738 "symbol", 4739 "image", 4740 "marker", 4741 "pattern", 4742 "view", 4743 "svg", 4744 ], 4745 svgInvalidAncestors = [ 4746 "pattern", 4747 "defs", 4748 "symbol", 4749 "metadata", 4750 "clipPath", 4751 "mask", 4752 "desc", 4753 ], 4754 svgValidParents = ["symbol", "g", "a", "svg", "clipPath", "defs"], 4755 attributesMap = { 4756 cx: "left", 4757 x: "left", 4758 r: "radius", 4759 cy: "top", 4760 y: "top", 4761 display: "visible", 4762 visibility: "visible", 4763 transform: "transformMatrix", 4764 "fill-opacity": "fillOpacity", 4765 "fill-rule": "fillRule", 4766 "font-family": "fontFamily", 4767 "font-size": "fontSize", 4768 "font-style": "fontStyle", 4769 "font-weight": "fontWeight", 4770 "letter-spacing": "charSpacing", 4771 "paint-order": "paintFirst", 4772 "stroke-dasharray": "strokeDashArray", 4773 "stroke-dashoffset": "strokeDashOffset", 4774 "stroke-linecap": "strokeLineCap", 4775 "stroke-linejoin": "strokeLineJoin", 4776 "stroke-miterlimit": "strokeMiterLimit", 4777 "stroke-opacity": "strokeOpacity", 4778 "stroke-width": "strokeWidth", 4779 "text-decoration": "textDecoration", 4780 "text-anchor": "textAnchor", 4781 opacity: "opacity", 4782 "clip-path": "clipPath", 4783 "clip-rule": "clipRule", 4784 "vector-effect": "strokeUniform", 4785 "image-rendering": "imageSmoothing", 4786 }, 4787 colorAttributes = { 4788 stroke: "strokeOpacity", 4789 fill: "fillOpacity", 4790 }, 4791 fSize = "font-size", 4792 cPath = "clip-path"; 4793 4794 fabric.svgValidTagNamesRegEx = getSvgRegex(svgValidTagNames); 4795 fabric.svgViewBoxElementsRegEx = getSvgRegex(svgViewBoxElements); 4796 fabric.svgInvalidAncestorsRegEx = getSvgRegex(svgInvalidAncestors); 4797 fabric.svgValidParentsRegEx = getSvgRegex(svgValidParents); 4798 4799 fabric.cssRules = {}; 4800 fabric.gradientDefs = {}; 4801 fabric.clipPaths = {}; 4802 4803 function normalizeAttr(attr) { 4804 // transform attribute names 4805 if (attr in attributesMap) { 4806 return attributesMap[attr]; 4807 } 4808 return attr; 4809 } 4810 4811 function normalizeValue(attr, value, parentAttributes, fontSize) { 4812 var isArray = Array.isArray(value),
4813 parsed; 4814 4815 if ((attr === "fill" || attr === "stroke") && value === "none") { 4816 value = ""; 4817 } else if (attr === "strokeUniform") { 4818 return value === "non-scaling-stroke"; 4819 } else if (attr === "strokeDashArray") { 4820 if (value === "none") { 4821 value = null; 4822 } else { 4823 value = value.replace(/,/g, " ").split(/\s+/).map(parseFloat); 4824 } 4825 } else if (attr === "transformMatrix") { 4826 if (parentAttributes && parentAttributes.transformMatrix) { 4827 value = multiplyTransformMatrices( 4828 parentAttributes.transformMatrix, 4829 fabric.parseTransformAttribute(value), 4830 ); 4831 } else { 4832 value = fabric.parseTransformAttribute(value); 4833 } 4834 } else if (attr === "visible") { 4835 value = value !== "none" && value !== "hidden"; 4836 // display=none on parent element always takes precedence over child element 4837 if (parentAttributes && parentAttributes.visible === false) { 4838 value = false; 4839 } 4840 } else if (attr === "opacity") { 4841 value = parseFloat(value); 4842 if (parentAttributes && typeof parentAttributes.opacity !== "undefined") { 4843 value *= parentAttributes.opacity; 4844 } 4845 } else if (attr === "textAnchor" /* text-anchor */) { 4846 value = value === "start" ? "left" : value === "end" ? "right" : "center"; 4847 } else if (attr === "charSpacing") { 4848 // parseUnit returns px and we convert it to em 4849 parsed = (parseUnit(value, fontSize) / fontSize) * 1000; 4850 } else if (attr === "paintFirst") { 4851 var fillIndex = value.indexOf("fill"); 4852 var strokeIndex = value.indexOf("stroke"); 4853 var value = "fill"; 4854 if (fillIndex > -1 && strokeIndex > -1 && strokeIndex < fillIndex) { 4855 value = "stroke"; 4856 } else if (fillIndex === -1 && strokeIndex > -1) { 4857 value = "stroke"; 4858 } 4859 } else if (attr === "href" || attr === "xlink:href" || attr === "font") { 4860 return value; 4861 } else if (attr === "imageSmoothing") { 4862 return value === "optimizeQuality"; 4863 } else { 4864 parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); 4865 } 4866 4867 return !isArray && isNaN(parsed) ? value : parsed; 4868 } 4869 4870 /** 4871 * @private 4872 */ 4873 function getSvgRegex(arr) { 4874 return new RegExp("^(" + arr.join("|") + ")\\b", "i"); 4875 } 4876 4877 /** 4878 * @private 4879 * @param {Object} attributes Array of attributes to parse 4880 */ 4881 function _setStrokeFillOpacity(attributes) { 4882 for (var attr in colorAttributes) { 4883 if ( 4884 typeof attributes[colorAttributes[attr]] === "undefined" || 4885 attributes[attr] === "" 4886 ) { 4887 continue; 4888 } 4889 4890 if (typeof attributes[attr] === "undefined") { 4891 if (!fabric.Object.prototype[attr]) { 4892 continue; 4893 } 4894 attributes[attr] = fabric.Object.prototype[attr]; 4895 } 4896 4897 if (attributes[attr].indexOf("url(") === 0) { 4898 continue; 4899 } 4900 4901 var color = new fabric.Color(attributes[attr]); 4902 attributes[attr] = color 4903 .setAlpha( 4904 toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2), 4905 ) 4906 .toRgba(); 4907 } 4908 return attributes; 4909 } 4910 4911 /** 4912 * @private 4913 */ 4914 function _getMultipleNodes(doc, nodeNames) { 4915 var nodeName, 4916 nodeArray = [], 4917 nodeList, 4918 i, 4919 len; 4920 for (i = 0, len = nodeNames.length; i < len; i++) { 4921 nodeName = nodeNames[i]; 4922 nodeList = doc.getElementsByTagName(nodeName); 4923 nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList)); 4924 } 4925 return nodeArray; 4926 } 4927 4928 /** 4929 * Parses "transform" attribute, returning an array of values 4930 * @static 4931 * @function 4932 * @memberOf fabric 4933 * @param {String} attributeValue String containing attribute value 4934 * @return {Array} Array of 6 elements representing transformation matrix 4935 */ 4936 fabric.parseTransformAttribute = (function () { 4937 function rotateMatrix(matrix, args) { 4938 var cos = fabric.util.cos(args[0]), 4939 sin = fabric.util.sin(args[0]), 4940 x = 0, 4941 y = 0; 4942 if (args.length === 3) { 4943 x = args[1]; 4944 y = args[2]; 4945 } 4946 4947 matrix[0] = cos;
4948 matrix[1] = sin; 4949 matrix[2] = -sin; 4950 matrix[3] = cos; 4951 matrix[4] = x - (cos * x - sin * y); 4952 matrix[5] = y - (sin * x + cos * y); 4953 } 4954 4955 function scaleMatrix(matrix, args) { 4956 var multiplierX = args[0], 4957 multiplierY = args.length === 2 ? args[1] : args[0]; 4958 4959 matrix[0] = multiplierX; 4960 matrix[3] = multiplierY; 4961 } 4962 4963 function skewMatrix(matrix, args, pos) { 4964 matrix[pos] = Math.tan(fabric.util.degreesToRadians(args[0])); 4965 } 4966 4967 function translateMatrix(matrix, args) { 4968 matrix[4] = args[0]; 4969 if (args.length === 2) { 4970 matrix[5] = args[1]; 4971 } 4972 } 4973 4974 // identity matrix 4975 var iMatrix = fabric.iMatrix, 4976 // == begin transform regexp 4977 number = fabric.reNum, 4978 commaWsp = fabric.commaWsp, 4979 skewX = "(?:(skewX)\\s*\\(\\s*(" + number + ")\\s*\\))", 4980 skewY = "(?:(skewY)\\s*\\(\\s*(" + number + ")\\s*\\))", 4981 rotate = 4982 "(?:(rotate)\\s*\\(\\s*(" + 4983 number + 4984 ")(?:" + 4985 commaWsp + 4986 "(" + 4987 number + 4988 ")" + 4989 commaWsp + 4990 "(" + 4991 number + 4992 "))?\\s*\\))", 4993 scale = 4994 "(?:(scale)\\s*\\(\\s*(" + 4995 number + 4996 ")(?:" + 4997 commaWsp + 4998 "(" + 4999 number + 5000 "))?\\s*\\))", 5001 translate = 5002 "(?:(translate)\\s*\\(\\s*(" + 5003 number + 5004 ")(?:" + 5005 commaWsp + 5006 "(" + 5007 number + 5008 "))?\\s*\\))", 5009 matrix = 5010 "(?:(matrix)\\s*\\(\\s*" + 5011 "(" + 5012 number + 5013 ")" + 5014 commaWsp + 5015 "(" + 5016 number + 5017 ")" + 5018 commaWsp + 5019 "(" + 5020 number + 5021 ")" + 5022 commaWsp + 5023 "(" + 5024 number + 5025 ")" + 5026 commaWsp + 5027 "(" + 5028 number + 5029 ")" + 5030 commaWsp + 5031 "(" + 5032 number + 5033 ")" + 5034 "\\s*\\))", 5035 transform = 5036 "(?:" + 5037 matrix + 5038 "|" + 5039 translate + 5040 "|" + 5041 scale + 5042 "|" + 5043 rotate + 5044 "|" + 5045 skewX + 5046 "|" + 5047 skewY + 5048 ")", 5049 transforms = 5050 "(?:" + transform + "(?:" + commaWsp + "*" + transform + ")*" + ")", 5051 transformList = "^\\s*(?:" + transforms + "?)\\s*$", 5052 // http://www.w3.org/TR/SVG/coords.html#TransformAttribute 5053 reTransformList = new RegExp(transformList), 5054 // == end transform regexp 5055 5056 reTransform = new RegExp(transform, "g"); 5057 5058 return function (attributeValue) { 5059 // start with identity matrix 5060 var matrix = iMatrix.concat(), 5061 matrices = []; 5062 5063 // return if no argument was given or 5064 // an argument does not match transform attribute regexp 5065 if ( 5066 !attributeValue || 5067 (attributeValue && !reTransformList.test(attributeValue)) 5068 ) { 5069 return matrix; 5070 } 5071 5072 attributeValue.replace(reTransform, function (match) { 5073 var m = new RegExp(transform).exec(match).filter(function (match) { 5074 // match !== '' && match != null 5075 return !!match; 5076 }), 5077 operation = m[1], 5078 args = m.slice(2).map(parseFloat); 5079 5080 switch (operation) { 5081 case "translate": 5082 translateMatrix(matrix, args); 5083 break; 5084 case "rotate": 5085 args[0] = fabric.util.degreesToRadians(args[0]); 5086 rotateMatrix(matrix, args); 5087 break; 5088 case "scale": 5089 scaleMatrix(matrix, args); 5090 break; 5091 case "skewX": 5092 skewMatrix(matrix, args, 2); 5093 break; 5094 case "skewY": 5095 skewMatrix(matrix, args, 1); 5096 break; 5097 case "matrix": 5098 matrix = args; 5099 break; 5100 } 5101 5102 // snapshot current matrix into matrices array 5103 matrices.push(matrix.concat()); 5104 // reset 5105 matrix = iMatrix.concat(); 5106 }); 5107 5108 var combinedMatrix = matrices[0]; 5109 while (matrices.length > 1) { 5110 matrices.shift(); 5111 combinedMatrix = fabric.util.multiplyTransformMatrices( 5112 combinedMatrix, 5113 matrices[0], 5114 ); 5115 } 5116 return combinedMatrix; 5117 }; 5118 })(); 5119 5120 /** 5121 * @private 5122 */
vendor: 6,206 bytes, lines 5123-5358
5123 function parseStyleString(style, oStyle) { 5124 var attr, value; 5125 style 5126 .replace(/;\s*$/, "") 5127 .split(";") 5128 .forEach(function (chunk) { 5129 var pair = chunk.split(":"); 5130 5131 attr = pair[0].trim().toLowerCase(); 5132 value = pair[1].trim(); 5133 5134 oStyle[attr] = value; 5135 }); 5136 } 5137 5138 /** 5139 * @private 5140 */ 5141 function parseStyleObject(style, oStyle) { 5142 var attr, value; 5143 for (var prop in style) { 5144 if (typeof style[prop] === "undefined") { 5145 continue; 5146 } 5147 5148 attr = prop.toLowerCase(); 5149 value = style[prop]; 5150 5151 oStyle[attr] = value; 5152 } 5153 } 5154 5155 /** 5156 * @private 5157 */ 5158 function getGlobalStylesForElement(element, svgUid) { 5159 var styles = {}; 5160 for (var rule in fabric.cssRules[svgUid]) { 5161 if (elementMatchesRule(element, rule.split(" "))) { 5162 for (var property in fabric.cssRules[svgUid][rule]) { 5163 styles[property] = fabric.cssRules[svgUid][rule][property]; 5164 } 5165 } 5166 } 5167 return styles; 5168 } 5169 5170 /** 5171 * @private 5172 */ 5173 function elementMatchesRule(element, selectors) { 5174 var firstMatching, 5175 parentMatching = true; 5176 //start from rightmost selector. 5177 firstMatching = selectorMatches(element, selectors.pop()); 5178 if (firstMatching && selectors.length) { 5179 parentMatching = doesSomeParentMatch(element, selectors); 5180 } 5181 return firstMatching && parentMatching && selectors.length === 0; 5182 } 5183 5184 function doesSomeParentMatch(element, selectors) { 5185 var selector, 5186 parentMatching = true; 5187 while ( 5188 element.parentNode && 5189 element.parentNode.nodeType === 1 && 5190 selectors.length 5191 ) { 5192 if (parentMatching) { 5193 selector = selectors.pop(); 5194 } 5195 element = element.parentNode; 5196 parentMatching = selectorMatches(element, selector); 5197 } 5198 return selectors.length === 0; 5199 } 5200 5201 /** 5202 * @private 5203 */ 5204 function selectorMatches(element, selector) { 5205 var nodeName = element.nodeName, 5206 classNames = element.getAttribute("class"), 5207 id = element.getAttribute("id"), 5208 matcher, 5209 i; 5210 // i check if a selector matches slicing away part from it. 5211 // if i get empty string i should match 5212 matcher = new RegExp("^" + nodeName, "i"); 5213 selector = selector.replace(matcher, ""); 5214 if (id && selector.length) { 5215 matcher = new RegExp("#" + id + "(?![a-zA-Z\\-]+)", "i"); 5216 selector = selector.replace(matcher, ""); 5217 } 5218 if (classNames && selector.length) { 5219 classNames = classNames.split(" "); 5220 for (i = classNames.length; i--; ) { 5221 matcher = new RegExp("\\." + classNames[i] + "(?![a-zA-Z\\-]+)", "i"); 5222 selector = selector.replace(matcher, ""); 5223 } 5224 } 5225 return selector.length === 0; 5226 } 5227 5228 /** 5229 * @private 5230 * to support IE8 missing getElementById on SVGdocument and on node xmlDOM 5231 */ 5232 function elementById(doc, id) { 5233 var el; 5234 doc.getElementById && (el = doc.getElementById(id)); 5235 if (el) { 5236 return el; 5237 } 5238 var node, 5239 i, 5240 len, 5241 nodelist = doc.getElementsByTagName("*"); 5242 for (i = 0, len = nodelist.length; i < len; i++) { 5243 node = nodelist[i]; 5244 if (id === node.getAttribute("id")) { 5245 return node; 5246 } 5247 } 5248 } 5249 5250 /** 5251 * @private 5252 */ 5253 function parseUseDirectives(doc) { 5254 var nodelist = _getMultipleNodes(doc, ["use", "svg:use"]), 5255 i = 0; 5256 while (nodelist.length && i < nodelist.length) { 5257 var el = nodelist[i], 5258 xlinkAttribute = 5259 el.getAttribute("xlink:href") || el.getAttribute("href"); 5260 5261 if (xlinkAttribute === null) { 5262 return; 5263 } 5264 5265 var xlink = xlinkAttribute.slice(1), 5266 x = el.getAttribute("x") || 0, 5267 y = el.getAttribute("y") || 0, 5268 el2 = elementById(doc, xlink).cloneNode(true), 5269 currentTrans = 5270 (el2.getAttribute("transform") || "") + 5271 " translate(" + 5272 x + 5273 ", " + 5274 y + 5275 ")", 5276 parentNode, 5277 oldLength = nodelist.length, 5278 attr, 5279 j, 5280 attrs, 5281 len, 5282 namespace = fabric.svgNS; 5283 5284 applyViewboxTransform(el2); 5285 if (/^svg$/i.test(el2.nodeName)) { 5286 var el3 = el2.ownerDocument.createElementNS(namespace, "g"); 5287 for (j = 0, attrs = el2.attributes, len = attrs.length; j < len; j++) { 5288 attr = attrs.item(j); 5289 el3.setAttributeNS(namespace, attr.nodeName, attr.nodeValue); 5290 } 5291 // el2.firstChild != null 5292 while (el2.firstChild) { 5293 el3.appendChild(el2.firstChild); 5294 } 5295 el2 = el3; 5296 } 5297 5298 for (j = 0, attrs = el.attributes, len = attrs.length; j < len; j++) { 5299 attr = attrs.item(j); 5300 if ( 5301 attr.nodeName === "x" || 5302 attr.nodeName === "y" || 5303 attr.nodeName === "xlink:href" || 5304 attr.nodeName === "href" 5305 ) { 5306 continue; 5307 } 5308 5309 if (attr.nodeName === "transform") { 5310 currentTrans = attr.nodeValue + " " + currentTrans; 5311 } else { 5312 el2.setAttribute(attr.nodeName, attr.nodeValue); 5313 } 5314 } 5315 5316 el2.setAttribute("transform", currentTrans); 5317 el2.setAttribute("instantiated_by_use", "1"); 5318 el2.removeAttribute("id"); 5319 parentNode = el.parentNode; 5320 parentNode.replaceChild(el2, el); 5321 // some browsers do not shorten nodelist after replaceChild (IE8) 5322 if (nodelist.length === oldLength) { 5323 i++; 5324 } 5325 } 5326 } 5327 5328 // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute 5329 // matches, e.g.: +14.56e-12, etc. 5330 var reViewBoxAttrValue = new RegExp( 5331 "^" + 5332 "\\s*(" + 5333 fabric.reNum + 5334 "+)\\s*,?" + 5335 "\\s*(" + 5336 fabric.reNum + 5337 "+)\\s*,?" + 5338 "\\s*(" + 5339 fabric.reNum + 5340 "+)\\s*,?" + 5341 "\\s*(" + 5342 fabric.reNum + 5343 "+)\\s*" + 5344 "$", 5345 ); 5346 5347 /** 5348 * Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements 5349 */ 5350 function applyViewboxTransform(element) { 5351 if (!fabric.svgViewBoxElementsRegEx.test(element.nodeName)) { 5352 return {}; 5353 } 5354 var viewBoxAttr = element.getAttribute("viewBox"), 5355 scaleX = 1, 5356 scaleY = 1, 5357 minX = 0, 5358 minY = 0,
5359 viewBoxWidth, 5360 viewBoxHeight, 5361 matrix, 5362 el, 5363 widthAttr = element.getAttribute("width"), 5364 heightAttr = element.getAttribute("height"), 5365 x = element.getAttribute("x") || 0, 5366 y = element.getAttribute("y") || 0, 5367 preserveAspectRatio = element.getAttribute("preserveAspectRatio") || "", 5368 missingViewBox = 5369 !viewBoxAttr || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue)), 5370 missingDimAttr = 5371 !widthAttr || 5372 !heightAttr || 5373 widthAttr === "100%" || 5374 heightAttr === "100%", 5375 toBeParsed = missingViewBox && missingDimAttr, 5376 parsedDim = {}, 5377 translateMatrix = "", 5378 widthDiff = 0, 5379 heightDiff = 0; 5380 5381 parsedDim.width = 0; 5382 parsedDim.height = 0; 5383 parsedDim.toBeParsed = toBeParsed; 5384 5385 if (missingViewBox) { 5386 if ( 5387 (x || y) && 5388 element.parentNode && 5389 element.parentNode.nodeName !== "#document" 5390 ) { 5391 translateMatrix = 5392 " translate(" + parseUnit(x) + " " + parseUnit(y) + ") "; 5393 matrix = (element.getAttribute("transform") || "") + translateMatrix; 5394 element.setAttribute("transform", matrix); 5395 element.removeAttribute("x"); 5396 element.removeAttribute("y"); 5397 } 5398 } 5399 5400 if (toBeParsed) { 5401 return parsedDim; 5402 } 5403 5404 if (missingViewBox) { 5405 parsedDim.width = parseUnit(widthAttr); 5406 parsedDim.height = parseUnit(heightAttr); 5407 // set a transform for elements that have x y and are inner(only) SVGs 5408 return parsedDim; 5409 } 5410 minX = -parseFloat(viewBoxAttr[1]); 5411 minY = -parseFloat(viewBoxAttr[2]); 5412 viewBoxWidth = parseFloat(viewBoxAttr[3]); 5413 viewBoxHeight = parseFloat(viewBoxAttr[4]); 5414 parsedDim.minX = minX; 5415 parsedDim.minY = minY; 5416 parsedDim.viewBoxWidth = viewBoxWidth; 5417 parsedDim.viewBoxHeight = viewBoxHeight; 5418 if (!missingDimAttr) { 5419 parsedDim.width = parseUnit(widthAttr); 5420 parsedDim.height = parseUnit(heightAttr); 5421 scaleX = parsedDim.width / viewBoxWidth; 5422 scaleY = parsedDim.height / viewBoxHeight; 5423 } else { 5424 parsedDim.width = viewBoxWidth; 5425 parsedDim.height = viewBoxHeight; 5426 } 5427 5428 // default is to preserve aspect ratio 5429 preserveAspectRatio = 5430 fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio); 5431 if (preserveAspectRatio.alignX !== "none") { 5432 //translate all container for the effect of Mid, Min, Max 5433 if (preserveAspectRatio.meetOrSlice === "meet") { 5434 scaleY = scaleX = scaleX > scaleY ? scaleY : scaleX; 5435 // calculate additional translation to move the viewbox 5436 } 5437 if (preserveAspectRatio.meetOrSlice === "slice") { 5438 scaleY = scaleX = scaleX > scaleY ? scaleX : scaleY; 5439 // calculate additional translation to move the viewbox 5440 } 5441 widthDiff = parsedDim.width - viewBoxWidth * scaleX; 5442 heightDiff = parsedDim.height - viewBoxHeight * scaleX; 5443 if (preserveAspectRatio.alignX === "Mid") { 5444 widthDiff /= 2; 5445 } 5446 if (preserveAspectRatio.alignY === "Mid") { 5447 heightDiff /= 2; 5448 } 5449 if (preserveAspectRatio.alignX === "Min") { 5450 widthDiff = 0; 5451 } 5452 if (preserveAspectRatio.alignY === "Min") { 5453 heightDiff = 0; 5454 } 5455 } 5456 5457 if ( 5458 scaleX === 1 && 5459 scaleY === 1 && 5460 minX === 0 && 5461 minY === 0 && 5462 x === 0 && 5463 y === 0 5464 ) { 5465 return parsedDim; 5466 } 5467 if ((x || y) && element.parentNode.nodeName !== "#document") { 5468 translateMatrix = 5469 " translate(" + parseUnit(x) + " " + parseUnit(y) + ") "; 5470 } 5471 5472 matrix = 5473 translateMatrix + 5474 " matrix(" + 5475 scaleX + 5476 " 0" + 5477 " 0 " + 5478 scaleY + 5479 " " + 5480 (minX * scaleX + widthDiff) + 5481 " " + 5482 (minY * scaleY + heightDiff) + 5483 ") "; 5484 // seems unused. 5485 // parsedDim.viewboxTransform = fabric.parseTransformAttribute(matrix); 5486 if (element.nodeName === "svg") { 5487 el = element.ownerDocument.createElementNS(fabric.svgNS, "g"); 5488 // element.firstChild != null 5489 while (element.firstChild) { 5490 el.appendChild(element.firstChild); 5491 } 5492 element.appendChild(el); 5493 } else { 5494 el = element; 5495 el.removeAttribute("x"); 5496 el.removeAttribute("y"); 5497 matrix = el.getAttribute("transform") + matrix; 5498 }
5499 el.setAttribute("transform", matrix); 5500 return parsedDim; 5501 } 5502 5503 function hasAncestorWithNodeName(element, nodeName) { 5504 while (element && (element = element.parentNode)) { 5505 if ( 5506 element.nodeName && 5507 nodeName.test(element.nodeName.replace("svg:", "")) && 5508 !element.getAttribute("instantiated_by_use") 5509 ) { 5510 return true; 5511 } 5512 } 5513 return false; 5514 } 5515 5516 /** 5517 * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback 5518 * @static 5519 * @function 5520 * @memberOf fabric 5521 * @param {SVGDocument} doc SVG document to parse 5522 * @param {Function} callback Callback to call when parsing is finished; 5523 * It's being passed an array of elements (parsed from a document). 5524 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 5525 * @param {Object} [parsingOptions] options for parsing document 5526 * @param {String} [parsingOptions.crossOrigin] crossOrigin settings 5527 */ 5528 fabric.parseSVGDocument = function (doc, callback, reviver, parsingOptions) { 5529 if (!doc) { 5530 return; 5531 } 5532 5533 parseUseDirectives(doc); 5534 5535 var svgUid = fabric.Object.__uid++, 5536 i, 5537 len, 5538 options = applyViewboxTransform(doc), 5539 descendants = fabric.util.toArray(doc.getElementsByTagName("*")); 5540 options.crossOrigin = parsingOptions && parsingOptions.crossOrigin; 5541 options.svgUid = svgUid; 5542 5543 if (descendants.length === 0 && fabric.isLikelyNode) { 5544 // we're likely in node, where "o3-xml" library fails to gEBTN("*") 5545 // https://github.com/ajaxorg/node-o3-xml/issues/21 5546 descendants = doc.selectNodes('//*[name(.)!="svg"]'); 5547 var arr = []; 5548 for (i = 0, len = descendants.length; i < len; i++) { 5549 arr[i] = descendants[i]; 5550 } 5551 descendants = arr; 5552 } 5553 5554 var elements = descendants.filter(function (el) { 5555 applyViewboxTransform(el); 5556 return ( 5557 fabric.svgValidTagNamesRegEx.test(el.nodeName.replace("svg:", "")) && 5558 !hasAncestorWithNodeName(el, fabric.svgInvalidAncestorsRegEx) 5559 ); // http://www.w3.org/TR/SVG/struct.html#DefsElement 5560 }); 5561 if (!elements || (elements && !elements.length)) { 5562 callback && callback([], {}); 5563 return; 5564 } 5565 var clipPaths = {}; 5566 descendants 5567 .filter(function (el) { 5568 return el.nodeName.replace("svg:", "") === "clipPath"; 5569 }) 5570 .forEach(function (el) { 5571 var id = el.getAttribute("id"); 5572 clipPaths[id] = fabric.util 5573 .toArray(el.getElementsByTagName("*")) 5574 .filter(function (el) { 5575 return fabric.svgValidTagNamesRegEx.test( 5576 el.nodeName.replace("svg:", ""), 5577 ); 5578 }); 5579 }); 5580 fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); 5581 fabric.cssRules[svgUid] = fabric.getCSSRules(doc); 5582 fabric.clipPaths[svgUid] = clipPaths; 5583 // Precedence of rules: style > class > attribute 5584 fabric.parseElements( 5585 elements, 5586 function (instances, elements) { 5587 if (callback) { 5588 callback(instances, options, elements, descendants); 5589 delete fabric.gradientDefs[svgUid]; 5590 delete fabric.cssRules[svgUid]; 5591 delete fabric.clipPaths[svgUid]; 5592 } 5593 }, 5594 clone(options), 5595 reviver, 5596 parsingOptions, 5597 ); 5598 }; 5599 5600 function recursivelyParseGradientsXlink(doc, gradient) { 5601 var gradientsAttrs = [ 5602 "gradientTransform", 5603 "x1", 5604 "x2", 5605 "y1", 5606 "y2", 5607 "gradientUnits", 5608 "cx", 5609 "cy", 5610 "r", 5611 "fx", 5612 "fy", 5613 ], 5614 xlinkAttr = "xlink:href", 5615 xLink = gradient.getAttribute(xlinkAttr).slice(1), 5616 referencedGradient = elementById(doc, xLink); 5617 if (referencedGradient && referencedGradient.getAttribute(xlinkAttr)) { 5618 recursivelyParseGradientsXlink(doc, referencedGradient); 5619 } 5620 gradientsAttrs.forEach(function (attr) { 5621 if ( 5622 referencedGradient && 5623 !gradient.hasAttribute(attr) && 5624 referencedGradient.hasAttribute(attr) 5625 ) { 5626 gradient.setAttribute(attr, referencedGradient.getAttribute(attr)); 5627 } 5628 }); 5629 if (!gradient.children.length) { 5630 var referenceClone = referencedGradient.cloneNode(true); 5631 while (referenceClone.firstChild) { 5632 gradient.appendChild(referenceClone.firstChild); 5633 } 5634 } 5635 gradient.removeAttribute(xlinkAttr); 5636 } 5637 5638 var reFontDeclaration = new RegExp( 5639 "(normal|italic)?\\s*(normal|small-caps)?\\s*" + 5640 "(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(" + 5641 fabric.reNum +
5642 "(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|" + 5643 fabric.reNum + 5644 "))?\\s+(.*)", 5645 ); 5646 5647 extend(fabric, { 5648 /** 5649 * Parses a short font declaration, building adding its properties to a style object 5650 * @static 5651 * @function 5652 * @memberOf fabric 5653 * @param {String} value font declaration 5654 * @param {Object} oStyle definition 5655 */ 5656 parseFontDeclaration: function (value, oStyle) { 5657 var match = value.match(reFontDeclaration); 5658 5659 if (!match) { 5660 return; 5661 } 5662 var fontStyle = match[1], 5663 // font variant is not used 5664 // fontVariant = match[2], 5665 fontWeight = match[3], 5666 fontSize = match[4], 5667 lineHeight = match[5], 5668 fontFamily = match[6]; 5669 5670 if (fontStyle) { 5671 oStyle.fontStyle = fontStyle; 5672 } 5673 if (fontWeight) { 5674 oStyle.fontWeight = isNaN(parseFloat(fontWeight)) 5675 ? fontWeight 5676 : parseFloat(fontWeight); 5677 } 5678 if (fontSize) { 5679 oStyle.fontSize = parseUnit(fontSize); 5680 } 5681 if (fontFamily) { 5682 oStyle.fontFamily = fontFamily; 5683 } 5684 if (lineHeight) { 5685 oStyle.lineHeight = lineHeight === "normal" ? 1 : lineHeight; 5686 } 5687 }, 5688 5689 /** 5690 * Parses an SVG document, returning all of the gradient declarations found in it 5691 * @static 5692 * @function 5693 * @memberOf fabric 5694 * @param {SVGDocument} doc SVG document to parse 5695 * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element 5696 */ 5697 getGradientDefs: function (doc) { 5698 var tagArray = [ 5699 "linearGradient", 5700 "radialGradient", 5701 "svg:linearGradient", 5702 "svg:radialGradient", 5703 ], 5704 elList = _getMultipleNodes(doc, tagArray), 5705 el, 5706 j = 0, 5707 gradientDefs = {}; 5708 j = elList.length; 5709 while (j--) { 5710 el = elList[j]; 5711 if (el.getAttribute("xlink:href")) { 5712 recursivelyParseGradientsXlink(doc, el); 5713 } 5714 gradientDefs[el.getAttribute("id")] = el; 5715 } 5716 return gradientDefs; 5717 }, 5718 5719 /** 5720 * Returns an object of attributes' name/value, given element and an array of attribute names; 5721 * Parses parent "g" nodes recursively upwards. 5722 * @static 5723 * @memberOf fabric 5724 * @param {DOMElement} element Element to parse 5725 * @param {Array} attributes Array of attributes to parse 5726 * @return {Object}
5726 object containing parsed attributes' names/values 5727 */ 5728 parseAttributes: function (element, attributes, svgUid) { 5729 if (!element) { 5730 return; 5731 } 5732 5733 var value, 5734 parentAttributes = {}, 5735 fontSize, 5736 parentFontSize; 5737 5738 if (typeof svgUid === "undefined") { 5739 svgUid = element.getAttribute("svgUid"); 5740 } 5741 // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards 5742 if ( 5743 element.parentNode && 5744 fabric.svgValidParentsRegEx.test(element.parentNode.nodeName) 5745 ) { 5746 parentAttributes = fabric.parseAttributes( 5747 element.parentNode, 5748 attributes, 5749 svgUid, 5750 ); 5751 } 5752 5753 var ownAttributes = attributes.reduce(function (memo, attr) { 5754 value = element.getAttribute(attr); 5755 if (value) { 5756 // eslint-disable-line 5757 memo[attr] = value; 5758 } 5759 return memo; 5760 }, {}); 5761 // add values parsed from style, which take precedence over attributes 5762 // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) 5763 var cssAttrs = extend( 5764 getGlobalStylesForElement(element, svgUid), 5765 fabric.parseStyleAttribute(element), 5766 ); 5767 ownAttributes = extend(ownAttributes, cssAttrs); 5768 if (cssAttrs[cPath]) { 5769 element.setAttribute(cPath, cssAttrs[cPath]); 5770 } 5771 fontSize = parentFontSize = 5772 parentAttributes.fontSize || fabric.Text.DEFAULT_SVG_FONT_SIZE; 5773 if (ownAttributes[fSize]) { 5774 // looks like the minimum should be 9px when dealing with ems. this is what looks like in browsers. 5775 ownAttributes[fSize] = fontSize = parseUnit( 5776 ownAttributes[fSize], 5777 parentFontSize, 5778 ); 5779 } 5780 5781 var normalizedAttr, 5782 normalizedValue, 5783 normalizedStyle = {}; 5784 for (var attr in ownAttributes) { 5785 normalizedAttr = normalizeAttr(attr); 5786 normalizedValue = normalizeValue( 5787 normalizedAttr, 5788 ownAttributes[attr], 5789 parentAttributes, 5790 fontSize, 5791 ); 5792 normalizedStyle[normalizedAttr] = normalizedValue; 5793 } 5794 if (normalizedStyle && normalizedStyle.font) { 5795 fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle); 5796 } 5797 var mergedAttrs = extend(parentAttributes, normalizedStyle); 5798 return fabric.svgValidParentsRegEx.test(element.nodeName) 5799 ? mergedAttrs 5800 : _setStrokeFillOpacity(mergedAttrs); 5801 }, 5802 5803 /** 5804 * Transforms an array of svg elements to corresponding fabric.*
5804instances 5805 * @static 5806 * @memberOf fabric 5807 * @param {Array} elements Array of elements to parse 5808 * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) 5809 * @param {Object} [options] Options object 5810 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 5811 */ 5812 parseElements: function ( 5813 elements, 5814 callback, 5815 options, 5816 reviver, 5817 parsingOptions, 5818 ) { 5819 new fabric.ElementsParser( 5820 elements, 5821 callback, 5822 options, 5823 reviver, 5824 parsingOptions, 5825 ).parse(); 5826 }, 5827 5828 /** 5829 * Parses "style" attribute, retuning an object with values 5830 * @static 5831 * @memberOf fabric 5832 * @param {SVGElement} element Element to parse 5833 * @return {Object} Objects with values parsed from style attribute of an element 5834 */ 5835 parseStyleAttribute: function (element) { 5836 var oStyle = {}, 5837 style = element.getAttribute("style"); 5838 5839 if (!style) { 5840 return oStyle; 5841 } 5842 5843 if (typeof style === "string") { 5844 parseStyleString(style, oStyle); 5845 } else { 5846 parseStyleObject(style, oStyle); 5847 } 5848 5849 return oStyle; 5850 }, 5851 5852 /** 5853 * Parses "points" attribute, returning an array of values 5854 * @static 5855 * @memberOf fabric 5856 * @param {String} points points attribute string 5857 * @return {Array} array of points 5858 */ 5859 parsePointsAttribute: function (points) { 5860 // points attribute is required and must not be empty 5861 if (!points) { 5862 return null; 5863 } 5864 5865 // replace commas with whitespace and remove bookending whitespace 5866 points = points.replace(/,/g, " ").trim(); 5867 5868 points = points.split(/\s+/); 5869 var parsedPoints = [], 5870 i, 5871 len; 5872 5873 for (i = 0, len = points.length; i < len; i += 2) { 5874 parsedPoints.push({ 5875 x: parseFloat(points[i]), 5876 y: parseFloat(points[i + 1]), 5877 }); 5878 } 5879 5880 // odd number of points is an error 5881 // if (parsedPoints.length % 2 !== 0) { 5882 // return null; 5883 // } 5884 5885 return parsedPoints; 5886 }, 5887 5888 /** 5889 * Returns CSS rules for a given SVG document 5890 * @static 5891 * @function 5892 * @memberOf fabric 5893 * @param {SVGDocument} doc SVG document to parse 5894 * @return {Object} CSS rules of this document 5895 */ 5896 getCSSRules: function (doc) { 5897 var styles = doc.getElementsByTagName("style"), 5898 i, 5899 len, 5900 allRules = {}, 5901 rules; 5902 5903 // very crude parsing of style contents 5904 for (i = 0, len = styles.length; i < len; i++) { 5905 var styleContents = styles[i].textContent; 5906 5907 // remove comments 5908 styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ""); 5909 if (styleContents.trim() === "") { 5910 continue; 5911 } 5912 // recovers all the rule in this form `body { style code... }` 5913 // rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); 5914 rules = styleContents.split("}"); 5915 // remove empty rules. 5916 rules = rules.filter(function (rule) { 5917 return rule.trim(); 5918 }); 5919 // at this point we have hopefully an array of rules `body { style code... ` 5920 // eslint-disable-next-line no-loop-func
5921 rules.forEach(function (rule) { 5922 var match = rule.split("{"), 5923 ruleObj = {}, 5924 declaration = match[1].trim(), 5925 propertyValuePairs = declaration.split(";").filter(function (pair) { 5926 return pair.trim(); 5927 }); 5928 5929 for (i = 0, len = propertyValuePairs.length; i < len; i++) { 5930 var pair = propertyValuePairs[i].split(":"), 5931 property = pair[0].trim(), 5932 value = pair[1].trim(); 5933 ruleObj[property] = value; 5934 } 5935 rule = match[0].trim(); 5936 rule.split(",").forEach(function (_rule) { 5937 _rule = _rule.replace(/^svg/i, "").trim(); 5938 if (_rule === "") { 5939 return; 5940 } 5941 if (allRules[_rule]) { 5942 fabric.util.object.extend(allRules[_rule], ruleObj); 5943 } else { 5944 allRules[_rule] = fabric.util.object.clone(ruleObj); 5945 } 5946 }); 5947 }); 5948 } 5949 return allRules; 5950 }, 5951 5952 /** 5953 * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. 5954 * Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) 5955 * @memberOf fabric 5956 * @param {String} url 5957 * @param {Function} callback 5958 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 5959 * @param {Object} [options] Object containing options for parsing 5960 * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources 5961 */ 5962 loadSVGFromURL: function (url, callback, reviver, options) { 5963 url = url.replace(/^\n\s*/, "").trim(); 5964 url = replaceCdnUrl(url); 5965 new fabric.util.request(url, { 5966 method: "get", 5967 onComplete: onComplete, 5968 }); 5969 5970 function onComplete(r) { 5971 var xml = r.responseXML; 5972 if (!xml || !xml.documentElement) { 5973 callback && callback(null); 5974 return false; 5975 } 5976 5977 fabric.parseSVGDocument( 5978 xml.documentElement, 5979 function (results, _options, elements, allElements) { 5980 callback && callback(results, _options, elements, allElements); 5981 }, 5982 reviver, 5983 options, 5984 ); 5985 } 5986 }, 5987 5988 /** 5989 * Takes string corresponding to an SVG document, and parses it into a set of fabric objects 5990 * @memberOf fabric 5991 * @param {String} string 5992 * @param {Function} callback 5993 * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. 5994 * @param {Object} [options] Object containing options for parsing 5995 * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources 5996 */ 5997 loadSVGFromString: function (string, callback, reviver, options) { 5998 var parser = new fabric.window.DOMParser(), 5999 doc = parser.parseFromString(string.trim(), "text/xml"); 6000 fabric.parseSVGDocument( 6001 doc.documentElement, 6002 function (results, _options, elements, allElements) { 6003 callback(results, _options, elements, allElements); 6004 }, 6005 reviver, 6006 options, 6007 ); 6008 }, 6009 }); 6010})(typeof exports !== "undefined" ? exports : this); 6011 6012fabric.ElementsParser = function ( 6013 elements, 6014 callback, 6015 options, 6016 reviver, 6017 parsingOptions, 6018 doc, 6019) { 6020 this.elements = elements; 6021 this.callback = callback; 6022 this.options = options; 6023 this.reviver = reviver; 6024 this.svgUid = (options && options.svgUid) || 0; 6025 this.parsingOptions = parsingOptions; 6026 this.regexUrl = /^url\(['"]?#([^'"]+)['"]?\)/g; 6027 this.doc = doc; 6028}; 6029 6030(function (proto) { 6031 proto.parse = function () { 6032 this.instances = new Array(this.elements.length); 6033 this.numElements = this.elements.length; 6034 this.createObjects(); 6035 }; 6036 6037 proto.createObjects = function () { 6038 var _this = this; 6039 this.elements.forEach(function (element, i) { 6040 element.setAttribute("svgUid", _this.svgUid); 6041 _this.createObject(element, i); 6042 }); 6043 }; 6044 6045 proto.findTag = function (el) { 6046 return fabric[ 6047 fabric.util.string.capitalize(el.tagName.replace("svg:", "")) 6048 ]; 6049 }; 6050 6051 proto.createObject = function (el, index) { 6052 var klass = this.findTag(el); 6053 if (klass && klass.fromElement) {
vendor: 6,771 bytes, lines 6054-6309
6054 try { 6055 klass.fromElement(el, this.createCallback(index, el), this.options); 6056 } catch (err) { 6057 fabric.log(err); 6058 } 6059 } else { 6060 this.checkIfDone(); 6061 } 6062 }; 6063 6064 proto.createCallback = function (index, el) { 6065 var _this = this; 6066 return function (obj) { 6067 var _options; 6068 _this.resolveGradient(obj, el, "fill"); 6069 _this.resolveGradient(obj, el, "stroke"); 6070 if (obj instanceof fabric.Image && obj._originalElement) { 6071 _options = obj.parsePreserveAspectRatioAttribute(el); 6072 } 6073 obj._removeTransformMatrix(_options); 6074 _this.resolveClipPath(obj, el); 6075 _this.reviver && _this.reviver(el, obj); 6076 _this.instances[index] = obj; 6077 _this.checkIfDone(); 6078 }; 6079 }; 6080 6081 proto.extractPropertyDefinition = function (obj, property, storage) { 6082 var value = obj[property], 6083 regex = this.regexUrl; 6084 if (!regex.test(value)) { 6085 return; 6086 } 6087 regex.lastIndex = 0; 6088 var id = regex.exec(value)[1]; 6089 regex.lastIndex = 0; 6090 return fabric[storage][this.svgUid][id]; 6091 }; 6092 6093 proto.resolveGradient = function (obj, el, property) { 6094 var gradientDef = this.extractPropertyDefinition( 6095 obj, 6096 property, 6097 "gradientDefs", 6098 ); 6099 if (gradientDef) { 6100 var opacityAttr = el.getAttribute(property + "-opacity"); 6101 var gradient = fabric.Gradient.fromElement( 6102 gradientDef, 6103 obj, 6104 opacityAttr, 6105 this.options, 6106 ); 6107 obj.set(property, gradient); 6108 } 6109 }; 6110 6111 proto.createClipPathCallback = function (obj, container) { 6112 return function (_newObj) { 6113 _newObj._removeTransformMatrix(); 6114 _newObj.fillRule = _newObj.clipRule; 6115 container.push(_newObj); 6116 }; 6117 }; 6118 6119 proto.resolveClipPath = function (obj, usingElement) { 6120 var clipPath = this.extractPropertyDefinition(obj, "clipPath", "clipPaths"), 6121 element, 6122 klass, 6123 objTransformInv, 6124 container, 6125 gTransform, 6126 options; 6127 if (clipPath) { 6128 container = []; 6129 objTransformInv = fabric.util.invertTransform(obj.calcTransformMatrix()); 6130 // move the clipPath tag as sibling to the real element that is using it 6131 var clipPathTag = clipPath[0].parentNode; 6132 var clipPathOwner = usingElement; 6133 while ( 6134 clipPathOwner.parentNode && 6135 clipPathOwner.getAttribute("clip-path") !== obj.clipPath 6136 ) { 6137 clipPathOwner = clipPathOwner.parentNode; 6138 } 6139 clipPathOwner.parentNode.appendChild(clipPathTag); 6140 for (var i = 0; i < clipPath.length; i++) { 6141 element = clipPath[i]; 6142 klass = this.findTag(element); 6143 klass.fromElement( 6144 element, 6145 this.createClipPathCallback(obj, container), 6146 this.options, 6147 ); 6148 } 6149 if (container.length === 1) { 6150 clipPath = container[0]; 6151 } else { 6152 clipPath = new fabric.Group(container); 6153 } 6154 gTransform = fabric.util.multiplyTransformMatrices( 6155 objTransformInv, 6156 clipPath.calcTransformMatrix(), 6157 ); 6158 if (clipPath.clipPath) { 6159 this.resolveClipPath(clipPath, clipPathOwner); 6160 } 6161 var options = fabric.util.qrDecompose(gTransform); 6162 clipPath.flipX = false; 6163 clipPath.flipY = false; 6164 clipPath.set("scaleX", options.scaleX); 6165 clipPath.set("scaleY", options.scaleY); 6166 clipPath.angle = options.angle; 6167 clipPath.skewX = options.skewX; 6168 clipPath.skewY = 0; 6169 clipPath.setPositionByOrigin( 6170 { x: options.translateX, y: options.translateY }, 6171 "center", 6172 "center", 6173 ); 6174 obj.clipPath = clipPath; 6175 } else { 6176 // if clip-path does not resolve to any element, delete the property. 6177 delete obj.clipPath; 6178 } 6179 }; 6180 6181 proto.checkIfDone = function () { 6182 if (--this.numElements === 0) { 6183 this.instances = this.instances.filter(function (el) { 6184 // eslint-disable-next-line no-eq-null, eqeqeq 6185 return el != null; 6186 }); 6187 this.callback(this.instances, this.elements); 6188 } 6189 }; 6190})(fabric.ElementsParser.prototype); 6191 6192(function (global) { 6193 "use strict"; 6194 6195 /* Adaptation of work of Kevin Lindsey ([email protected]) */ 6196 6197 var fabric = global.fabric || (global.fabric = {}); 6198 6199 if (fabric.Point) { 6200 fabric.warn("fabric.Point is already defined"); 6201 return; 6202 } 6203 6204 fabric.Point = Point; 6205 6206 /** 6207 * Point class 6208 * @class fabric.Point 6209 * @memberOf fabric 6210 * @constructor 6211 * @param {Number} x 6212 * @param {Number} y 6213 * @return {fabric.Point} thisArg 6214 */ 6215 function Point(x, y) { 6216 this.x = x; 6217 this.y = y; 6218 } 6219 6220 Point.prototype = /** @lends fabric.Point.prototype */ { 6221 type: "point", 6222 6223 constructor: Point, 6224 6225 /** 6226 * Adds another point to this one and returns another one 6227 * @param {fabric.Point} that 6228 * @return {fabric.Point} new Point instance with added values 6229 */ 6230 add: function (that) { 6231 return new Point(this.x + that.x, this.y + that.y); 6232 }, 6233 6234 /** 6235 * Adds another point to this one 6236 * @param {fabric.Point} that 6237 * @return {fabric.Point} thisArg 6238 * @chainable 6239 */ 6240 addEquals: function (that) { 6241 this.x += that.x; 6242 this.y += that.y; 6243 return this; 6244 }, 6245 6246 /** 6247 * Adds value to this point and returns a new one 6248 * @param {Number} scalar 6249 * @return {fabric.Point} new Point with added value 6250 */ 6251 scalarAdd: function (scalar) { 6252 return new Point(this.x + scalar, this.y + scalar); 6253 }, 6254 6255 /** 6256 * Adds value to this point 6257 * @param {Number} scalar 6258 * @return {fabric.Point} thisArg 6259 * @chainable 6260 */ 6261 scalarAddEquals: function (scalar) { 6262 this.x += scalar; 6263 this.y += scalar; 6264 return this; 6265 }, 6266 6267 /** 6268 * Subtracts another point from this point and returns a new one 6269 * @param {fabric.Point} that 6270 * @return {fabric.Point} new Point object with subtracted values 6271 */ 6272 subtract: function (that) { 6273 return new Point(this.x - that.x, this.y - that.y); 6274 }, 6275 6276 /** 6277 * Subtracts another point from this point 6278 * @param {fabric.Point} that 6279 * @return {fabric.Point} thisArg 6280 * @chainable 6281 */ 6282 subtractEquals: function (that) { 6283 this.x -= that.x; 6284 this.y -= that.y; 6285 return this; 6286 }, 6287 6288 /** 6289 * Subtracts value from this point and returns a new one 6290 * @param {Number} scalar 6291 * @return {fabric.Point} 6292 */ 6293 scalarSubtract: function (scalar) { 6294 return new Point(this.x - scalar, this.y - scalar); 6295 }, 6296 6297 /** 6298 * Subtracts value from this point 6299 * @param {Number} scalar 6300 * @return {fabric.Point} thisArg 6301 * @chainable 6302 */ 6303 scalarSubtractEquals: function (scalar) { 6304 this.x -= scalar; 6305 this.y -= scalar; 6306 return this; 6307 }, 6308 6309 /**
6310 * Multiplies this point by a value and returns a new one 6311 * TODO: rename in scalarMultiply in 2.0 6312 * @param {Number} scalar 6313 * @return {fabric.Point} 6314 */ 6315 multiply: function (scalar) { 6316 return new Point(this.x * scalar, this.y * scalar); 6317 }, 6318 6319 /** 6320 * Multiplies this point by a value 6321 * TODO: rename in scalarMultiplyEquals in 2.0 6322 * @param {Number} scalar 6323 * @return {fabric.Point} thisArg 6324 * @chainable 6325 */ 6326 multiplyEquals: function (scalar) { 6327 this.x *= scalar; 6328 this.y *= scalar; 6329 return this; 6330 }, 6331 6332 /** 6333 * Divides this point by a value and returns a new one 6334 * TODO: rename in scalarDivide in 2.0 6335 * @param {Number} scalar 6336 * @return {fabric.Point} 6337 */ 6338 divide: function (scalar) { 6339 return new Point(this.x / scalar, this.y / scalar); 6340 }, 6341 6342 /** 6343 * Divides this point by a value 6344 * TODO: rename in scalarDivideEquals in 2.0 6345 * @param {Number} scalar 6346 * @return {fabric.Point} thisArg 6347 * @chainable 6348 */ 6349 divideEquals: function (scalar) { 6350 this.x /= scalar; 6351 this.y /= scalar; 6352 return this; 6353 }, 6354 6355 /** 6356 * Returns true if this point is equal to another one 6357 * @param {fabric.Point} that 6358 * @return {Boolean} 6359 */ 6360 eq: function (that) { 6361 return this.x === that.x && this.y === that.y; 6362 }, 6363 6364 /** 6365 * Returns true if this point is less than another one 6366 * @param {fabric.Point} that 6367 * @return {Boolean} 6368 */ 6369 lt: function (that) { 6370 return this.x < that.x && this.y < that.y; 6371 }, 6372 6373 /** 6374 * Returns true if this point is less than or equal to another one 6375 * @param {fabric.Point} that 6376 * @return {Boolean} 6377 */ 6378 lte: function (that) { 6379 return this.x <= that.x && this.y <= that.y; 6380 }, 6381 6382 /** 6383 6384 * Returns true if this point is greater another one 6385 * @param {fabric.Point} that 6386 * @return {Boolean} 6387 */ 6388 gt: function (that) { 6389 return this.x > that.x && this.y > that.y; 6390 }, 6391 6392 /** 6393 * Returns true if this point is greater than or equal to another one 6394 * @param {fabric.Point} that 6395 * @return {Boolean} 6396 */ 6397 gte: function (that) { 6398 return this.x >= that.x && this.y >= that.y; 6399 }, 6400 6401 /** 6402 * Returns new point which is the result of linear interpolation with this one and another one 6403 * @param {fabric.Point} that 6404 * @param {Number} t , position of interpolation, between 0 and 1 default 0.5 6405 * @return {fabric.Point} 6406 */ 6407 lerp: function (that, t) { 6408 if (typeof t === "undefined") { 6409 t = 0.5; 6410 } 6411 t = Math.max(Math.min(1, t), 0); 6412 return new Point( 6413 this.x + (that.x - this.x) * t, 6414 this.y + (that.y - this.y) * t, 6415 ); 6416 }, 6417 6418 /** 6419 * Returns distance from this point and another one 6420 * @param {fabric.Point} that 6421 * @return {Number} 6422 */ 6423 distanceFrom: function (that) { 6424 var dx = this.x - that.x, 6425 dy = this.y - that.y; 6426 return Math.sqrt(dx * dx + dy * dy); 6427 }, 6428 6429 /** 6430 * Returns the point between this point and another one 6431 * @param {fabric.Point} that 6432 * @return {fabric.Point} 6433 */ 6434 midPointFrom: function (that) { 6435 return this.lerp(that); 6436 }, 6437 6438 /** 6439 * Returns a new point which is the min of this and another one 6440 * @param {fabric.Point} that 6441 * @return {fabric.Point} 6442 */ 6443 min: function (that) { 6444 return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); 6445 }, 6446 6447 /** 6448 * Returns a new point which is the max of this and another one 6449 * @param {fabric.Point} that 6450 * @return {fabric.Point} 6451 */ 6452 max: function (that) { 6453 return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); 6454 }, 6455 6456 /** 6457 * Returns string representation of this point 6458 * @return {String} 6459 */ 6460 toString: function () { 6461 return this.x + "," + this.y; 6462 }, 6463 6464 /** 6465 * Sets x/y of this point 6466 * @param {Number} x 6467 * @param {Number} y 6468 * @chainable 6469 */ 6470 setXY: function (x, y) { 6471 this.x = x; 6472 this.y = y; 6473 return this; 6474 }, 6475 6476 /** 6477 * Sets x of this point 6478 * @param {Number} x 6479 * @chainable 6480 */ 6481 setX: function (x) { 6482 this.x = x; 6483 return this; 6484 }, 6485 6486 /** 6487 * Sets y of this point 6488 * @param {Number} y 6489 * @chainable 6490 */ 6491 setY: function (y) { 6492 this.y = y; 6493 return this; 6494 }, 6495 6496 /** 6497 * Sets x/y of this point from another point 6498 * @param {fabric.Point} that 6499 * @chainable 6500 */ 6501 setFromPoint: function (that) { 6502 this.x = that.x; 6503 this.y = that.y; 6504 return this; 6505 }, 6506 6507 /** 6508 * Swaps x/y of this point and another point 6509 * @param {fabric.Point} that 6510 */ 6511 swap: function (that) { 6512 var x = this.x, 6513 y = this.y; 6514 this.x = that.x; 6515 this.y = that.y; 6516 that.x = x; 6517 that.y = y; 6518 }, 6519 6520 /** 6521 * return a cloned instance of the point 6522 * @return {fabric.Point} 6523 */ 6524 clone: function () { 6525 return new Point(this.x, this.y); 6526 }, 6527 }; 6528})(typeof exports !== "undefined" ? exports : this); 6529 6530(function (global) { 6531 "use strict"; 6532 6533 /* Adaptation of work of Kevin Lindsey ([email protected]) */ 6534 var fabric = global.fabric || (global.fabric = {}); 6535 6536 if (fabric.Intersection) { 6537 fabric.warn("fabric.Intersection is already defined"); 6538 return; 6539 } 6540 6541 /** 6542 * Intersection class 6543 * @class fabric.Intersection 6544 * @memberOf fabric 6545 * @constructor 6546 */ 6547 function Intersection(status) { 6548 this.status = status; 6549 this.points = []; 6550 } 6551 6552 fabric.Intersection = Intersection; 6553 6554 fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { 6555 constructor: Intersection, 6556 6557 /** 6558 * Appends a point to intersection 6559 * @param {fabric.Point} point 6560 * @return {fabric.Intersection} thisArg 6561 * @chainable 6562 */ 6563 appendPoint: function (point) { 6564 this.points.push(point); 6565 return this; 6566 }, 6567 6568 /** 6569 * Appends points to intersection 6570 * @param {Array} points 6571 * @return {fabric.Intersection} thisArg 6572 * @chainable 6573 */ 6574 appendPoints: function (points) { 6575 this.points = this.points.concat(points); 6576 return this; 6577 }, 6578 }; 6579 6580 /** 6581 * Checks if one line intersects another 6582 * TODO: rename in intersectSegmentSegment 6583 * @static 6584 * @param {fabric.Point} a1 6585 * @param {fabric.Point} a2 6586 * @param {fabric.Point} b1 6587 * @param {fabric.Point} b2 6588 * @return {fabric.Intersection} 6589 */ 6590 fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { 6591 var result, 6592 uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), 6593 ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), 6594 uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); 6595 if (uB !== 0) { 6596 var ua = uaT / uB, 6597 ub = ubT / uB; 6598 if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { 6599 result = new Intersection("Intersection"); 6600 result.appendPoint( 6601 new fabric.Point( 6602 a1.x + ua * (a2.x - a1.x), 6603 a1.y + ua * (a2.y - a1.y), 6604 ), 6605 ); 6606 } else { 6607 result = new Intersection(); 6608 } 6609 } else { 6610 if (uaT === 0 || ubT === 0) { 6611 result = new Intersection("Coincident"); 6612 } else { 6613 result = new Intersection("Parallel"); 6614 } 6615 } 6616 return result; 6617 }; 6618 6619 /** 6620 * Checks if line intersects polygon 6621 * TODO: rename in intersectSegmentPolygon 6622 * fix detection of coincident 6623 * @static 6624 * @param {fabric.Point} a1 6625 * @param {fabric.Point} a2 6626 * @param {Array} points 6627 * @return {fabric.Intersection} 6628 */ 6629 fabric.Intersection.intersectLinePolygon = function (a1, a2, points) { 6630 var result = new Intersection(), 6631 length = points.length, 6632 b1, 6633 b2, 6634 inter, 6635 i; 6636 6637 for (i = 0; i < length; i++) { 6638 b1 = points[i]; 6639 b2 = points[(i + 1) % length]; 6640 inter = Intersection.intersectLineLine(a1, a2, b1, b2); 6641 6642 result.appendPoints(inter.points); 6643 } 6644 if (result.points.length > 0) { 6645 result.status = "Intersection"; 6646 } 6647 return result; 6648 }; 6649 6650 /** 6651 * Checks if polygon intersects another polygon 6652 * @static 6653 * @param {Array} points1 6654 * @param {Array} points2 6655 * @return {fabric.Intersection} 6656 */ 6657 fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { 6658 var result = new Intersection(), 6659 length = points1.length, 6660 i; 6661 6662 for (i = 0; i < length; i++) { 6663 var a1 = points1[i], 6664 a2 = points1[(i + 1) % length], 6665 inter = Intersection.intersectLinePolygon(a1, a2, points2); 6666 6667 result.appendPoints(inter.points); 6668 } 6669 if (result.points.length > 0) { 6670 result.status = "Intersection"; 6671 } 6672 return result; 6673 }; 6674 6675 /** 6676 * Checks if polygon intersects rectangle 6677 * @static 6678 * @param {Array} points 6679 * @param {fabric.Point} r1 6680 * @param {fabric.Point} r2 6681 * @return {fabric.Intersection} 6682 */ 6683 fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { 6684 var min = r1.min(r2), 6685 max = r1.max(r2), 6686 topRight = new fabric.Point(max.x, min.y), 6687 bottomLeft = new fabric.Point(min.x, max.y), 6688 inter1 = Intersection.intersectLinePolygon(min, topRight, points), 6689 inter2 = Intersection.intersectLinePolygon(topRight, max, points), 6690 inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), 6691 inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), 6692 result = new Intersection(); 6693 6694 result.appendPoints(inter1.points); 6695 result.appendPoints(inter2.points); 6696 result.appendPoints(inter3.points); 6697 result.appendPoints(inter4.points); 6698 6699 if (result.points.length > 0) { 6700 result.status = "Intersection"; 6701 } 6702 return result; 6703 }; 6704})(typeof exports !== "undefined" ? exports : this); 6705 6706(function (global) { 6707 "use strict"; 6708 6709 var fabric = global.fabric || (global.fabric = {}); 6710 6711 if (fabric.Color) { 6712 fabric.warn("fabric.Color is already defined."); 6713 return; 6714 } 6715 6716 /** 6717 * Color class 6718 * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; 6719 * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. 6720 * 6721 * @class fabric.Color 6722 * @param {String} color optional in hex or rgb(a) or hsl format or from known color list 6723 * @return {fabric.Color} thisArg 6724 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} 6725 */ 6726 function Color(color) { 6727 if (!color) { 6728 this.setSource([0, 0, 0, 1]); 6729 } else { 6730 this._tryParsingColor(color); 6731 } 6732 } 6733 6734 fabric.Color = Color; 6735 6736 fabric.Color.prototype = /** @lends fabric.Color.prototype */ { 6737 /** 6738 * @private 6739 * @param {String|Array} color Color value to parse 6740 */
6741 _tryParsingColor: function (color) { 6742 var source; 6743 6744 if (color in Color.colorNameMap) { 6745 color = Color.colorNameMap[color]; 6746 } 6747 6748 if (color === "transparent") { 6749 source = [255, 255, 255, 0]; 6750 } 6751 6752 if (!source) { 6753 source = Color.sourceFromHex(color); 6754 } 6755 if (!source) { 6756 source = Color.sourceFromRgb(color); 6757 } 6758 if (!source) { 6759 source = Color.sourceFromHsl(color); 6760 } 6761 if (!source) { 6762 //if color is not recognize let's make black as canvas does 6763 source = [0, 0, 0, 1]; 6764 } 6765 if (source) { 6766 this.setSource(source); 6767 } 6768 }, 6769 6770 /** 6771 * 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> 6772 * @private 6773 * @param {Number} r Red color value 6774 * @param {Number} g Green color value 6775 * @param {Number} b Blue color value 6776 * @return {Array} Hsl color 6777 */ 6778 _rgbToHsl: function (r, g, b) { 6779 r /= 255; 6780 g /= 255; 6781 b /= 255; 6782 6783 var h, 6784 s, 6785 l, 6786 max = fabric.util.array.max([r, g, b]), 6787 min = fabric.util.array.min([r, g, b]); 6788 6789 l = (max + min) / 2; 6790 6791 if (max === min) { 6792 h = s = 0; // achromatic 6793 } else { 6794 var d = max - min; 6795 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 6796 switch (max) { 6797 case r: 6798 h = (g - b) / d + (g < b ? 6 : 0); 6799 break; 6800 case g: 6801 h = (b - r) / d + 2; 6802 break; 6803 case b: 6804 h = (r - g) / d + 4; 6805 break; 6806 } 6807 h /= 6; 6808 } 6809 6810 return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)]; 6811 }, 6812 6813 /** 6814 * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) 6815 * @return {Array} 6816 */ 6817 getSource: function () { 6818 return this._source; 6819 }, 6820 6821 /** 6822 * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) 6823 * @param {Array} source 6824 */ 6825 setSource: function (source) { 6826 this._source = source; 6827 }, 6828 6829 /** 6830 * Returns color representation in RGB format 6831 * @return {String} ex: rgb(0-255,0-255,0-255) 6832 */ 6833 toRgb: function () { 6834 var source = this.getSource(); 6835 return "rgb(" + source[0] + "," + source[1] + "," + source[2] + ")"; 6836 }, 6837 6838 /** 6839 * Returns color representation in RGBA format 6840 * @return {String} ex: rgba(0-255,0-255,0-255,0-1) 6841 */ 6842 toRgba: function () { 6843 var source = this.getSource(); 6844 return ( 6845 "rgba(" + 6846 source[0] + 6847 "," + 6848 source[1] + 6849 "," + 6850 source[2] + 6851 "," + 6852 source[3] + 6853 ")" 6854 ); 6855 }, 6856 6857 /** 6858 * Returns color representation in HSL format 6859 * @return {String} ex: hsl(0-360,0%-100%,0%-100%) 6860 */ 6861 toHsl: function () { 6862 var source = this.getSource(), 6863 hsl = this._rgbToHsl(source[0], source[1], source[2]); 6864 6865 return "hsl(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%)"; 6866 }, 6867 6868 /** 6869 * Returns color representation in HSLA format 6870 * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) 6871 */ 6872 toHsla: function () { 6873 var source = this.getSource(), 6874 hsl = this._rgbToHsl(source[0], source[1], source[2]); 6875 6876 return ( 6877 "hsla(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%," + source[3] + ")" 6878 ); 6879 }, 6880 6881 /** 6882 * Returns color representation in HEX format 6883 * @return {String} ex: FF5555 6884 */ 6885 toHex: function () { 6886 var source = this.getSource(), 6887 r, 6888 g, 6889 b; 6890 6891 r = source[0].toString(16); 6892 r = r.length === 1 ? "0" + r : r; 6893 6894 g = source[1].toString(16); 6895 g = g.length === 1 ? "0" + g : g; 6896 6897 b = source[2].toString(16); 6898 b = b.length === 1 ? "0" + b : b; 6899 6900 return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); 6901 }, 6902 6903 /** 6904 * Returns color representation in HEXA format 6905 * @return {String} ex: FF5555CC 6906 */ 6907 toHexa: function () {
6908 var source = this.getSource(), 6909 a; 6910 6911 a = Math.round(source[3] * 255); 6912 a = a.toString(16); 6913 a = a.length === 1 ? "0" + a : a; 6914 6915 return this.toHex() + a.toUpperCase(); 6916 }, 6917 6918 /** 6919 * Gets value of alpha channel for this color 6920 * @return {Number} 0-1 6921 */ 6922 getAlpha: function () { 6923 return this.getSource()[3]; 6924 }, 6925 6926 /** 6927 * Sets value of alpha channel for this color 6928 * @param {Number} alpha Alpha value 0-1 6929 * @return {fabric.Color} thisArg 6930 */ 6931 setAlpha: function (alpha) { 6932 var source = this.getSource(); 6933 source[3] = alpha; 6934 this.setSource(source); 6935 return this; 6936 }, 6937 6938 /** 6939 * Transforms color to its grayscale representation 6940 * @return {fabric.Color} thisArg 6941 */ 6942 toGrayscale: function () { 6943 var source = this.getSource(), 6944 average = parseInt( 6945 (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 6946 10, 6947 ), 6948 currentAlpha = source[3]; 6949 this.setSource([average, average, average, currentAlpha]); 6950 return this; 6951 }, 6952 6953 /** 6954 * Transforms color to its black and white representation 6955 * @param {Number} threshold 6956 * @return {fabric.Color} thisArg 6957 */ 6958 toBlackWhite: function (threshold) { 6959 var source = this.getSource(), 6960 average = ( 6961 source[0] * 0.3 + 6962 source[1] * 0.59 + 6963 source[2] * 0.11 6964 ).toFixed(0), 6965 currentAlpha = source[3]; 6966 6967 threshold = threshold || 127; 6968 6969 average = Number(average) < Number(threshold) ? 0 : 255; 6970 this.setSource([average, average, average, currentAlpha]); 6971 return this; 6972 }, 6973 6974 /** 6975 * Overlays color with another color 6976 * @param {String|fabric.Color} otherColor 6977 * @return {fabric.Color} thisArg 6978 */ 6979 overlayWith: function (otherColor) { 6980 if (!(otherColor instanceof Color)) { 6981 otherColor = new Color(otherColor); 6982 } 6983 6984 var result = [], 6985 alpha = this.getAlpha(), 6986 otherAlpha = 0.5, 6987 source = this.getSource(), 6988 otherSource = otherColor.getSource(), 6989 i; 6990 6991 for (i = 0; i < 3; i++) { 6992 result.push( 6993 Math.round( 6994 source[i] * (1 - otherAlpha) + otherSource[i] * otherAlpha, 6995 ), 6996 ); 6997 } 6998 6999 result[3] = alpha; 7000 this.setSource(result); 7001 return this; 7002 }, 7003 }; 7004 7005 /** 7006 * 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)) 7007 * @static 7008 * @field 7009 * @memberOf fabric.Color 7010 */ 7011 // eslint-disable-next-line max-len 7012 fabric.Color.reRGBa = 7013 /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*((?:\d*\.?\d+)?)\s*)?\)$/i; 7014 7015 /** 7016 * 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 )) 7017 * @static 7018 * @field 7019 * @memberOf fabric.Color 7020 */ 7021 fabric.Color.reHSLa = 7022 /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/i; 7023 7024 /** 7025 * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) 7026 * @static 7027 * @field 7028 * @memberOf fabric.Color 7029 */ 7030 fabric.Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i; 7031 7032 /** 7033 * Map of the 148 color names with HEX code 7034 * @static 7035 * @field 7036 * @memberOf fabric.Color 7037 * @see: https://www.w3.org/TR/css3-color/#svg-color 7038 */ 7039 fabric.Color.colorNameMap = { 7040 aliceblue: "#F0F8FF", 7041 antiquewhite: "#FAEBD7", 7042 aqua: "#00FFFF", 7043 aquamarine: "#7FFFD4", 7044 azure: "#F0FFFF", 7045 beige: "#F5F5DC", 7046 bisque: "#FFE4C4", 7047 black: "#000000", 7048 blanchedalmond: "#FFEBCD", 7049 blue: "#0000FF", 7050 blueviolet: "#8A2BE2", 7051 brown: "#A52A2A", 7052 burlywood: "#DEB887", 7053 cadetblue: "#5F9EA0", 7054 chartreuse: "#7FFF00", 7055 chocolate: "#D2691E", 7056 coral: "#FF7F50", 7057 cornflowerblue: "#6495ED", 7058 cornsilk: "#FFF8DC", 7059 crimson: "#DC143C", 7060 cyan: "#00FFFF", 7061 darkblue: "#00008B", 7062 darkcyan: "#008B8B", 7063 darkgoldenrod: "#B8860B", 7064 darkgray: "#A9A9A9", 7065 darkgrey: "#A9A9A9", 7066 darkgreen: "#006400", 7067 darkkhaki: "#BDB76B", 7068 darkmagenta: "#8B008B", 7069 darkolivegreen: "#556B2F", 7070 darkorange: "#FF8C00", 7071 darkorchid: "#9932CC", 7072 darkred: "#8B0000", 7073 darksalmon: "#E9967A", 7074 darkseagreen: "#8FBC8F", 7075 darkslateblue: "#483D8B", 7076 darkslategray: "#2F4F4F", 7077 darkslategrey: "#2F4F4F", 7078 darkturquoise: "#00CED1", 7079 darkviolet: "#9400D3", 7080 deeppink: "#FF1493", 7081 deepskyblue: "#00BFFF", 7082 dimgray: "#696969", 7083 dimgrey: "#696969", 7084 dodgerblue: "#1E90FF", 7085 firebrick: "#B22222", 7086 floralwhite: "#FFFAF0", 7087 forestgreen: "#228B22",
7088 fuchsia: "#FF00FF", 7089 gainsboro: "#DCDCDC", 7090 ghostwhite: "#F8F8FF", 7091 gold: "#FFD700", 7092 goldenrod: "#DAA520", 7093 gray: "#808080", 7094 grey: "#808080", 7095 green: "#008000", 7096 greenyellow: "#ADFF2F", 7097 honeydew: "#F0FFF0", 7098 hotpink: "#FF69B4", 7099 indianred: "#CD5C5C", 7100 indigo: "#4B0082", 7101 ivory: "#FFFFF0", 7102 khaki: "#F0E68C", 7103 lavender: "#E6E6FA", 7104 lavenderblush: "#FFF0F5", 7105 lawngreen: "#7CFC00", 7106 lemonchiffon: "#FFFACD", 7107 lightblue: "#ADD8E6", 7108 lightcoral: "#F08080", 7109 lightcyan: "#E0FFFF", 7110 lightgoldenrodyellow: "#FAFAD2", 7111 lightgray: "#D3D3D3", 7112 lightgrey: "#D3D3D3", 7113 lightgreen: "#90EE90", 7114 lightpink: "#FFB6C1", 7115 lightsalmon: "#FFA07A", 7116 lightseagreen: "#20B2AA", 7117 lightskyblue: "#87CEFA", 7118 lightslategray: "#778899", 7119 lightslategrey: "#778899", 7120 lightsteelblue: "#B0C4DE", 7121 lightyellow: "#FFFFE0", 7122 lime: "#00FF00", 7123 limegreen: "#32CD32", 7124 linen: "#FAF0E6", 7125 magenta: "#FF00FF", 7126 maroon: "#800000", 7127 mediumaquamarine: "#66CDAA", 7128 mediumblue: "#0000CD", 7129 mediumorchid: "#BA55D3", 7130 mediumpurple: "#9370DB", 7131 mediumseagreen: "#3CB371", 7132 mediumslateblue: "#7B68EE", 7133 mediumspringgreen: "#00FA9A", 7134 mediumturquoise: "#48D1CC", 7135 mediumvioletred: "#C71585", 7136 midnightblue: "#191970", 7137 mintcream: "#F5FFFA", 7138 mistyrose: "#FFE4E1", 7139 moccasin: "#FFE4B5", 7140 navajowhite: "#FFDEAD", 7141 navy: "#000080", 7142 oldlace: "#FDF5E6", 7143 olive: "#808000", 7144 olivedrab: "#6B8E23", 7145 orange: "#FFA500", 7146 orangered: "#FF4500", 7147 orchid: "#DA70D6", 7148 palegoldenrod: "#EEE8AA", 7149 palegreen: "#98FB98", 7150 paleturquoise: "#AFEEEE", 7151 palevioletred: "#DB7093", 7152 papayawhip: "#FFEFD5", 7153 peachpuff: "#FFDAB9", 7154 peru: "#CD853F", 7155 pink: "#FFC0CB", 7156 plum: "#DDA0DD", 7157 powderblue: "#B0E0E6", 7158 purple: "#800080", 7159 rebeccapurple: "#663399", 7160 red: "#FF0000", 7161 rosybrown: "#BC8F8F", 7162 royalblue: "#4169E1", 7163 saddlebrown: "#8B4513", 7164 salmon: "#FA8072", 7165 sandybrown: "#F4A460", 7166 seagreen: "#2E8B57", 7167 seashell: "#FFF5EE", 7168 sienna: "#A0522D", 7169 silver: "#C0C0C0", 7170 skyblue: "#87CEEB", 7171 slateblue: "#6A5ACD", 7172 slategray: "#708090", 7173 slategrey: "#708090", 7174 snow: "#FFFAFA", 7175 springgreen: "#00FF7F", 7176 steelblue: "#4682B4", 7177 tan: "#D2B48C", 7178 teal: "#008080", 7179 thistle: "#D8BFD8", 7180 tomato: "#FF6347", 7181 turquoise: "#40E0D0", 7182 violet: "#EE82EE", 7183 wheat: "#F5DEB3", 7184 white: "#FFFFFF", 7185 whitesmoke: "#F5F5F5", 7186 yellow: "#FFFF00", 7187 yellowgreen: "#9ACD32", 7188 }; 7189 7190 /** 7191 * @private 7192 * @param {Number} p 7193 * @param {Number} q 7194 * @param {Number} t 7195 * @return {Number} 7196 */ 7197 function hue2rgb(p, q, t) { 7198 if (t < 0) { 7199 t += 1; 7200 } 7201 if (t > 1) { 7202 t -= 1; 7203 } 7204 if (t < 1 / 6) { 7205 return p + (q - p) * 6 * t; 7206 } 7207 if (t < 1 / 2) { 7208 return q; 7209 } 7210 if (t < 2 / 3) { 7211 return p + (q - p) * (2 / 3 - t) * 6; 7212 } 7213 return p; 7214 } 7215 7216 /** 7217 * Returns new color object, when given a color in RGB format 7218 * @memberOf fabric.Color 7219 * @param {String} color Color value ex: rgb(0-255,0-255,0-255) 7220 * @return {fabric.Color} 7221 */ 7222 fabric.Color.fromRgb = function (color) { 7223 return Color.fromSource(Color.sourceFromRgb(color)); 7224 }; 7225 7226 /** 7227 * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format 7228 * @memberOf fabric.Color 7229 * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) 7230 * @return {Array} source 7231 */ 7232 fabric.Color.sourceFromRgb = function (color) { 7233 var match = color.match(Color.reRGBa); 7234 if (match) { 7235 var r = 7236 (parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1)) * 7237 (/%$/.test(match[1]) ? 255 : 1), 7238 g = 7239 (parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1)) * 7240 (/%$/.test(match[2]) ? 255 : 1), 7241 b = 7242 (parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1)) * 7243 (/%$/.test(match[3]) ? 255 : 1); 7244 7245 return [ 7246 parseInt(r, 10), 7247 parseInt(g, 10), 7248 parseInt(b, 10), 7249 match[4] ? parseFloat(match[4]) : 1, 7250 ]; 7251 } 7252 }; 7253 7254 /** 7255 * Returns new color object, when given a color in RGBA format 7256 * @static 7257 * @function 7258 * @memberOf fabric.Color 7259 * @param {String} color 7260 * @return {fabric.Color} 7261 */ 7262 fabric.Color.fromRgba = Color.fromRgb; 7263 7264 /** 7265 * Returns new color object, when given a color in HSL format 7266 * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) 7267 * @memberOf fabric.Color 7268 * @return {fabric.Color} 7269 */ 7270 fabric.Color.fromHsl = function (color) { 7271 return Color.fromSource(Color.sourceFromHsl(color)); 7272 }; 7273 7274 /** 7275 * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. 7276 * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-
7276and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a> 7277 * @memberOf fabric.Color 7278 * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) 7279 * @return {Array} source 7280 * @see http://http://www.w3.org/TR/css3-color/#hsl-color 7281 */ 7282 fabric.Color.sourceFromHsl = function (color) { 7283 var match = color.match(Color.reHSLa); 7284 if (!match) { 7285 return; 7286 } 7287 7288 var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, 7289 s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), 7290 l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), 7291 r, 7292 g, 7293 b; 7294 7295 if (s === 0) { 7296 r = g = b = l; 7297 } else { 7298 var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, 7299 p = l * 2 - q; 7300 7301 r = hue2rgb(p, q, h + 1 / 3); 7302 g = hue2rgb(p, q, h); 7303 b = hue2rgb(p, q, h - 1 / 3); 7304 } 7305 7306 return [ 7307 Math.round(r * 255), 7308 Math.round(g * 255), 7309 Math.round(b * 255), 7310 match[4] ? parseFloat(match[4]) : 1, 7311 ]; 7312 }; 7313 7314 /** 7315 * Returns new color object, when given a color in HSLA format 7316 * @static 7317 * @function 7318 * @memberOf fabric.Color 7319 * @param {String} color 7320 * @return {fabric.Color} 7321 */ 7322 fabric.Color.fromHsla = Color.fromHsl; 7323 7324 /** 7325 * Returns new color object, when given a color in HEX format 7326 * @static 7327 * @memberOf fabric.Color 7328 * @param {String} color Color value ex: FF5555 7329 * @return {fabric.Color} 7330 */ 7331 fabric.Color.fromHex = function (color) { 7332 return Color.fromSource(Color.sourceFromHex(color)); 7333 }; 7334 7335 /** 7336 * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HEX format 7337 * @static 7338 * @memberOf fabric.Color 7339 * @param {String} color ex: FF5555 or FF5544CC (RGBa) 7340 * @return {Array} source 7341 */ 7342 fabric.Color.sourceFromHex = function (color) { 7343 if (color.match(Color.reHex)) { 7344 var value = color.slice(color.indexOf("#") + 1), 7345 isShortNotation = value.length === 3 || value.length === 4, 7346 isRGBa = value.length === 8 || value.length === 4, 7347 r = isShortNotation 7348 ? value.charAt(0) + value.charAt(0) 7349 : value.substring(0, 2), 7350 g = isShortNotation 7351 ? value.charAt(1) + value.charAt(1) 7352 : value.substring(2, 4), 7353 b = isShortNotation 7354 ? value.charAt(2) + value.charAt(2) 7355 : value.substring(4, 6), 7356 a = isRGBa 7357 ? isShortNotation 7358 ? value.charAt(3) + value.charAt(3) 7359 : value.substring(6, 8) 7360 : "FF"; 7361 7362 return [ 7363 parseInt(r, 16), 7364 parseInt(g, 16), 7365 parseInt(b, 16),
7366 parseFloat((parseInt(a, 16) / 255).toFixed(2)), 7367 ]; 7368 } 7369 }; 7370 7371 /** 7372 * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) 7373 * @static 7374 * @memberOf fabric.Color 7375 * @param {Array} source 7376 * @return {fabric.Color} 7377 */ 7378 fabric.Color.fromSource = function (source) { 7379 var oColor = new Color(); 7380 oColor.setSource(source); 7381 return oColor; 7382 }; 7383})(typeof exports !== "undefined" ? exports : this); 7384 7385(function (global) { 7386 "use strict"; 7387 7388 var fabric = global.fabric || (global.fabric = {}), 7389 scaleMap = ["e", "se", "s", "sw", "w", "nw", "n", "ne", "e"], 7390 skewMap = ["ns", "nesw", "ew", "nwse"], 7391 controls = {}, 7392 LEFT = "left", 7393 TOP = "top", 7394 RIGHT = "right", 7395 BOTTOM = "bottom", 7396 CENTER = "center", 7397 opposite = { 7398 top: BOTTOM, 7399 bottom: TOP, 7400 left: RIGHT, 7401 right: LEFT, 7402 center: CENTER, 7403 }, 7404 radiansToDegrees = fabric.util.radiansToDegrees, 7405 sign = 7406 Math.sign || 7407 function (x) { 7408 return (x > 0) - (x < 0) || +x; 7409 }; 7410 7411 /** 7412 * Combine control position and object angle to find the control direction compared 7413 * to the object center. 7414 * @param {fabric.Object} fabricObject the fabric object for which we are rendering controls 7415 * @param {fabric.Control} control the control class 7416 * @return {Number} 0 - 7 a quadrant number 7417 */ 7418 function findCornerQuadrant(fabricObject, control) { 7419 var cornerAngle = 7420 fabricObject.angle + 7421 radiansToDegrees(Math.atan2(control.y, control.x)) + 7422 360; 7423 return Math.round((cornerAngle % 360) / 45); 7424 } 7425 7426 function fireEvent(eventName, options) { 7427 var target = options.transform.target, 7428 canvas = target.canvas, 7429 canvasOptions = fabric.util.object.clone(options); 7430 canvasOptions.target = target; 7431 canvas && canvas.fire("object:" + eventName, canvasOptions); 7432 target.fire(eventName, options); 7433 } 7434 7435 /** 7436 * Inspect event and fabricObject properties to understand if the scaling action 7437 * @param {Event} eventData from the user action 7438 * @param {fabric.Object} fabricObject the fabric object about to scale 7439 * @return {Boolean} true if scale is proportional 7440 */ 7441 function scaleIsProportional(eventData, fabricObject) { 7442 var canvas = fabricObject.canvas, 7443 uniScaleKey = canvas.uniScaleKey, 7444 uniformIsToggled = eventData[uniScaleKey]; 7445 return ( 7446 (canvas.uniformScaling && !uniformIsToggled) || 7447 (!canvas.uniformScaling && uniformIsToggled) 7448 ); 7449 } 7450 7451 /** 7452 * Checks if transform is centered 7453 * @param {Object} transform transform data 7454 * @return {Boolean} true if transform is centered 7455 */ 7456 function isTransformCentered(transform) { 7457 return transform.originX === CENTER && transform.originY === CENTER; 7458 } 7459 7460 /** 7461 * Inspect fabricObject to understand if the current scaling action is allowed 7462 * @param {fabric.Object} fabricObject the fabric object about to scale 7463 * @param {String} by 'x' or 'y' or '' 7464 * @param {Boolean} scaleProportionally true if we are trying to scale proportionally 7465 * @return {Boolean} true if scaling is not allowed at current conditions 7466 */ 7467 function scalingIsForbidden(fabricObject, by, scaleProportionally) { 7468 var lockX = fabricObject.lockScalingX, 7469 lockY = fabricObject.lockScalingY; 7470 if (lockX && lockY) { 7471 return true; 7472 } 7473 if (!by && (lockX || lockY) && scaleProportionally) { 7474 return true; 7475 } 7476 if (lockX && by === "x") { 7477 return true; 7478 } 7479 if (lockY && by === "y") { 7480 return true; 7481 } 7482 return false; 7483 } 7484 7485 /** 7486 * return the correct cursor style for the scale action 7487 * @param {Event} eventData the javascript event that is causing the scale 7488 * @param {fabric.Control} control the control that is interested in the action 7489 * @param {fabric.Object} fabricObject the fabric object that is interested in the action 7490 * @return {String} a valid css string for the cursor 7491 */ 7492 function scaleCursorStyleHandler(eventData, control, fabricObject) { 7493 var notAllowed = "not-allowed", 7494 scaleProportionally = scaleIsProportional(eventData, fabricObject), 7495 by = ""; 7496 if (control.x !== 0 && control.y === 0) { 7497 by = "x"; 7498 } else if (control.x === 0 && control.y !== 0) { 7499 by = "y"; 7500 } 7501 if (scalingIsForbidden(fabricObject, by, scaleProportionally)) { 7502 return notAllowed; 7503 } 7504 var n = findCornerQuadrant(fabricObject, control); 7505 return scaleMap[n] + "-resize"; 7506 } 7507 7508 /** 7509 * return the correct cursor style for the skew action 7510 * @param {Event} eventData the javascript event that is causing the scale 7511 * @param {fabric.Control} control the control that is interested in the action 7512 * @param {fabric.Object} fabricObject the fabric object that is interested in the action 7513 * @return {String} a valid css string for the cursor 7514 */ 7515 function skewCursorStyleHandler(eventData, control, fabricObject) { 7516 var notAllowed = "not-allowed"; 7517 if (control.x !== 0 && fabricObject.lockSkewingY) { 7518 return notAllowed; 7519 } 7520 if (control.y !== 0 && fabricObject.lockSkewingX) { 7521 return notAllowed; 7522 } 7523 var n = findCornerQuadrant(fabricObject, control) % 4; 7524 return skewMap[n] + "-resize"; 7525 } 7526 7527 /** 7528 * Combine skew and scale style handlers to cover fabric standard use case 7529 * @param {Event} eventData the javascript event that is causing the scale 7530 * @param {fabric.Control} control the control that is interested in the action 7531 * @param {fabric.Object} fabricObject the fabric object that is interested in the action 7532 * @return {String} a valid css string for the cursor 7533 */ 7534 function scaleSkewCursorStyleHandler(eventData, control, fabricObject) { 7535 if (eventData[fabricObject.canvas.altActionKey]) { 7536 return controls.skewCursorStyleHandler(eventData, control, fabricObject); 7537 } 7538 return controls.scaleCursorStyleHandler(eventData, control, fabricObject); 7539 } 7540 7541 /** 7542 * Inspect event, control and fabricObject to return the correct action name 7543 * @param {Event} eventData the javascript event that is causing the scale 7544 * @param {fabric.Control} control the control that is interested in the action 7545 * @param {fabric.Object} fabricObject the fabric object that is interested in the action 7546 * @return {String} an action name 7547 */ 7548 function scaleOrSkewActionName(eventData, control, fabricObject) { 7549 var isAlternative = eventData[fabricObject.canvas.altActionKey]; 7550 if (control.x === 0) { 7551 // then is scaleY or skewX 7552 return isAlternative ? "skewX" : "scaleY"; 7553 } 7554 if (control.y === 0) { 7555 // then is scaleY or skewX 7556 return isAlternative ? "skewY" : "scaleX"; 7557 } 7558 } 7559 7560 /** 7561 * Find the correct style for the control that is used for rotation. 7562 * this function is very simple and it just take care of not-allowed or standard cursor 7563 * @param {Event} eventData the javascript event that is causing the scale 7564 * @param {fabric.Control} control the control that is interested in the action 7565 * @param {fabric.Object} fabricObject the fabric object that is interested in the action 7566 * @return {String} a valid css string for the cursor 7567 */ 7568 function rotationStyleHandler(eventData, control, fabricObject) { 7569 if (fabricObject.lockRotation) { 7570 return "not-allowed"; 7571 }
7572 return control.cursorStyle; 7573 } 7574 7575 function commonEventInfo(eventData, transform, x, y) { 7576 return { 7577 e: eventData, 7578 transform: transform, 7579 pointer: { 7580 x: x, 7581 y: y, 7582 }, 7583 }; 7584 } 7585 7586 /** 7587 * Wrap an action handler with saving/restoring object position on the transform. 7588 * this is the code that permits to objects to keep their position while transforming. 7589 * @param {Function} actionHandler the function to wrap 7590 * @return {Function} a function with an action handler signature 7591 */ 7592 function wrapWithFixedAnchor(actionHandler) { 7593 return function (eventData, transform, x, y) { 7594 var target = transform.target, 7595 centerPoint = target.getCenterPoint(), 7596 constraint = target.translateToOriginPoint( 7597 centerPoint, 7598 transform.originX, 7599 transform.originY, 7600 ), 7601 actionPerformed = actionHandler(eventData, transform, x, y); 7602 target.setPositionByOrigin( 7603 constraint, 7604 transform.originX, 7605 transform.originY, 7606 ); 7607 return actionPerformed; 7608 }; 7609 } 7610 7611 /** 7612 * Wrap an action handler with firing an event if the action is performed 7613 * @param {Function} actionHandler the function to wrap 7614 * @return {Function} a function with an action handler signature 7615 */ 7616 function wrapWithFireEvent(eventName, actionHandler) { 7617 return function (eventData, transform, x, y) { 7618 var actionPerformed = actionHandler(eventData, transform, x, y); 7619 if (actionPerformed) { 7620 fireEvent(eventName, commonEventInfo(eventData, transform, x, y)); 7621 } 7622 return actionPerformed; 7623 }; 7624 } 7625 7626 /** 7627 * Transforms a point described by x and y in a distance from the top left corner of the object 7628 * bounding box. 7629 * @param {Object} transform 7630 * @param {String} originX 7631 * @param {String} originY 7632 * @param {number} x 7633 * @param {number} y 7634 * @return {Fabric.Point} the normalized point 7635 */ 7636 function getLocalPoint(transform, originX, originY, x, y) { 7637 var target = transform.target, 7638 control = target.controls[transform.corner], 7639 zoom = target.canvas.getZoom(), 7640 padding = target.padding / zoom, 7641 localPoint = target.toLocalPoint( 7642 new fabric.Point(x, y), 7643 originX, 7644 originY, 7645 ); 7646 if (localPoint.x >= padding) { 7647 localPoint.x -= padding; 7648 } 7649 if (localPoint.x <= -padding) { 7650 localPoint.x += padding; 7651 } 7652 if (localPoint.y >= padding) { 7653 localPoint.y -= padding; 7654 } 7655 if (localPoint.y <= padding) { 7656 localPoint.y += padding; 7657 } 7658 localPoint.x -= control.offsetX; 7659 localPoint.y -= control.offsetY; 7660 return localPoint; 7661 } 7662 7663 /** 7664 * Detect if the fabric object is flipped on one side. 7665 * @param {fabric.Object} target 7666 * @return {Boolean} true if one flip, but not two. 7667 */ 7668 function targetHasOneFlip(target) { 7669 return target.flipX !== target.flipY; 7670 } 7671 7672 /** 7673 * Utility function to compensate the scale factor when skew is applied on both axes 7674 * @private 7675 */ 7676 function compensateScaleForSkew( 7677 target, 7678 oppositeSkew, 7679 scaleToCompensate, 7680 axis, 7681 reference, 7682 ) { 7683 if (target[oppositeSkew] !== 0) { 7684 var newDim = target._getTransformedDimensions()[axis]; 7685 var newValue = (reference / newDim) * target[scaleToCompensate]; 7686 target.set(scaleToCompensate, newValue); 7687 } 7688 } 7689 7690 /** 7691 * Action handler for skewing on the X axis 7692 * @private 7693 */ 7694 function skewObjectX(eventData, transform, x, y) { 7695 var target = transform.target, 7696 // find how big the object would be, if there was no skewX. takes in account scaling 7697 dimNoSkew = target._getTransformedDimensions(0, target.skewY), 7698 localPoint = getLocalPoint( 7699 transform, 7700 transform.originX, 7701 transform.originY, 7702 x, 7703 y, 7704 ), 7705 // the mouse is in the center of the object, and we want it to stay there. 7706 // so the object will grow twice as much as the mouse. 7707 // this makes the skew growth to localPoint * 2 - dimNoSkew. 7708 totalSkewSize = Math.abs(localPoint.x * 2) - dimNoSkew.x, 7709 currentSkew = target.skewX, 7710 newSkew; 7711 if (totalSkewSize < 2) { 7712 // let's make it easy to go back to position 0. 7713 newSkew = 0; 7714 } else { 7715 newSkew = radiansToDegrees( 7716 Math.atan2(totalSkewSize / target.scaleX, dimNoSkew.y / target.scaleY), 7717 ); 7718 // now we have to find the sign of the skew. 7719 // it mostly depend on the origin of transformation. 7720 if (transform.originX === LEFT && transform.originY === BOTTOM) { 7721 newSkew = -newSkew; 7722 } 7723 if (transform.originX === RIGHT && transform.originY === TOP) { 7724 newSkew = -newSkew; 7725 } 7726 if (targetHasOneFlip(target)) { 7727 newSkew = -newSkew; 7728 } 7729 } 7730 var hasSkewed = currentSkew !== newSkew; 7731 if (hasSkewed) { 7732 var dimBeforeSkewing = target._getTransformedDimensions().y; 7733 target.set("skewX", newSkew); 7734 compensateScaleForSkew(target, "skewY", "scaleY", "y", dimBeforeSkewing); 7735 } 7736 return hasSkewed; 7737 } 7738 7739 /** 7740 * Action handler for skewing on the Y axis 7741 * @private 7742 */ 7743 function skewObjectY(eventData, transform, x, y) { 7744 var target = transform.target, 7745 // find how big the object would be, if there was no skewX. takes in account scaling 7746 dimNoSkew = target._getTransformedDimensions(target.skewX, 0), 7747 localPoint = getLocalPoint( 7748 transform, 7749 transform.originX, 7750 transform.originY, 7751 x, 7752 y, 7753 ), 7754 // the mouse is in the center of the object, and we want it to stay there. 7755 // so the object will grow twice as much as the mouse. 7756 // this makes the skew growth to localPoint * 2 - dimNoSkew. 7757 totalSkewSize = Math.abs(localPoint.y * 2) - dimNoSkew.y, 7758 currentSkew = target.skewY, 7759 newSkew; 7760 if (totalSkewSize < 2) { 7761 // let's make it easy to go back to position 0. 7762 newSkew = 0; 7763 } else { 7764 newSkew = radiansToDegrees( 7765 Math.atan2(totalSkewSize / target.scaleY, dimNoSkew.x / target.scaleX), 7766 ); 7767 // now we have to find the sign of the skew. 7768 // it mostly depend on the origin of transformation. 7769 if (transform.originX === LEFT && transform.originY === BOTTOM) { 7770 newSkew = -newSkew; 7771 } 7772 if (transform.originX === RIGHT && transform.originY === TOP) { 7773 newSkew = -newSkew; 7774 } 7775 if (targetHasOneFlip(target)) { 7776 newSkew = -newSkew; 7777 } 7778 } 7779 var hasSkewed = currentSkew !== newSkew; 7780 if (hasSkewed) { 7781 var dimBeforeSkewing = target._getTransformedDimensions().x; 7782 target.set("skewY", newSkew); 7783 compensateScaleForSkew(target, "skewX", "scaleX", "x", dimBeforeSkewing); 7784 } 7785 return hasSkewed; 7786 } 7787 7788 /** 7789 * Wrapped Action handler for skewing on the Y axis, takes care of the 7790 * skew direction and determine the correct transform origin for the anch
7790or point 7791 * @param {Event} eventData javascript event that is doing the transform 7792 * @param {Object} transform javascript object containing a series of information around the current transform 7793 * @param {number} x current mouse x position, canvas normalized 7794 * @param {number} y current mouse y position, canvas normalized 7795 * @return {Boolean} true if some change happened 7796 */ 7797 function skewHandlerX(eventData, transform, x, y) { 7798 // step1 figure out and change transform origin. 7799 // if skewX > 0 and originY bottom we anchor on right 7800 // if skewX > 0 and originY top we anchor on left 7801 // if skewX < 0 and originY bottom we anchor on left 7802 // if skewX < 0 and originY top we anchor on right 7803 // if skewX is 0, we look for mouse position to understand where are we going. 7804 var target = transform.target, 7805 currentSkew = target.skewX, 7806 originX, 7807 originY = transform.originY; 7808 if (target.lockSkewingX) { 7809 return false; 7810 } 7811 if (currentSkew === 0) { 7812 var localPointFromCenter = getLocalPoint(transform, CENTER, CENTER, x, y); 7813 if (localPointFromCenter.x > 0) { 7814 // we are pulling right, anchor left; 7815 originX = LEFT; 7816 } else { 7817 // we are pulling right, anchor right 7818 originX = RIGHT; 7819 } 7820 } else { 7821 if (currentSkew > 0) { 7822 originX = originY === TOP ? LEFT : RIGHT; 7823 } 7824 if (currentSkew < 0) { 7825 originX = originY === TOP ? RIGHT : LEFT; 7826 } 7827 // is the object flipped on one side only? swap the origin. 7828 if (targetHasOneFlip(target)) { 7829 originX = originX === LEFT ? RIGHT : LEFT; 7830 } 7831 } 7832 7833 // once we have the origin, we find the anchor point 7834 transform.originX = originX; 7835 var finalHandler = wrapWithFireEvent( 7836 "skewing", 7837 wrapWithFixedAnchor(skewObjectX), 7838 ); 7839 return finalHandler(eventData, transform, x, y); 7840 } 7841 7842 /** 7843 * Wrapped Action handler for skewing on the Y axis, takes care of the 7844 * skew direction and determine the correct transform origin for the anchor point 7845 * @param {Event} eventData javascript event that is doing the transform 7846 * @param {Object} transform javascript object containing a series of information around the current transform 7847 * @param {number} x current mouse x position, canvas normalized 7848 * @param {number} y current mouse y position, canvas normalized 7849 * @return {Boolean} true if some change happened 7850 */ 7851 function skewHandlerY(eventData, transform, x, y) { 7852 // step1 figure out and change transform origin. 7853 // if skewY > 0 and originX left we anchor on top 7854 // if skewY > 0 and originX right we anchor on bottom 7855 // if skewY < 0 and originX left we anchor on bottom 7856 // if skewY < 0 and originX right we anchor on top 7857 // if skewY is 0, we look for mouse position to understand where are we going. 7858 var target = transform.target, 7859 currentSkew = target.skewY, 7860 originY, 7861 originX = transform.originX; 7862 if (target.lockSkewingY) { 7863 return false; 7864 } 7865 if (currentSkew === 0) { 7866 var localPointFromCenter = getLocalPoint(transform, CENTER, CENTER, x, y); 7867 if (localPointFromCenter.y > 0) { 7868 // we are pulling down, anchor up; 7869 originY = TOP; 7870 } else { 7871 // we are pulling up, anchor down 7872 originY = BOTTOM; 7873 } 7874 } else { 7875 if (currentSkew > 0) { 7876 originY = originX === LEFT ? TOP : BOTTOM; 7877 } 7878 if (currentSkew < 0) { 7879 originY = originX === LEFT ? BOTTOM : TOP; 7880 } 7881 // is the object flipped on one side only? swap the origin. 7882 if (targetHasOneFlip(target)) { 7883 originY = originY === TOP ? BOTTOM : TOP; 7884 } 7885 } 7886 7887 // once we have the origin, we find the anchor point 7888 transform.originY = originY; 7889 var finalHandler = wrapWithFireEvent( 7890 "skewing", 7891 wrapWithFixedAnchor(skewObjectY), 7892 ); 7893 return finalHandler(eventData, transform, x, y); 7894 } 7895 7896 /** 7897 * Action handler for rotation and snapping, without anchor point. 7898 * Needs to be wrapped with `wrapWithFixedAnchor` to be effective 7899 * @param {Event} eventData javascript event that is doing the transform 7900 * @param {Object}
7900 transform javascript object containing a series of information around the current transform 7901 * @param {number} x current mouse x position, canvas normalized 7902 * @param {number} y current mouse y position, canvas normalized 7903 * @return {Boolean} true if some change happened 7904 * @private 7905 */ 7906 function rotationWithSnapping(eventData, transform, x, y) { 7907 var t = transform, 7908 target = t.target, 7909 pivotPoint = target.translateToOriginPoint( 7910 target.getCenterPoint(), 7911 t.originX, 7912 t.originY, 7913 ); 7914 7915 if (target.lockRotation) { 7916 return false; 7917 } 7918 7919 var lastAngle = Math.atan2(t.ey - pivotPoint.y, t.ex - pivotPoint.x), 7920 curAngle = Math.atan2(y - pivotPoint.y, x - pivotPoint.x), 7921 angle = radiansToDegrees(curAngle - lastAngle + t.theta), 7922 hasRotated = true; 7923 7924 if (target.snapAngle > 0) { 7925 var snapAngle = target.snapAngle, 7926 snapThreshold = target.snapThreshold || snapAngle, 7927 rightAngleLocked = Math.ceil(angle / snapAngle) * snapAngle, 7928 leftAngleLocked = Math.floor(angle / snapAngle) * snapAngle; 7929 7930 if (Math.abs(angle - leftAngleLocked) < snapThreshold) { 7931 angle = leftAngleLocked; 7932 } else if (Math.abs(angle - rightAngleLocked) < snapThreshold) { 7933 angle = rightAngleLocked; 7934 } 7935 } 7936 7937 // normalize angle to positive value 7938 if (angle < 0) { 7939 angle = 360 + angle; 7940 } 7941 angle %= 360; 7942 7943 hasRotated = target.angle !== angle; 7944 target.angle = angle; 7945 return hasRotated; 7946 } 7947 7948 /** 7949 * Basic scaling logic, reused with different constrain for scaling X,Y, freely or equally. 7950 * Needs to be wrapped with `wrapWithFixedAnchor` to be effective 7951 * @param {Event} eventData javascript event that is doing the transform 7952 * @param {Object} transform javascript object containing a series of information around the current transform 7953 * @param {number} x current mouse x position, canvas normalized 7954 * @param {number} y current mouse y position, canvas normalized 7955 * @param {Object} options additional information for scaling 7956 * @param {String} options.by 'x', 'y', 'equally' or '' to indicate type of scaling 7957 * @return {Boolean} true if some change happened 7958 * @private 7959 */ 7960 function scaleObject(eventData, transform, x, y, options) { 7961 options = options || {}; 7962 var target = transform.target, 7963 lockScalingX = target.lockScalingX, 7964 lockScalingY = target.lockScalingY, 7965 by = options.by, 7966 newPoint, 7967 scaleX, 7968 scaleY, 7969 dim, 7970 scaleProportionally = scaleIsProportional(eventData, target), 7971 forbidScaling = scalingIsForbidden(target, by, scaleProportionally), 7972 signX, 7973 signY, 7974 gestureScale = transform.gestureScale; 7975 7976 if (forbidScaling) { 7977 return false; 7978 } 7979 if (gestureScale) { 7980 scaleX = transform.scaleX * gestureScale; 7981 scaleY = transform.scaleY * gestureScale; 7982 } else { 7983 newPoint = getLocalPoint( 7984 transform, 7985 transform.originX, 7986 transform.originY, 7987 x, 7988 y, 7989 ); 7990 // use of sign: We use sign to detect change of direction of an action. sign usually change when 7991 // we cross the origin point with the mouse. So a scale flip for example. There is an issue when scaling 7992 // by center and scaling using one middle control ( default: mr, mt, ml, mb), the mouse movement can easily 7993 // cross many time the origin point and flip the object. so we need a way to filter out the noise. 7994 // This ternary here should be ok to filter out X scaling when we want Y only and vice versa. 7995 signX = by !== "y" ? sign(newPoint.x) : 1; 7996 signY = by !== "x" ? sign(newPoint.y) : 1; 7997 if (!transform.signX) { 7998 transform.signX = signX; 7999 } 8000 if (!transform.signY) { 8001 transform.signY = signY; 8002 } 8003 8004 if ( 8005 target.lockScalingFlip && 8006 (transform.signX !== signX || transform.signY !== signY) 8007 ) { 8008 return false; 8009 } 8010 8011 dim = target._getTransformedDimensions(); 8012 // missing detection of flip and logic to switch the origin 8013 if (scaleProportionally && !by) { 8014 // uniform scaling 8015 var distance = Math.abs(newPoint.x) + Math.abs(newPoint.y), 8016 original = transform.original, 8017 originalDistance = 8018 Math.abs((dim.x * original.scaleX) / target.scaleX) + 8019 Math.abs((dim.y * original.scaleY) / target.scaleY), 8020 scale = distance / originalDistance; 8021 scaleX = original.scaleX * scale; 8022 scaleY = original.scaleY * scale; 8023 } else { 8024 scaleX = Math.abs((newPoint.x * target.scaleX) / dim.x); 8025 scaleY = Math.abs((newPoint.y * target.scaleY) / dim.y); 8026 } 8027 // if we are scaling by center, we need to double the scale 8028 if (isTransformCentered(transform)) { 8029 scaleX *= 2; 8030 scaleY *= 2; 8031 } 8032 if (transform.signX !== signX && by !== "y") { 8033 transform.originX = opposite[transform.originX]; 8034 scaleX *= -1; 8035 transform.signX = signX; 8036 } 8037 if (transform.signY !== signY && by !== "x") { 8038 transform.originY = opposite[transform.originY]; 8039 scaleY *= -1; 8040 transform.signY = signY; 8041 } 8042 } 8043 // minScale is taken are in the setter. 8044 var oldScaleX = target.scaleX, 8045 oldScaleY = target.scaleY; 8046 if (!by) { 8047 !lockScalingX && target.set("scaleX", scaleX); 8048 !lockScalingY && target.set("scaleY", scaleY); 8049 } else { 8050 // forbidden cases already handled on top here. 8051 by === "x" && target.set("scaleX", scaleX); 8052 by === "y" && target.set("scaleY", scaleY); 8053 } 8054 return oldScaleX !== target.scaleX || oldScaleY !== target.scaleY; 8055 } 8056 8057 /** 8058 * Generic scaling logic, to scale from corners either equally or freely. 8059 * Needs to be wrapped with `wrapWithFixedAnchor` to be effective 8060 * @param {Event} eventData javascript event that is doing the transform 8061 * @param {Object}
8061 transform javascript object containing a series of information around the current transform 8062 * @param {number} x current mouse x position, canvas normalized 8063 * @param {number} y current mouse y position, canvas normalized 8064 * @return {Boolean} true if some change happened 8065 */ 8066 function scaleObjectFromCorner(eventData, transform, x, y) { 8067 return scaleObject(eventData, transform, x, y); 8068 } 8069 8070 /** 8071 * Scaling logic for the X axis. 8072 * Needs to be wrapped with `wrapWithFixedAnchor` to be effective 8073 * @param {Event} eventData javascript event that is doing the transform 8074 * @param {Object} transform javascript object containing a series of information around the current transform 8075 * @param {number} x current mouse x position, canvas normalized 8076 * @param {number} y current mouse y position, canvas normalized 8077 * @return {Boolean} true if some change happened 8078 */ 8079 function scaleObjectX(eventData, transform, x, y) { 8080 return scaleObject(eventData, transform, x, y, { by: "x" }); 8081 } 8082 8083 /** 8084 * Scaling logic for the Y axis. 8085 * Needs to be wrapped with `wrapWithFixedAnchor` to be effective 8086 * @param {Event} eventData javascript event that is doing the transform 8087 * @param {Object} transform javascript object containing a series of information around the current transform 8088 * @param {number} x current mouse x position, canvas normalized 8089 * @param {number} y current mouse y position, canvas normalized 8090 * @return {Boolean} true if some change happened 8091 */ 8092 function scaleObjectY(eventData, transform, x, y) { 8093 return scaleObject(eventData, transform, x, y, { by: "y" }); 8094 } 8095 8096 /** 8097 * Composed action handler to either scale Y or skew X 8098 * Needs to be wrapped with `wrapWithFixedAnchor` to be effective 8099 * @param {Event} eventData javascript event that is doing the transform 8100 * @param {Object} transform javascript object containing a series of information around the current transform 8101 * @param {number} x current mouse x position, canvas normalized 8102 * @param {number} y current mouse y position, canvas normalized 8103 * @return {Boolean} true if some change happened 8104 */ 8105 function scalingYOrSkewingX(eventData, transform, x, y) { 8106 // ok some safety needed here. 8107 if (eventData[transform.target.canvas.altActionKey]) { 8108 return controls.skewHandlerX(eventData, transform, x, y); 8109 } 8110 return controls.scalingY(eventData, transform, x, y); 8111 } 8112 8113 /** 8114 * Composed action handler to either scale X or skew Y 8115 * Needs to be wrapped with `wrapWithFixedAnchor` to be effective 8116 * @param {Event} eventData javascript event that is doing the transform 8117 * @param {Object} transform javascript object containing a series of information around the current transform 8118 * @param {number} x current mouse x position, canvas normalized 8119 * @param {number} y current mouse y position, canvas normalized 8120 * @return {Boolean} true if some change happened 8121 */ 8122 function scalingXOrSkewingY(eventData, transform, x, y) { 8123 // ok some safety needed here. 8124 if (eventData[transform.target.canvas.altActionKey]) { 8125 return controls.skewHandlerY(eventData, transform, x, y); 8126 } 8127 return controls.scalingX(eventData, transform, x, y); 8128 } 8129 8130 /** 8131 * Action handler to change textbox width 8132 * Needs to be wrapped with `wrapWithFixedAnchor` to be effective 8133 * @param {Event} eventData javascript event that is doing the transform 8134 * @param {Object} transform javascript object containing a series of information around the current transform 8135 * @param {number} x current mouse x position, canvas normalized 8136 * @param {number} y current mouse y position, canvas normalized 8137 * @return {Boolean} true if some change happened 8138 */ 8139 function changeWidth(eventData, transform, x, y) { 8140 var target = transform.target, 8141 localPoint = getLocalPoint( 8142 transform, 8143 transform.originX, 8144 transform.originY, 8145 x, 8146 y, 8147 ), 8148 strokePadding = 8149 target.strokeWidth / (target.strokeUniform ? target.scaleX : 1), 8150 multiplier = isTransformCentered(transform) ? 2 : 1, 8151 oldWidth = target.width, 8152 newWidth = 8153 Math.abs((localPoint.x * multiplier) / target.scaleX) - strokePadding; 8154 target.set("width", Math.max(newWidth, 0)); 8155 return oldWidth !== newWidth; 8156 } 8157 8158 /** 8159 * Action handler 8160 * @private 8161 * @param {Event} eventData javascript event that is doing the transform 8162 * @param {Object}
8162 transform javascript object containing a series of information around the current transform 8163 * @param {number} x current mouse x position, canvas normalized 8164 * @param {number} y current mouse y position, canvas normalized 8165 * @return {Boolean} true if the translation occurred 8166 */ 8167 function dragHandler(eventData, transform, x, y) { 8168 var target = transform.target, 8169 newLeft = x - transform.offsetX, 8170 newTop = y - transform.offsetY, 8171 moveX = !target.get("lockMovementX") && target.left !== newLeft, 8172 moveY = !target.get("lockMovementY") && target.top !== newTop; 8173 moveX && target.set("left", newLeft); 8174 moveY && target.set("top", newTop); 8175 if (moveX || moveY) { 8176 fireEvent("moving", commonEventInfo(eventData, transform, x, y)); 8177 } 8178 return moveX || moveY; 8179 } 8180 8181 controls.scaleCursorStyleHandler = scaleCursorStyleHandler; 8182 controls.skewCursorStyleHandler = skewCursorStyleHandler; 8183 controls.scaleSkewCursorStyleHandler = scaleSkewCursorStyleHandler; 8184 controls.rotationWithSnapping = wrapWithFireEvent( 8185 "rotating", 8186 wrapWithFixedAnchor(rotationWithSnapping), 8187 ); 8188 controls.scalingEqually = wrapWithFireEvent( 8189 "scaling", 8190 wrapWithFixedAnchor(scaleObjectFromCorner), 8191 ); 8192 controls.scalingX = wrapWithFireEvent( 8193 "scaling", 8194 wrapWithFixedAnchor(scaleObjectX), 8195 ); 8196 controls.scalingY = wrapWithFireEvent( 8197 "scaling", 8198 wrapWithFixedAnchor(scaleObjectY), 8199 ); 8200 controls.scalingYOrSkewingX = scalingYOrSkewingX; 8201 controls.scalingXOrSkewingY = scalingXOrSkewingY; 8202 controls.changeWidth = wrapWithFireEvent( 8203 "resizing", 8204 wrapWithFixedAnchor(changeWidth), 8205 ); 8206 controls.skewHandlerX = skewHandlerX; 8207 controls.skewHandlerY = skewHandlerY; 8208 controls.dragHandler = dragHandler; 8209 controls.scaleOrSkewActionName = scaleOrSkewActionName; 8210 controls.rotationStyleHandler = rotationStyleHandler; 8211 controls.fireEvent = fireEvent; 8212 controls.wrapWithFixedAnchor = wrapWithFixedAnchor; 8213 controls.wrapWithFireEvent = wrapWithFireEvent; 8214 controls.getLocalPoint = getLocalPoint; 8215 fabric.controlsUtils = controls; 8216})(typeof exports !== "undefined" ? exports : this); 8217 8218(function (global) { 8219 "use strict"; 8220 8221 var fabric = global.fabric || (global.fabric = {}), 8222 degreesToRadians = fabric.util.degreesToRadians, 8223 controls = fabric.controlsUtils; 8224 8225 /** 8226 * Render a round control, as per fabric features. 8227 * This function is written to respect object properties like transparentCorners, cornerSize 8228 * cornerColor, cornerStrokeColor 8229 * plus the addition of offsetY and offsetX. 8230 * @param {CanvasRenderingContext2D} ctx context to render on 8231 * @param {Number} left x coordinate where the control center should be 8232 * @param {Number} top y coordinate where the control center should be 8233 * @param {Object} styleOverride override for fabric.Object controls style 8234 * @param {fabric.Object} fabricObject the fabric object for which we are rendering controls 8235 */ 8236 function renderCircleControl(ctx, left, top, styleOverride, fabricObject) { 8237 styleOverride = styleOverride || {}; 8238 var xSize = 8239 this.sizeX || styleOverride.cornerSize || fabricObject.cornerSize, 8240 ySize = this.sizeY || styleOverride.cornerSize || fabricObject.cornerSize, 8241 transparentCorners = 8242 typeof styleOverride.transparentCorners !== "undefined" 8243 ? styleOverride.transparentCorners 8244 : fabricObject.transparentCorners, 8245 methodName = transparentCorners ? "stroke" : "fill", 8246 stroke = 8247 !transparentCorners && 8248 (styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor), 8249 myLeft = left, 8250 myTop = top, 8251 size; 8252 ctx.save(); 8253 ctx.fillStyle = styleOverride.cornerColor || fabricObject.cornerColor; 8254 ctx.strokeStyle = 8255 styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor; 8256 // as soon as fabric react v5, remove ie11, use proper ellipse code. 8257 if (xSize > ySize) { 8258 size = xSize; 8259 ctx.scale(1.0, ySize / xSize); 8260 myTop = (top * xSize) / ySize; 8261 } else if (ySize > xSize) { 8262 size = ySize; 8263 ctx.scale(xSize / ySize, 1.0); 8264 myLeft = (left * ySize) / xSize; 8265 } else { 8266 size = xSize; 8267 } 8268 // this is still wrong 8269 ctx.lineWidth = 1; 8270 ctx.beginPath(); 8271 ctx.arc(myLeft, myTop, size / 2, 0, 2 * Math.PI, false); 8272 ctx[methodName](); 8273 if (stroke) { 8274 ctx.stroke(); 8275 }
vendor: 6,260 bytes, lines 8276-8454
8276 ctx.restore(); 8277 } 8278 8279 /** 8280 * Render a square control, as per fabric features. 8281 * This function is written to respect object properties like transparentCorners, cornerSize 8282 * cornerColor, cornerStrokeColor 8283 * plus the addition of offsetY and offsetX. 8284 * @param {CanvasRenderingContext2D} ctx context to render on 8285 * @param {Number} left x coordinate where the control center should be 8286 * @param {Number} top y coordinate where the control center should be 8287 * @param {Object} styleOverride override for fabric.Object controls style 8288 * @param {fabric.Object} fabricObject the fabric object for which we are rendering controls 8289 */ 8290 function renderSquareControl(ctx, left, top, styleOverride, fabricObject) { 8291 styleOverride = styleOverride || {}; 8292 var xSize = 8293 this.sizeX || styleOverride.cornerSize || fabricObject.cornerSize, 8294 ySize = this.sizeY || styleOverride.cornerSize || fabricObject.cornerSize, 8295 transparentCorners = 8296 typeof styleOverride.transparentCorners !== "undefined" 8297 ? styleOverride.transparentCorners 8298 : fabricObject.transparentCorners, 8299 methodName = transparentCorners ? "stroke" : "fill", 8300 stroke = 8301 !transparentCorners && 8302 (styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor), 8303 xSizeBy2 = xSize / 2, 8304 ySizeBy2 = ySize / 2; 8305 ctx.save(); 8306 ctx.fillStyle = styleOverride.cornerColor || fabricObject.cornerColor; 8307 ctx.strokeStyle = 8308 styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor; 8309 // this is still wrong 8310 ctx.lineWidth = 1; 8311 ctx.translate(left, top); 8312 ctx.rotate(degreesToRadians(fabricObject.angle)); 8313 // this does not work, and fixed with ( && ) does not make sense. 8314 // to have real transparent corners we need the controls on upperCanvas 8315 // transparentCorners || ctx.clearRect(-xSizeBy2, -ySizeBy2, xSize, ySize); 8316 ctx[methodName + "Rect"](-xSizeBy2, -ySizeBy2, xSize, ySize); 8317 if (stroke) { 8318 ctx.strokeRect(-xSizeBy2, -ySizeBy2, xSize, ySize); 8319 } 8320 ctx.restore(); 8321 } 8322 8323 controls.renderCircleControl = renderCircleControl; 8324 controls.renderSquareControl = renderSquareControl; 8325})(typeof exports !== "undefined" ? exports : this); 8326 8327(function (global) { 8328 "use strict"; 8329 8330 var fabric = global.fabric || (global.fabric = {}); 8331 8332 function Control(options) { 8333 for (var i in options) { 8334 this[i] = options[i]; 8335 } 8336 } 8337 8338 fabric.Control = Control; 8339 8340 fabric.Control.prototype = /** @lends fabric.Control.prototype */ { 8341 /** 8342 * keep track of control visibility. 8343 * mainly for backward compatibility. 8344 * if you do not want to see a control, you can remove it 8345 * from the controlset. 8346 * @type {Boolean} 8347 * @default true 8348 */ 8349 visible: true, 8350 8351 /** 8352 * Name of the action that the control will likely execute. 8353 * This is optional. FabricJS uses to identify what the user is doing for some 8354 * extra optimizations. If you are writing a custom control and you want to know 8355 * somewhere else in the code what is going on, you can use this string here. 8356 * you can also provide a custom getActionName if your control run multiple actions 8357 * depending on some external state. 8358 * default to scale since is the most common, used on 4 corners by default 8359 * @type {String} 8360 * @default 'scale' 8361 */ 8362 actionName: "scale", 8363 8364 /** 8365 * Drawing angle of the control. 8366 * NOT used for now, but name marked as needed for internal logic 8367 * example: to reuse the same drawing function for different rotated controls 8368 * @type {Number} 8369 * @default 0 8370 */ 8371 angle: 0, 8372 8373 /** 8374 * Relative position of the control. X 8375 * 0,0 is the center of the Object, while -0.5 (left) or 0.5 (right) are the extremities 8376 * of the bounding box. 8377 * @type {Number} 8378 * @default 0 8379 */ 8380 x: 0, 8381 8382 /** 8383 * Relative position of the control. Y 8384 * 0,0 is the center of the Object, while -0.5 (top) or 0.5 (bottom) are the extremities 8385 * of the bounding box. 8386 * @type {Number} 8387 * @default 0 8388 */ 8389 y: 0, 8390 8391 /** 8392 * Horizontal offset of the control from the defined position. In pixels 8393 * Positive offset moves the control to the right, negative to the left. 8394 * It used when you want to have position of control that does not scale with 8395 * the bounding box. Example: rotation control is placed at x:0, y: 0.5 on 8396 * the boundindbox, with an offset of 30 pixels vertically. Those 30 pixels will 8397 * stay 30 pixels no matter how the object is big. Another example is having 2 8398 * controls in the corner, that stay in the same position when the object scale. 8399 * of the bounding box. 8400 * @type {Number} 8401 * @default 0 8402 */ 8403 offsetX: 0, 8404 8405 /** 8406 * Vertical offset of the control from the defined position. In pixels 8407 * Positive offset moves the control to the bottom, negative to the top. 8408 * @type {Number} 8409 * @default 0 8410 */ 8411 offsetY: 0, 8412 8413 /** 8414 * Sets the length of the control. If null, defaults to object's cornerSize. 8415 * Expects both sizeX and sizeY to be set when set. 8416 * @type {?Number} 8417 * @default null 8418 */ 8419 sizeX: null, 8420 8421 /** 8422 * Sets the height of the control. If null, defaults to object's cornerSize. 8423 * Expects both sizeX and sizeY to be set when set. 8424 * @type {?Number} 8425 * @default null 8426 */ 8427 sizeY: null, 8428 8429 /** 8430 * Sets the length of the touch area of the control. If null, defaults to object's touchCornerSize. 8431 * Expects both touchSizeX and touchSizeY to be set when set. 8432 * @type {?Number} 8433 * @default null 8434 */ 8435 touchSizeX: null, 8436 8437 /** 8438 * Sets the height of the touch area of the control. If null, defaults to object's touchCornerSize. 8439 * Expects both touchSizeX and touchSizeY to be set when set. 8440 * @type {?Number} 8441 * @default null 8442 */ 8443 touchSizeY: null, 8444 8445 /** 8446 * Css cursor style to display when the control is hovered. 8447 * if the method `cursorStyleHandler` is provided, this property is ignored. 8448 * @type {String} 8449 * @default 'crosshair' 8450 */ 8451 cursorStyle: "crosshair", 8452 8453 /** 8454 * If controls has an offsetY or offsetX, draw a line that c
8454onnects 8455 * the control to the bounding box 8456 * @type {Boolean} 8457 * @default false 8458 */ 8459 withConnection: false, 8460 8461 /** 8462 * The control actionHandler, provide one to handle action ( control being moved ) 8463 * @param {Event} eventData the native mouse event 8464 * @param {Object} transformData properties of the current transform 8465 * @param {Number} x x position of the cursor 8466 * @param {Number} y y position of the cursor 8467 * @return {Boolean} true if the action/event modified the object 8468 */ 8469 actionHandler: function (/* eventData, transformData, x, y */) {}, 8470 8471 /** 8472 * The control handler for mouse down, provide one to handle mouse down on control 8473 * @param {Event} eventData the native mouse event 8474 * @param {Object} transformData properties of the current transform 8475 * @param {Number} x x position of the cursor 8476 * @param {Number} y y position of the cursor 8477 * @return {Boolean} true if the action/event modified the object 8478 */ 8479 mouseDownHandler: function (/* eventData, transformData, x, y */) {}, 8480 8481 /** 8482 * The control mouseUpHandler, provide one to handle an effect on mouse up. 8483 * @param {Event} eventData the native mouse event 8484 * @param {Object} transformData properties of the current transform 8485 * @param {Number} x x position of the cursor 8486 * @param {Number} y y position of the cursor 8487 * @return {Boolean} true if the action/event modified the object 8488 */ 8489 mouseUpHandler: function (/* eventData, transformData, x, y */) {}, 8490 8491 /** 8492 * Returns control actionHandler 8493 * @param {Event} eventData the native mouse event 8494 * @param {fabric.Object} fabricObject on which the control is displayed 8495 * @param {fabric.Control} control control for which the action handler is being asked 8496 * @return {Function} the action handler 8497 */ 8498 getActionHandler: function (/* eventData, fabricObject, control */) { 8499 return this.actionHandler; 8500 }, 8501 8502 /** 8503 * Returns control mouseDown handler 8504 * @param {Event} eventData the native mouse event 8505 * @param {fabric.Object} fabricObject on which the control is displayed 8506 * @param {fabric.Control} control control for which the action handler is being asked 8507 * @return {Function} the action handler 8508 */ 8509 getMouseDownHandler: function (/* eventData, fabricObject, control */) { 8510 return this.mouseDownHandler; 8511 }, 8512 8513 /** 8514 * Returns control mouseUp handler 8515 * @param {Event} eventData the native mouse event 8516 * @param {fabric.Object} fabricObject on which the control is displayed 8517 * @param {fabric.Control} control control for which the action handler is being asked 8518 * @return {Function} the action handler 8519 */ 8520 getMouseUpHandler: function (/* eventData, fabricObject, control */) { 8521 return this.mouseUpHandler; 8522 }, 8523 8524 /** 8525 * Returns control cursorStyle for css using cursorStyle. If you need a more elaborate 8526 * function you can pass one in the constructor 8527 * the cursorStyle property 8528 * @param {Event} eventData the native mouse event 8529 * @param {fabric.Control} control the current control ( likely this) 8530 * @param {fabric.Object} object on which the control is displayed 8531 * @return {String} 8532 */ 8533 cursorStyleHandler: function (eventData, control /* fabricObject */) { 8534 return control.cursorStyle; 8535 }, 8536 8537 /** 8538 * Returns the action name. The basic implementation just return the actionName property. 8539 * @param {Event} eventData the native mouse event 8540 * @param {fabric.Control} control the current control ( likely this) 8541 * @param {fabric.Object} object on which the control is displayed 8542 * @return {String} 8543 */ 8544 getActionName: function (eventData, control /* fabricObject */) { 8545 return control.actionName; 8546 }, 8547 8548 /** 8549 * Returns controls visibility 8550 * @param {fabric.Object} object on which the control is displayed 8551 * @param {String} controlKey key where the control is memorized on the 8552 * @return {Boolean} 8553 */ 8554 getVisibility: function (fabricObject, controlKey) { 8555 var objectVisibility = fabricObject._controlsVisibility; 8556 if ( 8557 objectVisibility && 8558 typeof objectVisibility[controlKey] !== "undefined" 8559 ) { 8560 return objectVisibility[controlKey]; 8561 } 8562 return this.visible; 8563 }, 8564 8565 /** 8566 * Sets controls visibility 8567 * @param {Boolean} visibility for the object 8568 * @return {Void} 8569 */ 8570 setVisibility: function (visibility /* name, fabricObject */) { 8571 this.visible = visibility; 8572 }, 8573 8574 positionHandler: function ( 8575 dim, 8576 finalMatrix /*, fabricObject, currentControl */, 8577 ) { 8578 var point = fabric.util.transformPoint( 8579 { 8580 x: this.x * dim.x + this.offsetX, 8581 y: this.y * dim.y + this.offsetY, 8582 }, 8583 finalMatrix, 8584 ); 8585 return point; 8586 }, 8587 8588 /** 8589 * Returns the coords for this control based on object values. 8590 * @param {Number} objectAngle angle from the fabric object holding the control 8591 * @param {Number} objectCornerSize cornerSize from the fabric object holding the control (or touchCornerSize if 8592 * isTouch is true) 8593 * @param {Number} centerX x coordinate where the control center should be 8594 * @param {Number} centerY y coordinate where the control center should be 8595 * @param {boolean} isTouch true if touch corner, false if normal corner 8596 */ 8597 calcCornerCoords: function ( 8598 objectAngle, 8599 objectCornerSize, 8600 centerX, 8601 centerY, 8602 isTouch, 8603 ) { 8604 var cosHalfOffset, 8605 sinHalfOffset, 8606 cosHalfOffsetComp, 8607 sinHalfOffsetComp, 8608 xSize = isTouch ? this.touchSizeX : this.sizeX, 8609 ySize = isTouch ? this.touchSizeY : this.sizeY; 8610 if (xSize && ySize && xSize !== ySize) { 8611 // handle rectangular corners 8612 var controlTriangleAngle = Math.atan2(ySize, xSize); 8613 var cornerHypotenuse = Math.sqrt(xSize * xSize + ySize * ySize) / 2; 8614 var newTheta = 8615 controlTriangleAngle - fabric.util.degreesToRadians(objectAngle); 8616 var newThetaComp = 8617 Math.PI / 2 - 8618 controlTriangleAngle - 8619 fabric.util.degreesToRadians(objectAngle); 8620 cosHalfOffset = cornerHypotenuse * fabric.util.cos(newTheta); 8621 sinHalfOffset = cornerHypotenuse * fabric.util.sin(newTheta); 8622 // use complementary angle for two corners 8623 cosHalfOffsetComp = cornerHypotenuse * fabric.util.cos(newThetaComp); 8624 sinHalfOffsetComp = cornerHypotenuse * fabric.util.sin(newThetaComp); 8625 } else { 8626 // handle square corners
8627 // use default object corner size unless size is defined 8628 var cornerSize = xSize && ySize ? xSize : objectCornerSize; 8629 /* 0.7071067812 stands for sqrt(2)/2 */ 8630 cornerHypotenuse = cornerSize * 0.7071067812; 8631 // complementary angles are equal since they're both 45 degrees 8632 var newTheta = fabric.util.degreesToRadians(45 - objectAngle); 8633 cosHalfOffset = cosHalfOffsetComp = 8634 cornerHypotenuse * fabric.util.cos(newTheta); 8635 sinHalfOffset = sinHalfOffsetComp = 8636 cornerHypotenuse * fabric.util.sin(newTheta); 8637 } 8638 8639 return { 8640 tl: { 8641 x: centerX - sinHalfOffsetComp, 8642 y: centerY - cosHalfOffsetComp, 8643 }, 8644 tr: { 8645 x: centerX + cosHalfOffset, 8646 y: centerY - sinHalfOffset, 8647 }, 8648 bl: { 8649 x: centerX - cosHalfOffset, 8650 y: centerY + sinHalfOffset, 8651 }, 8652 br: { 8653 x: centerX + sinHalfOffsetComp, 8654 y: centerY + cosHalfOffsetComp, 8655 }, 8656 }; 8657 }, 8658 8659 /** 8660 * Render function for the control. 8661 * When this function runs the context is unscaled. unrotate. Just retina scaled. 8662 * all the functions will have to translate to the point left,top before starting Drawing 8663 * if they want to draw a control where the position is detected. 8664 * left and top are the result of the positionHandler function 8665 * @param {RenderingContext2D} ctx the context where the control will be drawn 8666 * @param {Number} left position of the canvas where we are about to render the control. 8667 * @param {Number} top position of the canvas where we are about to render the control. 8668 * @param {Object} styleOverride 8669 * @param {fabric.Object} fabricObject the object where the control is about to be rendered 8670 */ 8671 render: function (ctx, left, top, styleOverride, fabricObject) { 8672 styleOverride = styleOverride || {}; 8673 switch (styleOverride.cornerStyle || fabricObject.cornerStyle) { 8674 case "circle": 8675 fabric.controlsUtils.renderCircleControl.call( 8676 this, 8677 ctx, 8678 left, 8679 top, 8680 styleOverride, 8681 fabricObject, 8682 ); 8683 break; 8684 default: 8685 fabric.controlsUtils.renderSquareControl.call( 8686 this, 8687 ctx, 8688 left, 8689 top, 8690 styleOverride, 8691 fabricObject, 8692 ); 8693 } 8694 }, 8695 }; 8696})(typeof exports !== "undefined" ? exports : this); 8697 8698(function () { 8699 /* _FROM_SVG_START_ */ 8700 function getColorStop(el, multiplier) { 8701 var style = el.getAttribute("style"), 8702 offset = el.getAttribute("offset") || 0, 8703 color, 8704 colorAlpha, 8705 opacity, 8706 i; 8707 8708 // convert percents to absolute values 8709 offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); 8710 offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; 8711 if (style) { 8712 var keyValuePairs = style.split(/\s*;\s*/); 8713 8714 if (keyValuePairs[keyValuePairs.length - 1] === "") { 8715 keyValuePairs.pop(); 8716 } 8717 8718 for (i = keyValuePairs.length; i--; ) { 8719 var split = keyValuePairs[i].split(/\s*:\s*/), 8720 key = split[0].trim(), 8721 value = split[1].trim(); 8722 8723 if (key === "stop-color") { 8724 color = value; 8725 } else if (key === "stop-opacity") { 8726 opacity = value; 8727 } 8728 } 8729 } 8730 8731 if (!color) { 8732 color = el.getAttribute("stop-color") || "rgb(0,0,0)"; 8733 } 8734 if (!opacity) { 8735 opacity = el.getAttribute("stop-opacity"); 8736 } 8737 8738 color = new fabric.Color(color); 8739 colorAlpha = color.getAlpha(); 8740 opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); 8741 opacity *= colorAlpha * multiplier; 8742 8743 return { 8744 offset: offset, 8745 color: color.toRgb(), 8746 opacity: opacity, 8747 }; 8748 } 8749 8750 function getLinearCoords(el) { 8751 return { 8752 x1: el.getAttribute("x1") || 0, 8753 y1: el.getAttribute("y1") || 0, 8754 x2: el.getAttribute("x2") || "100%", 8755 y2: el.getAttribute("y2") || 0, 8756 }; 8757 } 8758 8759 function getRadialCoords(el) { 8760 return { 8761 x1: el.getAttribute("fx") || el.getAttribute("cx") || "50%", 8762 y1: el.getAttribute("fy") || el.getAttribute("cy") || "50%", 8763 r1: 0, 8764 x2: el.getAttribute("cx") || "50%", 8765 y2: el.getAttribute("cy") || "50%", 8766 r2: el.getAttribute("r") || "50%", 8767 }; 8768 } 8769 /* _FROM_SVG_END_ */ 8770 8771 var clone = fabric.util.object.clone; 8772 8773 /** 8774 * Gradient class 8775 * @class fabric.Gradient 8776 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#gradients} 8777 * @see {@link fabric.Gradient#initialize}
8777 for constructor definition 8778 */ 8779 fabric.Gradient = fabric.util.createClass( 8780 /** @lends fabric.Gradient.prototype */ { 8781 /** 8782 * Horizontal offset for aligning gradients coming from SVG when outside pathgroups 8783 * @type Number 8784 * @default 0 8785 */ 8786 offsetX: 0, 8787 8788 /** 8789 * Vertical offset for aligning gradients coming from SVG when outside pathgroups 8790 * @type Number 8791 * @default 0 8792 */ 8793 offsetY: 0, 8794 8795 /** 8796 * A transform matrix to apply to the gradient before painting. 8797 * Imported from svg gradients, is not applied with the current transform in the center. 8798 * Before this transform is applied, the origin point is at the top left corner of the object 8799 * plus the addition of offsetY and offsetX. 8800 * @type Number[] 8801 * @default null 8802 */ 8803 gradientTransform: null, 8804 8805 /** 8806 * coordinates units for coords. 8807 * If `pixels`, the number of coords are in the same unit of width / height. 8808 * If set as `percentage` the coords are still a number, but 1 means 100% of width 8809 * for the X and 100% of the height for the y. It can be bigger than 1 and negative. 8810 * allowed values pixels or percentage.
8811 * @type String 8812 * @default 'pixels' 8813 */ 8814 gradientUnits: "pixels", 8815 8816 /** 8817 * Gradient type linear or radial 8818 * @type String 8819 * @default 'pixels' 8820 */ 8821 type: "linear", 8822 8823 /** 8824 * Constructor 8825 * @param {Object} options Options object with type, coords, gradientUnits and colorStops 8826 * @param {Object} [options.type] gradient type linear or radial 8827 * @param {Object} [options.gradientUnits] gradient units 8828 * @param {Object} [options.offsetX] SVG import compatibility 8829 * @param {Object} [options.offsetY] SVG import compatibility 8830 * @param {Object[]} options.colorStops contains the colorstops. 8831 * @param {Object} options.coords contains the coords of the gradient 8832 * @param {Number} [options.coords.x1] X coordiante of the first point for linear or of the focal point for radial 8833 * @param {Number} [options.coords.y1] Y coordiante of the first point for linear or of the focal point for radial 8834 * @param {Number} [options.coords.x2] X coordiante of the second point for linear or of the center point for radial 8835 * @param {Number} [options.coords.y2] Y coordiante of the second point for linear or of the center point for radial 8836 * @param {Number} [options.coords.r1] only for radial gradient, radius of the inner circle 8837 * @param {Number} [options.coords.r2] only for radial gradient, radius of the external circle 8838 * @return {fabric.Gradient} thisArg 8839 */ 8840 initialize: function (options) { 8841 options || (options = {}); 8842 options.coords || (options.coords = {}); 8843 8844 var coords, 8845 _this = this; 8846 8847 // sets everything, then coords and colorstops get sets again 8848 Object.keys(options).forEach(function (option) { 8849 _this[option] = options[option]; 8850 }); 8851 8852 if (this.id) { 8853 this.id += "_" + fabric.Object.__uid++; 8854 } else { 8855 this.id = fabric.Object.__uid++; 8856 } 8857 8858 coords = { 8859 x1: options.coords.x1 || 0, 8860 y1: options.coords.y1 || 0, 8861 x2: options.coords.x2 || 0, 8862 y2: options.coords.y2 || 0, 8863 }; 8864 8865 if (this.type === "radial") { 8866 coords.r1 = options.coords.r1 || 0; 8867 coords.r2 = options.coords.r2 || 0; 8868 } 8869 8870 this.coords = coords; 8871 this.colorStops = options.colorStops.slice(); 8872 }, 8873 8874 /** 8875 * Adds another colorStop 8876 * @param {Object} colorStop Object with offset and color 8877 * @return {fabric.Gradient} thisArg 8878 */ 8879 addColorStop: function (colorStops) { 8880 for (var position in colorStops) { 8881 var color = new fabric.Color(colorStops[position]); 8882 this.colorStops.push({ 8883 offset: parseFloat(position), 8884 color: color.toRgb(), 8885 opacity: color.getAlpha(), 8886 }); 8887 } 8888 return this; 8889 }, 8890 8891 /** 8892 * Returns object representation of a gradient 8893 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 8894 * @return {Object} 8895 */ 8896 toObject: function (propertiesToInclude) { 8897 var object = { 8898 type: this.type, 8899 coords: this.coords, 8900 colorStops: this.colorStops, 8901 offsetX: this.offsetX, 8902 offsetY: this.offsetY, 8903 gradientUnits: this.gradientUnits, 8904 gradientTransform: this.gradientTransform 8905 ? this.gradientTransform.concat() 8906 : this.gradientTransform, 8907 }; 8908 fabric.util.populateWithProperties(this, object, propertiesToInclude); 8909 8910 return object; 8911 }, 8912 8913 /* _TO_SVG_START_ */ 8914 /** 8915 * Returns SVG representation of an gradient 8916 * @param {Object} object Object to create a gradient for 8917 * @return {String} SVG representation of an gradient (linear/radial) 8918 */ 8919 toSVG: function (object, options) { 8920 var coords = clone(this.coords, true), 8921 i, 8922 len, 8923 options = options || {}, 8924 markup, 8925 commonAttributes, 8926 colorStops = clone(this.colorStops, true),
8927 needsSwap = coords.r1 > coords.r2, 8928 transform = this.gradientTransform 8929 ? this.gradientTransform.concat() 8930 : fabric.iMatrix.concat(), 8931 offsetX = -this.offsetX, 8932 offsetY = -this.offsetY, 8933 withViewport = !!options.additionalTransform, 8934 gradientUnits = 8935 this.gradientUnits === "pixels" 8936 ? "userSpaceOnUse" 8937 : "objectBoundingBox"; 8938 // colorStops must be sorted ascending 8939 colorStops.sort(function (a, b) { 8940 return a.offset - b.offset; 8941 }); 8942 8943 if (gradientUnits === "objectBoundingBox") { 8944 offsetX /= object.width; 8945 offsetY /= object.height; 8946 } else { 8947 offsetX += object.width / 2; 8948 offsetY += object.height / 2; 8949 } 8950 if (object.type === "path" && this.gradientUnits !== "percentage") { 8951 offsetX -= object.pathOffset.x; 8952 offsetY -= object.pathOffset.y; 8953 } 8954 8955 transform[4] -= offsetX; 8956 transform[5] -= offsetY; 8957 8958 commonAttributes = 8959 'id="SVGID_' + this.id + '" gradientUnits="' + gradientUnits + '"'; 8960 commonAttributes += 8961 ' gradientTransform="' + 8962 (withViewport ? options.additionalTransform + " " : "") + 8963 fabric.util.matrixToSVG(transform) + 8964 '" '; 8965 8966 if (this.type === "linear") { 8967 markup = [ 8968 "<linearGradient ", 8969 commonAttributes, 8970 ' x1="', 8971 coords.x1, 8972 '" y1="', 8973 coords.y1, 8974 '" x2="', 8975 coords.x2, 8976 '" y2="', 8977 coords.y2, 8978 '">\n', 8979 ]; 8980 } else if (this.type === "radial") { 8981 // svg radial gradient has just 1 radius. the biggest. 8982 markup = [ 8983 "<radialGradient ", 8984 commonAttributes, 8985 ' cx="', 8986 needsSwap ? coords.x1 : coords.x2, 8987 '" cy="', 8988 needsSwap ? coords.y1 : coords.y2, 8989 '" r="', 8990 needsSwap ? coords.r1 : coords.r2, 8991 '" fx="', 8992 needsSwap ? coords.x2 : coords.x1, 8993 '" fy="', 8994 needsSwap ? coords.y2 : coords.y1, 8995 '">\n', 8996 ]; 8997 } 8998 8999 if (this.type === "radial") { 9000 if (needsSwap) { 9001 // svg goes from internal to external radius. if radius are inverted, swap color stops. 9002 colorStops = colorStops.concat(); 9003 colorStops.reverse(); 9004 for (i = 0, len = colorStops.length; i < len; i++) { 9005 colorStops[i].offset = 1 - colorStops[i].offset; 9006 } 9007 } 9008 var minRadius = Math.min(coords.r1, coords.r2); 9009 if (minRadius > 0) { 9010 // i have to shift all colorStops and add new one in 0. 9011 var maxRadius = Math.max(coords.r1, coords.r2), 9012 percentageShift = minRadius / maxRadius; 9013 for (i = 0, len = colorStops.length; i < len; i++) { 9014 colorStops[i].offset += 9015 percentageShift * (1 - colorStops[i].offset); 9016 } 9017 } 9018 } 9019 9020 for (i = 0, len = colorStops.length; i < len; i++) { 9021 var colorStop = colorStops[i]; 9022 markup.push( 9023 "<stop ", 9024 'offset="', 9025 colorStop.offset * 100 + "%", 9026 '" style="stop-color:', 9027 colorStop.color, 9028 typeof colorStop.opacity !== "undefined" 9029 ? ";stop-opacity: " + colorStop.opacity 9030 : ";", 9031 '"/>\n', 9032 ); 9033 } 9034 9035 markup.push( 9036 this.type === "linear" 9037 ? "</linearGradient>\n" 9038 : "</radialGradient>\n", 9039 ); 9040 9041 return markup.join(""); 9042 }, 9043 /* _TO_SVG_END_ */ 9044 9045 /** 9046 * Returns an instance of CanvasGradient 9047 * @param {CanvasRenderingContext2D} ctx Context to render on 9048 * @return {CanvasGradient} 9049 */ 9050 toLive: function (ctx) { 9051 var gradient, 9052 coords = fabric.util.object.clone(this.coords), 9053 i, 9054 len; 9055 9056 if (!this.type) { 9057 return; 9058 } 9059
9060 if (this.type === "linear") { 9061 gradient = ctx.createLinearGradient( 9062 coords.x1, 9063 coords.y1, 9064 coords.x2, 9065 coords.y2, 9066 ); 9067 } else if (this.type === "radial") { 9068 gradient = ctx.createRadialGradient( 9069 coords.x1, 9070 coords.y1, 9071 coords.r1, 9072 coords.x2, 9073 coords.y2, 9074 coords.r2, 9075 ); 9076 } 9077 9078 for (i = 0, len = this.colorStops.length; i < len; i++) { 9079 var color = this.colorStops[i].color, 9080 opacity = this.colorStops[i].opacity, 9081 offset = this.colorStops[i].offset; 9082 9083 if (typeof opacity !== "undefined") { 9084 color = new fabric.Color(color).setAlpha(opacity).toRgba(); 9085 } 9086 gradient.addColorStop(offset, color); 9087 } 9088 9089 return gradient; 9090 }, 9091 }, 9092 ); 9093 9094 fabric.util.object.extend(fabric.Gradient, { 9095 /* _FROM_SVG_START_ */ 9096 /** 9097 * Returns {@link fabric.Gradient} instance from an SVG element 9098 * @static 9099 * @memberOf fabric.Gradient 9100 * @param {SVGGradientElement} el SVG gradient element 9101 * @param {fabric.Object} instance 9102 * @param {String} opacityAttr A fill-opacity or stroke-opacity attribute to multiply to each stop's opacity. 9103 * @param {Object}
9103 svgOptions an object containing the size of the SVG in order to parse correctly gradients 9104 * that uses gradientUnits as 'userSpaceOnUse' and percentages. 9105 * @param {Object.number} viewBoxWidth width part of the viewBox attribute on svg 9106 * @param {Object.number} viewBoxHeight height part of the viewBox attribute on svg 9107 * @param {Object.number} width width part of the svg tag if viewBox is not specified 9108 * @param {Object.number} height height part of the svg tag if viewBox is not specified 9109 * @return {fabric.Gradient} Gradient instance 9110 * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement 9111 * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement 9112 */ 9113 fromElement: function (el, instance, opacityAttr, svgOptions) { 9114 /** 9115 * @example: 9116 * 9117 * <linearGradient id="linearGrad1"> 9118 * <stop offset="0%" stop-color="white"/> 9119 * <stop offset="100%" stop-color="black"/> 9120 * </linearGradient> 9121 * 9122 * OR 9123 * 9124 * <linearGradient id="linearGrad2"> 9125 * <stop offset="0" style="stop-color:rgb(255,255,255)"/> 9126 * <stop offset="1" style="stop-color:rgb(0,0,0)"/> 9127 * </linearGradient> 9128 * 9129 * OR 9130 * 9131 * <radialGradient id="radialGrad1"> 9132 * <stop offset="0%" stop-color="white" stop-opacity="1" /> 9133 * <stop offset="50%" stop-color="black" stop-opacity="0.5" /> 9134 * <stop offset="100%" stop-color="white" stop-opacity="1" /> 9135 * </radialGradient> 9136 * 9137 * OR 9138 * 9139 * <radialGradient id="radialGrad2"> 9140 * <stop offset="0" stop-color="rgb(255,255,255)" /> 9141 * <stop offset="0.5" stop-color="rgb(0,0,0)" /> 9142 * <stop offset="1" stop-color="rgb(255,255,255)" /> 9143 * </radialGradient> 9144 * 9145 */ 9146 9147 var multiplier = 9148 parseFloat(opacityAttr) / (/%$/.test(opacityAttr) ? 100 : 1); 9149 multiplier = multiplier < 0 ? 0 : multiplier > 1 ? 1 : multiplier; 9150 if (isNaN(multiplier)) { 9151 multiplier = 1; 9152 } 9153 9154 var colorStopEls = el.getElementsByTagName("stop"), 9155 type, 9156 gradientUnits = 9157 el.getAttribute("gradientUnits") === "userSpaceOnUse" 9158 ? "pixels" 9159 : "percentage", 9160 gradientTransform = el.getAttribute("gradientTransform") || "", 9161 colorStops = [], 9162 coords, 9163 i, 9164 offsetX = 0, 9165 offsetY = 0, 9166 transformMatrix; 9167 if ( 9168 el.nodeName === "linearGradient" || 9169 el.nodeName === "LINEARGRADIENT" 9170 ) { 9171 type = "linear"; 9172 coords = getLinearCoords(el); 9173 } else { 9174 type = "radial"; 9175 coords = getRadialCoords(el); 9176 } 9177 9178 for (i = colorStopEls.length; i--; ) { 9179 colorStops.push(getColorStop(colorStopEls[i], multiplier)); 9180 } 9181 9182 transformMatrix = fabric.parseTransformAttribute(gradientTransform); 9183 9184 __convertPercentUnitsToValues( 9185 instance, 9186 coords, 9187 svgOptions, 9188 gradientUnits, 9189 ); 9190 9191 if (gradientUnits === "pixels") { 9192 offsetX = -instance.left; 9193 offsetY = -instance.top; 9194 } 9195 9196 var gradient = new fabric.Gradient({ 9197 id: el.getAttribute("id"), 9198 type: type, 9199 coords: coords, 9200 colorStops: colorStops, 9201 gradientUnits: gradientUnits, 9202 gradientTransform: transformMatrix, 9203 offsetX: offsetX, 9204 offsetY: offsetY, 9205 }); 9206 9207 return gradient; 9208 }, 9209 /* _FROM_SVG_END_ */ 9210 }); 9211 9212 /** 9213 * @private 9214 */ 9215 function __convertPercentUnitsToValues( 9216 instance, 9217 options, 9218 svgOptions, 9219 gradientUnits, 9220 ) { 9221 var propValue, finalValue; 9222 Object.keys(options).forEach(function (prop) { 9223 propValue = options[prop]; 9224 if (propValue === "Infinity") { 9225 finalValue = 1; 9226 } else if (propValue === "-Infinity") { 9227 finalValue = 0; 9228 } else { 9229 finalValue = parseFloat(options[prop], 10); 9230 if ( 9231 typeof propValue === "string" && 9232 /^(\d+\.\d+)%|(\d+)%$/.test(propValue) 9233 ) { 9234 finalValue *= 0.01; 9235 if (gradientUnits === "pixels") { 9236 // then we need to fix those percentages here in svg parsing 9237 if (prop === "x1" || prop === "x2" || prop === "r2") { 9238 finalValue *= svgOptions.viewBoxWidth || svgOptions.width; 9239 } 9240 if (prop === "y1" || prop === "y2") { 9241 finalValue *= svgOptions.viewBoxHeight || svgOptions.height; 9242 } 9243 } 9244 } 9245 } 9246 options[prop] = finalValue; 9247 }); 9248 } 9249})(); 9250 9251(function () { 9252 "use strict"; 9253 9254 var toFixed = fabric.util.toFixed; 9255 9256 /** 9257 * Pattern class 9258 * @class fabric.Pattern 9259 * @see {@link http://fabricjs.com/patterns|Pattern demo} 9260 * @see {@link http://fabricjs.com/dynamic-patterns|DynamicPattern demo} 9261 * @see {@link fabric.Pattern#initialize}
9261 for constructor definition 9262 */ 9263 9264 fabric.Pattern = fabric.util.createClass( 9265 /** @lends fabric.Pattern.prototype */ { 9266 /** 9267 * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) 9268 * @type String 9269 * @default 9270 */ 9271 repeat: "repeat", 9272 9273 /** 9274 * Pattern horizontal offset from object's left/top corner 9275 * @type Number 9276 * @default 9277 */ 9278 offsetX: 0, 9279 9280 /** 9281 * Pattern vertical offset from object's left/top corner 9282 * @type Number 9283 * @default 9284 */ 9285 offsetY: 0, 9286 9287 /** 9288 * crossOrigin value (one of "", "anonymous", "use-credentials") 9289 * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes 9290 * @type String 9291 * @default 9292 */ 9293 crossOrigin: "", 9294 9295 /** 9296 * transform matrix to change the pattern, imported from svgs. 9297 * @type Array 9298 * @default 9299 */ 9300 patternTransform: null, 9301 9302 /** 9303 * Constructor 9304 * @param {Object} [options] Options object 9305 * @param {Function} [callback] function to invoke after callback init. 9306 * @return {fabric.Pattern} thisArg 9307 */ 9308 initialize: function (options, callback) { 9309 options || (options = {}); 9310 9311 this.id = fabric.Object.__uid++; 9312 this.setOptions(options); 9313 if ( 9314 !options.source || 9315 (options.source && typeof options.source !== "string") 9316 ) { 9317 callback && callback(this); 9318 return; 9319 } else { 9320 // img src string 9321 var _this = this; 9322 this.source = fabric.util.createImage(); 9323 fabric.util.loadImage( 9324 options.source, 9325 function (img, isError) { 9326 _this.source = img; 9327 callback && callback(_this, isError); 9328 }, 9329 null, 9330 this.crossOrigin, 9331 ); 9332 } 9333 }, 9334 9335 /** 9336 * Returns object representation of a pattern 9337 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 9338 * @return {Object} Object representation of a pattern instance 9339 */ 9340 toObject: function (propertiesToInclude) { 9341 var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, 9342 source, 9343 object; 9344 9345 // <img> element 9346 if (typeof this.source.src === "string") { 9347 source = this.source.src; 9348 } 9349 // <canvas> element 9350 else if (typeof this.source === "object" && this.source.toDataURL) { 9351 source = this.source.toDataURL(); 9352 } 9353 9354 object = { 9355 type: "pattern", 9356 source: source, 9357 repeat: this.repeat, 9358 crossOrigin: this.crossOrigin, 9359 offsetX: toFixed(this.offsetX, NUM_FRACTION_DIGITS), 9360 offsetY: toFixed(this.offsetY, NUM_FRACTION_DIGITS), 9361 patternTransform: this.patternTransform 9362 ? this.patternTransform.concat() 9363 : null, 9364 }; 9365 fabric.util.populateWithProperties(this, object, propertiesToInclude); 9366 9367 return object; 9368 }, 9369 9370 /* _TO_SVG_START_ */ 9371 /** 9372 * Returns SVG representation of a pattern 9373 * @param {fabric.Object} object 9374 * @return {String} SVG representation of a pattern 9375 */ 9376 toSVG: function (object) { 9377 var patternSource = 9378 typeof this.source === "function" ? this.source() : this.source, 9379 patternWidth = patternSource.width / object.width, 9380 patternHeight = patternSource.height / object.height, 9381 patternOffsetX = this.offsetX / object.width, 9382 patternOffsetY = this.offsetY / object.height, 9383 patternImgSrc = ""; 9384 if (this.repeat === "repeat-x" || this.repeat === "no-repeat") { 9385 patternHeight = 1; 9386 if (patternOffsetY) { 9387 patternHeight += Math.abs(patternOffsetY); 9388 } 9389 } 9390 if (this.repeat === "repeat-y" || this.repeat === "no-repeat") { 9391 patternWidth = 1; 9392 if (patternOffsetX) { 9393 patternWidth += Math.abs(patternOffsetX); 9394 } 9395 } 9396 if (patternSource.src) { 9397 patternImgSrc = patternSource.src; 9398 } else if (patternSource.toDataURL) { 9399 patternImgSrc = patternSource.toDataURL(); 9400 } 9401 9402 return ( 9403 '<pattern id="SVGID_' + 9404 this.id + 9405 '" x="' + 9406 patternOffsetX + 9407 '" y="' + 9408 patternOffsetY + 9409 '" width="' + 9410 patternWidth + 9411 '" height="' + 9412 patternHeight + 9413 '">\n' + 9414 '<image x="0" y="0"' + 9415 ' width="' + 9416 patternSource.width + 9417 '" height="' + 9418 patternSource.height + 9419 '" xlink:href="' + 9420 patternImgSrc + 9421 '"></image>\n' + 9422 "</pattern>\n" 9423 ); 9424 }, 9425 /* _TO_SVG_END_ */ 9426 9427 setOptions: function (options) { 9428 for (var prop in options) { 9429 this[prop] = options[prop]; 9430 } 9431 }, 9432 9433 /** 9434 * Returns an instance of CanvasPattern 9435 * @param {CanvasRenderingContext2D} ctx Context to create pattern 9436 * @return {CanvasPattern} 9437 */ 9438 toLive: function (ctx) { 9439 var source = this.source; 9440 // if the image failed to load, return, and allow rest to continue loading 9441 if (!source) { 9442 return ""; 9443 } 9444 9445 // if an image 9446 if (typeof source.src !== "undefined") { 9447 if (!source.complete) { 9448 return ""; 9449 }
9450 if (source.naturalWidth === 0 || source.naturalHeight === 0) { 9451 return ""; 9452 } 9453 } 9454 return ctx.createPattern(source, this.repeat); 9455 }, 9456 }, 9457 ); 9458})(); 9459 9460(function (global) { 9461 "use strict"; 9462 9463 var fabric = global.fabric || (global.fabric = {}), 9464 toFixed = fabric.util.toFixed; 9465 9466 if (fabric.Shadow) { 9467 fabric.warn("fabric.Shadow is already defined."); 9468 return; 9469 } 9470 9471 /** 9472 * Shadow class 9473 * @class fabric.Shadow 9474 * @see {@link http://fabricjs.com/shadows|Shadow demo} 9475 * @see {@link fabric.Shadow#initialize} for constructor definition 9476 */ 9477 fabric.Shadow = fabric.util.createClass( 9478 /** @lends fabric.Shadow.prototype */ { 9479 /** 9480 * Shadow color 9481 * @type String 9482 * @default 9483 */ 9484 color: "rgb(0,0,0)", 9485 9486 /** 9487 * Shadow blur 9488 * @type Number 9489 */ 9490 blur: 0, 9491 9492 /** 9493 * Shadow horizontal offset 9494 * @type Number 9495 * @default 9496 */ 9497 offsetX: 0, 9498 9499 /** 9500 * Shadow vertical offset 9501 * @type Number 9502 * @default 9503 */ 9504 offsetY: 0, 9505 9506 /** 9507 * Whether the shadow should affect stroke operations 9508 * @type Boolean 9509 * @default 9510 */ 9511 affectStroke: false, 9512 9513 /** 9514 * Indicates whether toObject should include default values 9515 * @type Boolean 9516 * @default 9517 */ 9518 includeDefaultValues: true, 9519 9520 /** 9521 * When `false`, the shadow will scale with the object. 9522 * When `true`, the shadow's offsetX, offsetY, and blur will not be affected by the object's scale. 9523 * default to false 9524 * @type Boolean 9525 * @default 9526 */ 9527 nonScaling: false, 9528 9529 /** 9530 * Constructor 9531 * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetY properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px") 9532 * @return {fabric.Shadow} thisArg 9533 */ 9534 initialize: function (options) { 9535 if (typeof options === "string") { 9536 options = this._parseShadow(options); 9537 } 9538 9539 for (var prop in options) { 9540 this[prop] = options[prop]; 9541 } 9542 9543 this.id = fabric.Object.__uid++; 9544 }, 9545 9546 /** 9547 * @private 9548 * @param {String} shadow Shadow value to parse 9549 * @return {Object} Shadow object with color, offsetX, offsetY and blur 9550 */ 9551 _parseShadow: function (shadow) { 9552 var shadowStr = shadow.trim(), 9553 offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [], 9554 color = 9555 shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, "") || 9556 "rgb(0,0,0)"; 9557 9558 return { 9559 color: color.trim(), 9560 offsetX: parseFloat(offsetsAndBlur[1], 10) || 0, 9561 offsetY: parseFloat(offsetsAndBlur[2], 10) || 0, 9562 blur: parseFloat(offsetsAndBlur[3], 10) || 0, 9563 }; 9564 }, 9565 9566 /** 9567 * Returns a string representation of an instance 9568 * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow 9569 * @return {String} Returns CSS3 text-shadow declaration 9570 */ 9571 toString: function () { 9572 return [this.offsetX, this.offsetY, this.blur, this.color].join("px "); 9573 }, 9574 9575 /* _TO_SVG_START_ */ 9576 /** 9577 * Returns SVG representation of a shadow 9578 * @param {fabric.Object} object 9579 * @return {String} SVG representation of a shadow 9580 */ 9581 toSVG: function (object) { 9582 var fBoxX = 40, 9583 fBoxY = 40, 9584 NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, 9585 offset = fabric.util.rotateVector( 9586 { x: this.offsetX, y: this.offsetY }, 9587 fabric.util.degreesToRadians(-object.angle), 9588 ), 9589 BLUR_BOX = 20, 9590 color = new fabric.Color(this.color); 9591 9592 if (object.width && object.height) { 9593 //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion 9594 // we add some extra space to filter box to contain the blur ( 20 ) 9595 fBoxX = 9596 toFixed( 9597 (Math.abs(offset.x) + this.blur) / object.width, 9598 NUM_FRACTION_DIGITS, 9599 ) * 9600 100 + 9601 BLUR_BOX; 9602 fBoxY = 9603 toFixed( 9604 (Math.abs(offset.y) + this.blur) / object.height, 9605 NUM_FRACTION_DIGITS, 9606 ) * 9607 100 + 9608 BLUR_BOX; 9609 } 9610 if (object.flipX) { 9611 offset.x *= -1; 9612 } 9613 if (object.flipY) { 9614 offset.y *= -1; 9615 } 9616 9617 return ( 9618 '<filter id="SVGID_' + 9619 this.id + 9620 '" y="-' + 9621 fBoxY + 9622 '%" height="' + 9623 (100 + 2 * fBoxY) + 9624 '%" ' + 9625 'x="-' + 9626 fBoxX + 9627 '%" width="' + 9628 (100 + 2 * fBoxX) + 9629 '%" ' + 9630 ">\n" + 9631 '\t<feGaussianBlur in="SourceAlpha" stdDeviation="' + 9632 toFixed(this.blur ? this.blur / 2 : 0, NUM_FRACTION_DIGITS) + 9633 '"></feGaussianBlur>\n' + 9634 '\t<feOffset dx="' + 9635 toFixed(offset.x, NUM_FRACTION_DIGITS) + 9636 '" dy="' + 9637 toFixed(offset.y, NUM_FRACTION_DIGITS) + 9638 '" result="oBlur" ></feOffset>\n' + 9639 '\t<feFlood flood-color="' + 9640 color.toRgb() + 9641 '" flood-opacity="' + 9642 color.getAlpha() + 9643 '"/>\n' + 9644 '\t<feComposite in2="oBlur" operator="in" />\n' + 9645 "\t<feMerge>\n" + 9646 "\t\t<feMergeNode></feMergeNode>\n" + 9647 '\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' + 9648 "\t</feMerge>\n" + 9649 "</filter>\n" 9650 ); 9651 }, 9652 /* _TO_SVG_END_ */ 9653 9654 /** 9655 * Returns object representation of a shadow 9656 * @return {Object} Object representation of a shadow instance 9657 */ 9658 toObject: function () { 9659 if (this.includeDefaultValues) { 9660 return { 9661 color: this.color, 9662 blur: this.blur, 9663 offsetX: this.offsetX, 9664 offsetY: this.offsetY, 9665 affectStroke: this.affectStroke, 9666 nonScaling: this.nonScaling, 9667 }; 9668 } 9669 var obj = {}, 9670 proto = fabric.Shadow.prototype; 9671 9672 [ 9673 "color", 9674 "blur", 9675 "offsetX", 9676 "offsetY", 9677 "affectStroke", 9678 "nonScaling", 9679 ].forEach(function (prop) { 9680 if (this[prop] !== proto[prop]) { 9681 obj[prop] = this[prop]; 9682 } 9683 }, this); 9684 9685 return obj; 9686 }, 9687 }, 9688 ); 9689 9690 /**
9691 * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") 9692 * @static 9693 * @field 9694 * @memberOf fabric.Shadow 9695 */ 9696 // eslint-disable-next-line max-len 9697 fabric.Shadow.reOffsetsAndBlur = 9698 /(?:\s|^)(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(\d+(?:\.\d*)?(?:px)?)?(?:\s?|$)(?:$|\s)/; 9699})(typeof exports !== "undefined" ? exports : this); 9700 9701(function () { 9702 "use strict"; 9703 9704 if (fabric.StaticCanvas) { 9705 fabric.warn("fabric.StaticCanvas is already defined."); 9706 return; 9707 } 9708 9709 // aliases for faster resolution 9710 var extend = fabric.util.object.extend, 9711 getElementOffset = fabric.util.getElementOffset, 9712 removeFromArray = fabric.util.removeFromArray, 9713 toFixed = fabric.util.toFixed, 9714 transformPoint = fabric.util.transformPoint, 9715 invertTransform = fabric.util.invertTransform, 9716 getNodeCanvas = fabric.util.getNodeCanvas, 9717 createCanvasElement = fabric.util.createCanvasElement, 9718 CANVAS_INIT_ERROR = new Error("Could not initialize `canvas` element"); 9719 9720 /** 9721 * Static canvas class 9722 * @class fabric.StaticCanvas 9723 * @mixes fabric.Collection 9724 * @mixes fabric.Observable 9725 * @see {@link http://fabricjs.com/static_canvas|StaticCanvas demo} 9726 * @see {@link fabric.StaticCanvas#initialize} for constructor definition 9727 * @fires before:render 9728 * @fires after:render 9729 * @fires canvas:cleared 9730 * @fires object:added 9731 * @fires object:removed 9732 */ 9733 fabric.StaticCanvas = fabric.util.createClass( 9734 fabric.CommonMethods, 9735 /** @lends fabric.StaticCanvas.prototype */ { 9736 /** 9737 * Constructor 9738 * @param {HTMLElement | String} el <canvas> element to initialize instance on 9739 * @param {Object} [options] Options object 9740 * @return {Object} thisArg 9741 */ 9742 initialize: function (el, options) { 9743 options || (options = {}); 9744 this.renderAndResetBound = this.renderAndReset.bind(this); 9745 this.requestRenderAllBound = this.requestRenderAll.bind(this); 9746 this._initStatic(el, options); 9747 }, 9748 9749 /** 9750 * Background color of canvas instance. 9751 * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. 9752 * @type {(String|fabric.Pattern)} 9753 * @default 9754 */ 9755 backgroundColor: "", 9756 9757 /** 9758 * Background image of canvas instance. 9759 * since 2.4.0 image caching is active, please when putting an image as background, add to the 9760 * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom 9761 * vale. As an alternative you can disable image objectCaching 9762 * @type fabric.Image 9763 * @default 9764 */ 9765 backgroundImage: null, 9766 9767 /** 9768 * Overlay color of canvas instance. 9769 * Should be set via {@link fabric.StaticCanvas#setOverlayColor} 9770 * @since 1.3.9 9771 * @type {(String|fabric.Pattern)} 9772 * @default 9773 */ 9774 overlayColor: "", 9775 9776 /** 9777 * Overlay image of canvas instance. 9778 * since 2.4.0 image caching is active, please when putting an image as overlay, add to the 9779 * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom 9780 * vale. As an alternative you can disable image objectCaching 9781 * @type fabric.Image 9782 * @default 9783 */ 9784 overlayImage: null, 9785 9786 /** 9787 * Indicates whether toObject/toDatalessObject should include default values 9788 * if set to false, takes precedence over the object value. 9789 * @type Boolean 9790 * @default 9791 */ 9792 includeDefaultValues: true, 9793 9794 /** 9795 * Indicates whether objects' state should be saved 9796 * @type Boolean 9797 * @default 9798 */ 9799 stateful: false, 9800 9801 /** 9802 * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove}, 9803 * {@link fabric.StaticCanvas.moveTo}, {@link fabric.StaticCanvas.clear} and many more, should also re-render canvas. 9804 * Disabling this option will not give a performance boost when adding/removing a lot of objects to/from canvas at once 9805 * since the renders are quequed and executed one per frame. 9806 * Disabling is suggested anyway and managing the renders of the app manually is not a big effort ( canvas.requestRenderAll() ) 9807 * Left default to true to do not break documentation and old app, fiddles. 9808 * @type Boolean 9809 * @default 9810 */ 9811 renderOnAddRemove: true, 9812 9813 /**
9814 * Indicates whether object controls (borders/controls) are rendered above overlay image 9815 * @type Boolean 9816 * @default 9817 */ 9818 controlsAboveOverlay: false, 9819 9820 /** 9821 * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas 9822 * @type Boolean 9823 * @default 9824 */ 9825 allowTouchScrolling: false, 9826 9827 /** 9828 * Indicates whether this canvas will use image smoothing, this is on by default in browsers 9829 * @type Boolean 9830 * @default 9831 */ 9832 imageSmoothingEnabled: true, 9833 9834 /** 9835 * The transformation (a Canvas 2D API transform matrix) which focuses the viewport 9836 * @type Array 9837 * @example <caption>Default transform</caption> 9838 * canvas.viewportTransform = [1, 0, 0, 1, 0, 0]; 9839 * @example <caption>Scale by 70% and translate toward bottom-right by 50, without skewing</caption> 9840 * canvas.viewportTransform = [0.7, 0, 0, 0.7, 50, 50]; 9841 * @default 9842 */ 9843 viewportTransform: fabric.iMatrix.concat(), 9844 9845 /** 9846 * if set to false background image is not affected by viewport transform 9847 * @since 1.6.3 9848 * @type Boolean 9849 * @default 9850 */ 9851 backgroundVpt: true, 9852 9853 /** 9854 * if set to false overlya image is not affected by viewport transform 9855 * @since 1.6.3 9856 * @type Boolean 9857 * @default 9858 */ 9859 overlayVpt: true, 9860 9861 /** 9862 * When true, canvas is scaled by devicePixelRatio for better rendering on retina screens 9863 * @type Boolean 9864 * @default 9865 */ 9866 enableRetinaScaling: true, 9867 9868 /** 9869 * Describe canvas element extension over design 9870 * properties are tl,tr,bl,br. 9871 * if canvas is not zoomed/panned those points are the four corner of canvas 9872 * if canvas is viewportTransformed you those points indicate the extension 9873 * of canvas element in plain untrasformed coordinates 9874 * The coordinates get updated with @method calcViewportBoundaries. 9875 * @memberOf fabric.StaticCanvas.prototype 9876 */ 9877 vptCoords: {}, 9878 9879 /** 9880 * Based on vptCoords and object.aCoords, skip rendering of objects that 9881 * are not included in current viewport. 9882 * May greatly help in applications with crowded canvas and use of zoom/pan 9883 * If One of the corner of the bounding box of the object is on the canvas 9884 * the objects get rendered. 9885 * @memberOf fabric.StaticCanvas.prototype 9886 * @type Boolean 9887 * @default 9888 */ 9889 skipOffscreen: true, 9890 9891 /** 9892 * a fabricObject that, without stroke define a clipping area with their shape. filled in black 9893 * the clipPath object gets used when the canvas has rendered, and the context is placed in the 9894 * top left corner of the canvas. 9895 * clipPath will clip away controls, if you do not want this to happen use controlsAboveOverlay = true 9896 * @type fabric.Object 9897 */ 9898 clipPath: undefined, 9899 9900 /** 9901 * @private 9902 * @param {HTMLElement | String} el <canvas> element to initialize instance on 9903 * @param {Object} [options] Options object 9904 */ 9905 _initStatic: function (el, options) { 9906 var cb = this.requestRenderAllBound; 9907 this._objects = []; 9908 this._createLowerCanvas(el); 9909 this._initOptions(options); 9910 // only initialize retina scaling once 9911 if (!this.interactive) { 9912 this._initRetinaScaling(); 9913 } 9914 9915 if (options.overlayImage) { 9916 this.setOverlayImage(options.overlayImage, cb); 9917 } 9918 if (options.backgroundImage) { 9919 this.setBackgroundImage(options.backgroundImage, cb); 9920 } 9921 if (options.backgroundColor) { 9922 this.setBackgroundColor(options.backgroundColor, cb); 9923 } 9924 if (options.overlayColor) { 9925 this.setOverlayColor(options.overlayColor, cb); 9926 } 9927 this.calcOffset(); 9928 }, 9929 9930 /** 9931 * @private 9932 */ 9933 _isRetinaScaling: function () { 9934 return fabric.devicePixelRatio > 1 && this.enableRetinaScaling; 9935 }, 9936 9937 /** 9938 * @private 9939 * @return {Number} retinaScaling if applied, otherwise 1; 9940 */ 9941 getRetinaScaling: function () { 9942 return this._isRetinaScaling() 9943 ? Math.max(1, fabric.devicePixelRatio) 9944 : 1; 9945 }, 9946 9947 /** 9948 * @private 9949 */ 9950 _initRetinaScaling: function () { 9951 if (!this._isRetinaScaling()) { 9952 return; 9953 } 9954 var scaleRatio = fabric.devicePixelRatio; 9955 this.__initRetinaScaling( 9956 scaleRatio, 9957 this.lowerCanvasEl, 9958 this.contextContainer, 9959 ); 9960 if (this.upperCanvasEl) { 9961 this.__initRetinaScaling( 9962 scaleRatio, 9963 this.upperCanvasEl, 9964 this.contextTop, 9965 ); 9966 } 9967 }, 9968 9969 __initRetinaScaling: function (scaleRatio, canvas, context) { 9970 canvas.setAttribute("width", this.width * scaleRatio); 9971 canvas.setAttribute("height", this.height * scaleRatio); 9972 context.scale(scaleRatio, scaleRatio); 9973 }, 9974 9975 /** 9976 * Calculates canvas element offset relative to the document 9977 * This method is also attached as "resize" event handler of window 9978 * @return {fabric.Canvas} instance 9979 * @chainable 9980 */ 9981 calcOffset: function () { 9982 this._offset = getElementOffset(this.lowerCanvasEl); 9983 return this; 9984 }, 9985 9986 /** 9987 * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas 9988 * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to 9989 * @param {Function} callback callback to invoke when image is loaded and set as an overlay 9990 * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. 9991 * @return {fabric.Canvas} thisArg 9992 * @chainable 9993 * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} 9994 * @example <caption>Normal overlayImage with left/top = 0</caption> 9995 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 9996 * // Needed to position overlayImage at 0/0 9997 * originX: 'left', 9998 * originY: 'top' 9999 * }); 10000 * @example <caption>overlayImage with different properties</caption> 10001 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 10002 * opacity: 0.5, 10003 * angle: 45, 10004 * left: 400, 10005 * top: 400, 10006 * originX: 'left', 10007 * originY: 'top' 10008 * }); 10009 * @example <caption>Stretched overlayImage #1 - width/height correspond to canvas width/height</caption> 10010 * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img, isError) {
10011 * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); 10012 * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); 10013 * }); 10014 * @example <caption>Stretched overlayImage #2 - width/height correspond to canvas width/height</caption> 10015 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 10016 * width: canvas.width, 10017 * height: canvas.height, 10018 * // Needed to position overlayImage at 0/0 10019 * originX: 'left', 10020 * originY: 'top' 10021 * }); 10022 * @example <caption>overlayImage loaded from cross-origin</caption> 10023 * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { 10024 * opacity: 0.5, 10025 * angle: 45, 10026 * left: 400, 10027 * top: 400, 10028 * originX: 'left', 10029 * originY: 'top', 10030 * crossOrigin: 'anonymous' 10031 * }); 10032 */ 10033 setOverlayImage: function (image, callback, options) { 10034 return this.__setBgOverlayImage( 10035 "overlayImage", 10036 image, 10037 callback, 10038 options, 10039 ); 10040 }, 10041 10042 /** 10043 * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas 10044 * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to 10045 * @param {Function} callback Callback to invoke when image is loaded and set as background 10046 * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. 10047 * @return {fabric.Canvas} thisArg 10048 * @chainable 10049 * @see {@link http://jsfiddle.net/djnr8o7a/28/|jsFiddle demo} 10050 * @example <caption>Normal backgroundImage with left/top = 0</caption> 10051 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 10052 * // Needed to position backgroundImage at 0/0 10053 * originX: 'left', 10054 * originY: 'top' 10055 * }); 10056 * @example <caption>backgroundImage with different properties</caption> 10057 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 10058 * opacity: 0.5, 10059 * angle: 45, 10060 * left: 400, 10061 * top: 400, 10062 * originX: 'left', 10063 * originY: 'top' 10064 * }); 10065 * @example <caption>Stretched backgroundImage #1 - width/height correspond to canvas width/height</caption> 10066 * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img, isError) { 10067 * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); 10068 * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); 10069 * }); 10070 * @example <caption>Stretched backgroundImage #2 - width/height correspond to canvas width/height</caption> 10071 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 10072 * width: canvas.width, 10073 * height: canvas.height, 10074 * // Needed to position backgroundImage at 0/0 10075 * originX: 'left', 10076 * originY: 'top' 10077 * }); 10078 * @example <caption>backgroundImage loaded from cross-origin</caption> 10079 * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { 10080 * opacity: 0.5, 10081 * angle: 45, 10082 * left: 400, 10083 * top: 400, 10084 * originX: 'left', 10085 * originY: 'top', 10086 * crossOrigin: 'anonymous' 10087 * }); 10088 */
vendor: 20,971 bytes, lines 10089-10695
10089 // TODO: fix stretched examples 10090 setBackgroundImage: function (image, callback, options) { 10091 return this.__setBgOverlayImage( 10092 "backgroundImage", 10093 image, 10094 callback, 10095 options, 10096 ); 10097 }, 10098 10099 /** 10100 * Sets {@link fabric.StaticCanvas#overlayColor|foreground color} for this canvas 10101 * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set foreground color to 10102 * @param {Function} callback Callback to invoke when foreground color is set 10103 * @return {fabric.Canvas} thisArg 10104 * @chainable 10105 * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} 10106 * @example <caption>Normal overlayColor - color value</caption> 10107 * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); 10108 * @example <caption>fabric.Pattern used as overlayColor</caption> 10109 * canvas.setOverlayColor({ 10110 * source: 'http://fabricjs.com/assets/escheresque_ste.png' 10111 * }, canvas.renderAll.bind(canvas)); 10112 * @example <caption>fabric.Pattern used as overlayColor with repeat and offset</caption> 10113 * canvas.setOverlayColor({ 10114 * source: 'http://fabricjs.com/assets/escheresque_ste.png', 10115 * repeat: 'repeat', 10116 * offsetX: 200, 10117 * offsetY: 100 10118 * }, canvas.renderAll.bind(canvas)); 10119 */ 10120 setOverlayColor: function (overlayColor, callback) { 10121 return this.__setBgOverlayColor("overlayColor", overlayColor, callback); 10122 }, 10123 10124 /** 10125 * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas 10126 * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to 10127 * @param {Function} callback Callback to invoke when background color is set 10128 * @return {fabric.Canvas} thisArg 10129 * @chainable 10130 * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} 10131 * @example <caption>Normal backgroundColor - color value</caption> 10132 * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); 10133 * @example <caption>fabric.Pattern used as backgroundColor</caption> 10134 * canvas.setBackgroundColor({ 10135 * source: 'http://fabricjs.com/assets/escheresque_ste.png' 10136 * }, canvas.renderAll.bind(canvas)); 10137 * @example <caption>fabric.Pattern used as backgroundColor with repeat and offset</caption> 10138 * canvas.setBackgroundColor({ 10139 * source: 'http://fabricjs.com/assets/escheresque_ste.png', 10140 * repeat: 'repeat', 10141 * offsetX: 200, 10142 * offsetY: 100 10143 * }, canvas.renderAll.bind(canvas)); 10144 */ 10145 setBackgroundColor: function (backgroundColor, callback) { 10146 return this.__setBgOverlayColor( 10147 "backgroundColor", 10148 backgroundColor, 10149 callback, 10150 ); 10151 }, 10152 10153 /** 10154 * @private 10155 * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} 10156 * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) 10157 * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to 10158 * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay. The first argument is the created image, the second argument is a flag indicating whether an error occurred or not. 10159 * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. 10160 */ 10161 __setBgOverlayImage: function (property, image, callback, options) { 10162 if (typeof image === "string") { 10163 fabric.util.loadImage( 10164 image, 10165 function (img, isError) { 10166 if (img) { 10167 var instance = new fabric.Image(img, options); 10168 this[property] = instance; 10169 instance.canvas = this; 10170 } 10171 callback && callback(img, isError); 10172 }, 10173 this, 10174 options && options.crossOrigin, 10175 ); 10176 } else { 10177 options && image.setOptions(options); 10178 this[property] = image; 10179 image && (image.canvas = this); 10180 callback && callback(image, false); 10181 } 10182 10183 return this; 10184 }, 10185 10186 /** 10187 * @private 10188 * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} 10189 * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) 10190 * @param {(Object|String|null)} color Object with pattern information, color value or null 10191 * @param {Function} [callback] Callback is invoked when color is set 10192 */ 10193 __setBgOverlayColor: function (property, color, callback) { 10194 this[property] = color; 10195 this._initGradient(color, property); 10196 this._initPattern(color, property, callback); 10197 return this; 10198 }, 10199 10200 /** 10201 * @private 10202 */ 10203 _createCanvasElement: function () { 10204 var element = createCanvasElement(); 10205 if (!element) { 10206 throw CANVAS_INIT_ERROR; 10207 } 10208 if (!element.style) { 10209 element.style = {}; 10210 } 10211 if (typeof element.getContext === "undefined") { 10212 throw CANVAS_INIT_ERROR; 10213 } 10214 return element; 10215 }, 10216 10217 /** 10218 * @private 10219 * @param {Object} [options] Options object 10220 */ 10221 _initOptions: function (options) { 10222 var lowerCanvasEl = this.lowerCanvasEl; 10223 this._setOptions(options); 10224 10225 this.width = this.width || parseInt(lowerCanvasEl.width, 10) || 0; 10226 this.height = this.height || parseInt(lowerCanvasEl.height, 10) || 0; 10227 10228 if (!this.lowerCanvasEl.style) { 10229 return; 10230 } 10231 10232 lowerCanvasEl.width = this.width; 10233 lowerCanvasEl.height = this.height; 10234 10235 lowerCanvasEl.style.width = this.width + "px"; 10236 lowerCanvasEl.style.height = this.height + "px"; 10237 10238 this.viewportTransform = this.viewportTransform.slice(); 10239 }, 10240 10241 /** 10242 * Creates a bottom canvas 10243 * @private 10244 * @param {HTMLElement} [canvasEl] 10245 */ 10246 _createLowerCanvas: function (canvasEl) { 10247 // canvasEl === 'HTMLCanvasElement' does not work on jsdom/node 10248 if (canvasEl && canvasEl.getContext) { 10249 this.lowerCanvasEl = canvasEl; 10250 } else { 10251 this.lowerCanvasEl = 10252 fabric.util.getById(canvasEl) || this._createCanvasElement(); 10253 } 10254 10255 fabric.util.addClass(this.lowerCanvasEl, "lower-canvas"); 10256 this._originalCanvasStyle = this.lowerCanvasEl.style; 10257 if (this.interactive) { 10258 this._applyCanvasStyle(this.lowerCanvasEl); 10259 } 10260 10261 this.contextContainer = this.lowerCanvasEl.getContext("2d"); 10262 }, 10263 10264 /** 10265 * Returns canvas width (in px) 10266 * @return {Number} 10267 */ 10268 getWidth: function () { 10269 return this.width; 10270 }, 10271 10272 /** 10273 * Returns canvas height (in px) 10274 * @return {Number} 10275 */ 10276 getHeight: function () { 10277 return this.height; 10278 }, 10279 10280 /** 10281 * Sets width of this canvas instance 10282 * @param {Number|String} value Value to set width to 10283 * @param {Object} [options] Options object 10284 * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions 10285 * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions 10286 * @return {fabric.Canvas} instance 10287 * @chainable true 10288 */ 10289 setWidth: function (value, options) { 10290 return this.setDimensions({ width: value }, options); 10291 }, 10292 10293 /** 10294 * Sets height of this canvas instance 10295 * @param {Number|String} value Value to set height to 10296 * @param {Object} [options] Options object 10297 * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions 10298 * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions 10299 * @return {fabric.Canvas} instance 10300 * @chainable true 10301 */ 10302 setHeight: function (value, options) { 10303 return this.setDimensions({ height: value }, options); 10304 }, 10305 10306 /** 10307 * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) 10308 * @param {Object} dimensions Object with width/height properties 10309 * @param {Number|String} [dimensions.width] Width of canvas element 10310 * @param {Number|String} [dimensions.height] Height of canvas element 10311 * @param {Object} [options] Options object 10312 * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions 10313 * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions 10314 * @return {fabric.Canvas} thisArg 10315 * @chainable 10316 */ 10317 setDimensions: function (dimensions, options) { 10318 var cssValue; 10319 10320 options = options || {}; 10321 10322 for (var prop in dimensions) { 10323 cssValue = dimensions[prop]; 10324 10325 if (!options.cssOnly) { 10326 this._setBackstoreDimension(prop, dimensions[prop]); 10327 cssValue += "px"; 10328 this.hasLostContext = true; 10329 } 10330 10331 if (!options.backstoreOnly) { 10332 this._setCssDimension(prop, cssValue); 10333 } 10334 } 10335 if (this._isCurrentlyDrawing) { 10336 this.freeDrawingBrush && 10337 this.freeDrawingBrush._setBrushStyles(this.contextTop); 10338 } 10339 this._initRetinaScaling(); 10340 this.calcOffset(); 10341 10342 if (!options.cssOnly) { 10343 this.requestRenderAll(); 10344 } 10345 10346 return this; 10347 }, 10348 10349 /** 10350 * Helper for setting width/height 10351 * @private 10352 * @param {String} prop property (width|height) 10353 * @param {Number} value value to set property to 10354 * @return {fabric.Canvas} instance 10355 * @chainable true 10356 */ 10357 _setBackstoreDimension: function (prop, value) { 10358 this.lowerCanvasEl[prop] = value; 10359 10360 if (this.upperCanvasEl) { 10361 this.upperCanvasEl[prop] = value; 10362 } 10363 10364 if (this.cacheCanvasEl) { 10365 this.cacheCanvasEl[prop] = value; 10366 } 10367 10368 this[prop] = value; 10369 10370 return this; 10371 }, 10372 10373 /** 10374 * Helper for setting css width/height 10375 * @private 10376 * @param {String} prop property (width|height) 10377 * @param {String} value value to set property to 10378 * @return {fabric.Canvas} instance 10379 * @chainable true 10380 */ 10381 _setCssDimension: function (prop, value) { 10382 this.lowerCanvasEl.style[prop] = value; 10383 10384 if (this.upperCanvasEl) { 10385 this.upperCanvasEl.style[prop] = value; 10386 } 10387 10388 if (this.wrapperEl) { 10389 this.wrapperEl.style[prop] = value; 10390 } 10391 10392 return this; 10393 }, 10394 10395 /** 10396 * Returns canvas zoom level 10397 * @return {Number} 10398 */ 10399 getZoom: function () { 10400 return this.viewportTransform[0]; 10401 }, 10402 10403 /** 10404 * Sets viewport transformation of this canvas instance 10405 * @param {Array} vpt a Canvas 2D API transform matrix 10406 * @return {fabric.Canvas} instance 10407 * @chainable true 10408 */ 10409 setViewportTransform: function (vpt) { 10410 var activeObject = this._activeObject, 10411 backgroundObject = this.backgroundImage, 10412 overlayObject = this.overlayImage, 10413 object, 10414 i, 10415 len; 10416 this.viewportTransform = vpt; 10417 for (i = 0, len = this._objects.length; i < len; i++) { 10418 object = this._objects[i]; 10419 object.group || object.setCoords(true); 10420 } 10421 if (activeObject) { 10422 activeObject.setCoords(); 10423 } 10424 if (backgroundObject) { 10425 backgroundObject.setCoords(true); 10426 } 10427 if (overlayObject) { 10428 overlayObject.setCoords(true); 10429 } 10430 this.calcViewportBoundaries(); 10431 this.renderOnAddRemove && this.requestRenderAll(); 10432 return this; 10433 }, 10434 10435 /** 10436 * Sets zoom level of this canvas instance, the zoom centered around point 10437 * meaning that following zoom to point with the same point will have the visual 10438 * effect of the zoom originating from that point. The point won't move. 10439 * It has nothing to do with canvas center or visual center of the viewport. 10440 * @param {fabric.Point} point to zoom with respect to 10441 * @param {Number} value to set zoom to, less than 1 zooms out 10442 * @return {fabric.Canvas} instance 10443 * @chainable true 10444 */ 10445 zoomToPoint: function (point, value) { 10446 // TODO: just change the scale, preserve other transformations 10447 var before = point, 10448 vpt = this.viewportTransform.slice(0); 10449 point = transformPoint(point, invertTransform(this.viewportTransform)); 10450 vpt[0] = value; 10451 vpt[3] = value; 10452 var after = transformPoint(point, vpt); 10453 vpt[4] += before.x - after.x; 10454 vpt[5] += before.y - after.y; 10455 return this.setViewportTransform(vpt); 10456 }, 10457 10458 /** 10459 * Sets zoom level of this canvas instance 10460 * @param {Number} value to set zoom to, less than 1 zooms out 10461 * @return {fabric.Canvas} instance 10462 * @chainable true 10463 */ 10464 setZoom: function (value) { 10465 this.zoomToPoint(new fabric.Point(0, 0), value); 10466 return this; 10467 }, 10468 10469 /** 10470 * Pan viewport so as to place point at top left corner of canvas 10471 * @param {fabric.Point} point to move to 10472 * @return {fabric.Canvas} instance 10473 * @chainable true 10474 */ 10475 absolutePan: function (point) { 10476 var vpt = this.viewportTransform.slice(0); 10477 vpt[4] = -point.x; 10478 vpt[5] = -point.y; 10479 return this.setViewportTransform(vpt); 10480 }, 10481 10482 /** 10483 * Pans viewpoint relatively 10484 * @param {fabric.Point} point (position vector) to move by 10485 * @return {fabric.Canvas} instance 10486 * @chainable true 10487 */ 10488 relativePan: function (point) { 10489 return this.absolutePan( 10490 new fabric.Point( 10491 -point.x - this.viewportTransform[4], 10492 -point.y - this.viewportTransform[5], 10493 ), 10494 ); 10495 }, 10496 10497 /** 10498 * Returns <canvas> element corresponding to this instance 10499 * @return {HTMLCanvasElement} 10500 */ 10501 getElement: function () { 10502 return this.lowerCanvasEl; 10503 }, 10504 10505 /** 10506 * @private 10507 * @param {fabric.Object} obj Object that was added 10508 */ 10509 _onObjectAdded: function (obj) { 10510 this.stateful && obj.setupState(); 10511 obj._set("canvas", this); 10512 obj.setCoords(); 10513 this.fire("object:added", { target: obj }); 10514 obj.fire("added"); 10515 }, 10516 10517 /** 10518 * @private 10519 * @param {fabric.Object} obj Object that was removed 10520 */ 10521 _onObjectRemoved: function (obj) { 10522 this.fire("object:removed", { target: obj }); 10523 obj.fire("removed"); 10524 delete obj.canvas; 10525 }, 10526 10527 /** 10528 * Clears specified context of canvas element 10529 * @param {CanvasRenderingContext2D} ctx Context to clear 10530 * @return {fabric.Canvas} thisArg 10531 * @chainable 10532 */ 10533 clearContext: function (ctx) { 10534 ctx.clearRect(0, 0, this.width, this.height); 10535 return this; 10536 }, 10537 10538 /** 10539 * Returns context of canvas where objects are drawn 10540 * @return {CanvasRenderingContext2D} 10541 */ 10542 getContext: function () { 10543 return this.contextContainer; 10544 }, 10545 10546 /** 10547 * Clears all contexts (background, main, top) of an instance 10548 * @return {fabric.Canvas} thisArg 10549 * @chainable 10550 */ 10551 clear: function () { 10552 this.remove.apply(this, this.getObjects()); 10553 this.backgroundImage = null; 10554 this.overlayImage = null; 10555 this.backgroundColor = ""; 10556 this.overlayColor = ""; 10557 if (this._hasITextHandlers) { 10558 this.off("mouse:up", this._mouseUpITextHandler); 10559 this._iTextInstances = null; 10560 this._hasITextHandlers = false; 10561 } 10562 this.clearContext(this.contextContainer); 10563 this.fire("canvas:cleared"); 10564 this.renderOnAddRemove && this.requestRenderAll(); 10565 return this; 10566 }, 10567 10568 /** 10569 * Renders the canvas 10570 * @return {fabric.Canvas} instance 10571 * @chainable 10572 */ 10573 renderAll: function () { 10574 var canvasToDrawOn = this.contextContainer; 10575 this.renderCanvas(canvasToDrawOn, this._objects); 10576 return this; 10577 }, 10578 10579 /** 10580 * Function created to be instance bound at initialization 10581 * used in requestAnimationFrame rendering 10582 * Let the fabricJS call it. If you call it manually you could have more 10583 * animationFrame stacking on to of each other 10584 * for an imperative rendering, use canvas.renderAll 10585 * @private 10586 * @return {fabric.Canvas} instance 10587 * @chainable 10588 */ 10589 renderAndReset: function () { 10590 this.isRendering = 0; 10591 this.renderAll(); 10592 }, 10593 10594 /** 10595 * Append a renderAll request to next animation frame. 10596 * unless one is already in progress, in that case nothing is done 10597 * a boolean flag will avoid appending more. 10598 * @return {fabric.Canvas} instance 10599 * @chainable 10600 */ 10601 requestRenderAll: function () { 10602 if (!this.isRendering) { 10603 this.isRendering = fabric.util.requestAnimFrame( 10604 this.renderAndResetBound, 10605 ); 10606 } 10607 return this; 10608 }, 10609 10610 /** 10611 * Calculate the position of the 4 corner of canvas with current viewportTransform. 10612 * helps to determinate when an object is in the current rendering viewport using 10613 * object absolute coordinates ( aCoords ) 10614 * @return {Object} points.tl 10615 * @chainable 10616 */ 10617 calcViewportBoundaries: function () { 10618 var points = {}, 10619 width = this.width, 10620 height = this.height, 10621 iVpt = invertTransform(this.viewportTransform); 10622 points.tl = transformPoint({ x: 0, y: 0 }, iVpt); 10623 points.br = transformPoint({ x: width, y: height }, iVpt); 10624 points.tr = new fabric.Point(points.br.x, points.tl.y); 10625 points.bl = new fabric.Point(points.tl.x, points.br.y); 10626 this.vptCoords = points; 10627 return points; 10628 }, 10629 10630 cancelRequestedRender: function () { 10631 if (this.isRendering) { 10632 fabric.util.cancelAnimFrame(this.isRendering); 10633 this.isRendering = 0; 10634 } 10635 }, 10636 10637 /** 10638 * Renders background, objects, overlay and controls. 10639 * @param {CanvasRenderingContext2D} ctx 10640 * @param {Array} objects to render 10641 * @return {fabric.Canvas} instance 10642 * @chainable 10643 */ 10644 renderCanvas: function (ctx, objects) { 10645 var v = this.viewportTransform, 10646 path = this.clipPath; 10647 this.cancelRequestedRender(); 10648 this.calcViewportBoundaries(); 10649 this.clearContext(ctx); 10650 fabric.util.setImageSmoothing(ctx, this.imageSmoothingEnabled); 10651 this.fire("before:render", { ctx: ctx }); 10652 this._renderBackground(ctx); 10653 10654 ctx.save(); 10655 //apply viewport transform once for all rendering process 10656 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 10657 this._renderObjects(ctx, objects); 10658 ctx.restore(); 10659 if (!this.controlsAboveOverlay && this.interactive) { 10660 this.drawControls(ctx); 10661 } 10662 if (path) { 10663 path.canvas = this; 10664 // needed to setup a couple of variables 10665 path.shouldCache(); 10666 path._transformDone = true; 10667 path.renderCache({ forClipping: true }); 10668 this.drawClipPathOnCanvas(ctx); 10669 } 10670 this._renderOverlay(ctx); 10671 if (this.controlsAboveOverlay && this.interactive) { 10672 this.drawControls(ctx); 10673 } 10674 this.fire("after:render", { ctx: ctx }); 10675 }, 10676 10677 /** 10678 * Paint the cached clipPath on the lowerCanvasEl 10679 * @param {CanvasRenderingContext2D} ctx Context to render on 10680 */ 10681 drawClipPathOnCanvas: function (ctx) { 10682 var v = this.viewportTransform, 10683 path = this.clipPath; 10684 ctx.save(); 10685 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 10686 // DEBUG: uncomment this line, comment the following 10687 // ctx.globalAlpha = 0.4; 10688 ctx.globalCompositeOperation = "destination-in"; 10689 path.transform(ctx); 10690 ctx.scale(1 / path.zoomX, 1 / path.zoomY); 10691 ctx.drawImage( 10692 path._cacheCanvas, 10693 -path.cacheTranslationX, 10694 -path.cacheTranslationY, 10695 );
10696 ctx.restore(); 10697 }, 10698 10699 /** 10700 * @private 10701 * @param {CanvasRenderingContext2D} ctx Context to render on 10702 * @param {Array} objects to render 10703 */ 10704 _renderObjects: function (ctx, objects) { 10705 var i, len; 10706 for (i = 0, len = objects.length; i < len; ++i) { 10707 objects[i] && objects[i].render(ctx); 10708 } 10709 }, 10710 10711 /** 10712 * @private 10713 * @param {CanvasRenderingContext2D} ctx Context to render on 10714 * @param {string} property 'background' or 'overlay' 10715 */ 10716 _renderBackgroundOrOverlay: function (ctx, property) { 10717 var fill = this[property + "Color"], 10718 object = this[property + "Image"], 10719 v = this.viewportTransform, 10720 needsVpt = this[property + "Vpt"]; 10721 if (!fill && !object) { 10722 return; 10723 } 10724 if (fill) { 10725 ctx.save(); 10726 ctx.beginPath(); 10727 ctx.moveTo(0, 0); 10728 ctx.lineTo(this.width, 0); 10729 ctx.lineTo(this.width, this.height); 10730 ctx.lineTo(0, this.height); 10731 ctx.closePath(); 10732 ctx.fillStyle = fill.toLive ? fill.toLive(ctx, this) : fill; 10733 if (needsVpt) { 10734 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 10735 } 10736 ctx.transform(1, 0, 0, 1, fill.offsetX || 0, fill.offsetY || 0); 10737 var m = fill.gradientTransform || fill.patternTransform; 10738 m && ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); 10739 ctx.fill(); 10740 ctx.restore(); 10741 } 10742 if (object) { 10743 ctx.save(); 10744 if (needsVpt) { 10745 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 10746 } 10747 object.render(ctx); 10748 ctx.restore(); 10749 } 10750 }, 10751 10752 /** 10753 * @private 10754 * @param {CanvasRenderingContext2D} ctx Context to render on 10755 */ 10756 _renderBackground: function (ctx) { 10757 this._renderBackgroundOrOverlay(ctx, "background"); 10758 }, 10759 10760 /** 10761 * @private 10762 * @param {CanvasRenderingContext2D} ctx Context to render on 10763 */ 10764 _renderOverlay: function (ctx) { 10765 this._renderBackgroundOrOverlay(ctx, "overlay"); 10766 }, 10767 10768 /** 10769 * Returns coordinates of a center of canvas. 10770 * Returned value is an object with top and left properties 10771 * @return {Object} object with "top" and "left" number values 10772 * @deprecated migrate to `getCenterPoint` 10773 */ 10774 getCenter: function () { 10775 return { 10776 top: this.height / 2, 10777 left: this.width / 2, 10778 }; 10779 }, 10780 10781 /** 10782 * Returns coordinates of a center of canvas. 10783 * @return {fabric.Point} 10784 */ 10785 getCenterPoint: function () { 10786 return new fabric.Point(this.width / 2, this.height / 2); 10787 }, 10788 10789 /** 10790 * Centers object horizontally in the canvas 10791 * @param {fabric.Object} object Object to center horizontally 10792 * @return {fabric.Canvas} thisArg 10793 */ 10794 centerObjectH: function (object) { 10795 return this._centerObject( 10796 object, 10797 new fabric.Point(this.getCenterPoint().x, object.getCenterPoint().y), 10798 ); 10799 }, 10800 10801 /** 10802 * Centers object vertically in the canvas 10803 * @param {fabric.Object} object Object to center vertically 10804 * @return {fabric.Canvas} thisArg 10805 * @chainable 10806 */ 10807 centerObjectV: function (object) { 10808 return this._centerObject( 10809 object, 10810 new fabric.Point(object.getCenterPoint().x, this.getCenterPoint().y), 10811 ); 10812 }, 10813 10814 /** 10815 * Centers object vertically and horizontally in the canvas 10816 * @param {fabric.Object} object Object to center vertically and horizontally 10817 * @return {fabric.Canvas} thisArg 10818 * @chainable 10819 */ 10820 centerObject: function (object) { 10821 var center = this.getCenterPoint(); 10822 return this._centerObject(object, center); 10823 }, 10824 10825 /** 10826 * Centers object vertically and horizontally in the viewport 10827 * @param {fabric.Object} object Object to center vertically and horizontally 10828 * @return {fabric.Canvas} thisArg 10829 * @chainable 10830 */ 10831 viewportCenterObject: function (object) { 10832 var vpCenter = this.getVpCenter(); 10833 return this._centerObject(object, vpCenter); 10834 }, 10835 10836 /** 10837 * Centers object horizontally in the viewport, object.top is unchanged 10838 * @param {fabric.Object} object Object to center vertically and horizontally 10839 * @return {fabric.Canvas} thisArg 10840 * @chainable 10841 */ 10842 viewportCenterObjectH: function (object) { 10843 var vpCenter = this.getVpCenter(); 10844 this._centerObject( 10845 object, 10846 new fabric.Point(vpCenter.x, object.getCenterPoint().y), 10847 ); 10848 return this; 10849 }, 10850 10851 /** 10852 * Centers object Vertically in the viewport, object.top is unchanged 10853 * @param {fabric.Object} object Object to center vertically and horizontally 10854 * @return {fabric.Canvas} thisArg 10855 * @chainable 10856 */ 10857 viewportCenterObjectV: function (object) { 10858 var vpCenter = this.getVpCenter(); 10859 10860 return this._centerObject( 10861 object, 10862 new fabric.Point(object.getCenterPoint().x, vpCenter.y), 10863 ); 10864 }, 10865 10866 /** 10867 * Calculate the point in canvas that correspond to the center of actual viewport. 10868 * @return {fabric.Point} vpCenter, viewport center 10869 * @chainable 10870 */ 10871 getVpCenter: function () { 10872 var center = this.getCenterPoint(), 10873 iVpt = invertTransform(this.viewportTransform); 10874 return transformPoint(center, iVpt); 10875 }, 10876 10877 /** 10878 * @private 10879 * @param {fabric.Object} object Object to center 10880 * @param {fabric.Point} center Center point 10881 * @return {fabric.Canvas} thisArg 10882 * @chainable 10883 */ 10884 _centerObject: function (object, center) { 10885 object.setPositionByOrigin(center, "center", "center"); 10886 object.setCoords(); 10887 this.renderOnAddRemove && this.requestRenderAll(); 10888 return this; 10889 }, 10890 10891 /** 10892 * Returns dataless JSON representation of canvas 10893 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 10894 * @return {String} json string 10895 */ 10896 toDatalessJSON: function (propertiesToInclude) { 10897 return this.toDatalessObject(propertiesToInclude); 10898 }, 10899 10900 /** 10901 * Returns object representation of canvas 10902 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 10903 * @return {Object} object representation of an instance 10904 */ 10905 toObject: function (propertiesToInclude) { 10906 return this._toObjectMethod("toObject", propertiesToInclude); 10907 }, 10908 10909 /** 10910 * Returns dataless object representation of canvas 10911 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 10912 * @return {Object} object representation of an instance 10913 */ 10914 toDatalessObject: function (propertiesToInclude) { 10915 return this._toObjectMethod("toDatalessObject", propertiesToInclude); 10916 }, 10917 10918 /** 10919 * @private 10920 */ 10921 _toObjectMethod: function (methodName, propertiesToInclude) { 10922 var clipPath = this.clipPath, 10923 data = { 10924 version: fabric.version, 10925 objects: this._toObjects(methodName, propertiesToInclude), 10926 }; 10927 if (clipPath && !clipPath.excludeFromExport) { 10928 data.clipPath = this._toObject( 10929 this.clipPath, 10930 methodName, 10931 propertiesToInclude, 10932 ); 10933 } 10934 extend( 10935 data, 10936 this.__serializeBgOverlay(methodName, propertiesToInclude), 10937 ); 10938 10939 fabric.util.populateWithProperties(this, data, propertiesToInclude); 10940 10941 return data; 10942 }, 10943 10944 /** 10945 * @private 10946 */ 10947 _toObjects: function (methodName, propertiesToInclude) { 10948 return this._objects 10949 .filter(function (object) { 10950 return !object.excludeFromExport; 10951 }) 10952 .map(function (instance) { 10953 return this._toObject(instance, methodName, propertiesToInclude); 10954 }, this); 10955 }, 10956 10957 /** 10958 * @private 10959 */ 10960 _toObject: function (instance, methodName, propertiesToInclude) { 10961 var originalValue; 10962 10963 if (!this.includeDefaultValues) { 10964 originalValue = instance.includeDefaultValues; 10965 instance.includeDefaultValues = false;
10966 } 10967 10968 var object = instance[methodName](propertiesToInclude); 10969 if (!this.includeDefaultValues) { 10970 instance.includeDefaultValues = originalValue; 10971 } 10972 return object; 10973 }, 10974 10975 /** 10976 * @private 10977 */ 10978 __serializeBgOverlay: function (methodName, propertiesToInclude) { 10979 var data = {}, 10980 bgImage = this.backgroundImage, 10981 overlayImage = this.overlayImage, 10982 bgColor = this.backgroundColor, 10983 overlayColor = this.overlayColor; 10984 10985 if (bgColor && bgColor.toObject) { 10986 if (!bgColor.excludeFromExport) { 10987 data.background = bgColor.toObject(propertiesToInclude); 10988 } 10989 } else if (bgColor) { 10990 data.background = bgColor; 10991 } 10992 10993 if (overlayColor && overlayColor.toObject) { 10994 if (!overlayColor.excludeFromExport) { 10995 data.overlay = overlayColor.toObject(propertiesToInclude); 10996 } 10997 } else if (overlayColor) { 10998 data.overlay = overlayColor; 10999 } 11000 11001 if (bgImage && !bgImage.excludeFromExport) { 11002 data.backgroundImage = this._toObject( 11003 bgImage, 11004 methodName, 11005 propertiesToInclude, 11006 ); 11007 } 11008 if (overlayImage && !overlayImage.excludeFromExport) { 11009 data.overlayImage = this._toObject( 11010 overlayImage, 11011 methodName, 11012 propertiesToInclude, 11013 ); 11014 } 11015 11016 return data; 11017 }, 11018 11019 /* _TO_SVG_START_ */ 11020 /** 11021 * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, 11022 * a zoomed canvas will then produce zoomed SVG output. 11023 * @type Boolean 11024 * @default 11025 */ 11026 svgViewportTransformation: true, 11027 11028 /** 11029 * Returns SVG representation of canvas 11030 * @function 11031 * @param {Object} [options] Options object for SVG output 11032 * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included 11033 * @param {Object} [options.viewBox] SVG viewbox object 11034 * @param {Number} [options.viewBox.x] x-coordinate of viewbox 11035 * @param {Number} [options.viewBox.y] y-coordinate of viewbox 11036 * @param {Number} [options.viewBox.width] Width of viewbox 11037 * @param {Number} [options.viewBox.height] Height of viewbox 11038 * @param {String} [options.encoding=UTF-8] Encoding of SVG output 11039 * @param {String} [options.width] desired width of svg with or without units 11040 * @param {String} [options.height] desired height of svg with or without units 11041 * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object c
11041onverted into svg representation. 11042 * @return {String} SVG string 11043 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization} 11044 * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} 11045 * @example <caption>Normal SVG output</caption> 11046 * var svg = canvas.toSVG(); 11047 * @example <caption>SVG output without preamble (without <?xml ../>)</caption> 11048 * var svg = canvas.toSVG({suppressPreamble: true}); 11049 * @example <caption>SVG output with viewBox attribute</caption> 11050 * var svg = canvas.toSVG({ 11051 * viewBox: { 11052 * x: 100, 11053 * y: 100, 11054 * width: 200, 11055 * height: 300 11056 * } 11057 * }); 11058 * @example <caption>SVG output with different encoding (default: UTF-8)</caption> 11059 * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); 11060 * @example <caption>Modify SVG output with reviver function</caption> 11061 * var svg = canvas.toSVG(null, function(svg) { 11062 * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); 11063 * }); 11064 */ 11065 toSVG: function (options, reviver) { 11066 options || (options = {}); 11067 options.reviver = reviver; 11068 var markup = []; 11069 11070 this._setSVGPreamble(markup, options); 11071 this._setSVGHeader(markup, options); 11072 if (this.clipPath) { 11073 markup.push( 11074 '<g clip-path="url(#' + this.clipPath.clipPathId + ')" >\n', 11075 ); 11076 } 11077 this._setSVGBgOverlayColor(markup, "background"); 11078 this._setSVGBgOverlayImage(markup, "backgroundImage", reviver); 11079 this._setSVGObjects(markup, reviver); 11080 if (this.clipPath) { 11081 markup.push("</g>\n"); 11082 } 11083 this._setSVGBgOverlayColor(markup, "overlay"); 11084 this._setSVGBgOverlayImage(markup, "overlayImage", reviver); 11085 11086 markup.push("</svg>"); 11087 11088 return markup.join(""); 11089 }, 11090 11091 /** 11092 * @private 11093 */ 11094 _setSVGPreamble: function (markup, options) { 11095 if (options.suppressPreamble) { 11096 return; 11097 } 11098 markup.push( 11099 '<?xml version="1.0" encoding="', 11100 options.encoding || "UTF-8", 11101 '" standalone="no" ?>\n', 11102 '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ', 11103 '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n', 11104 ); 11105 }, 11106 11107 /** 11108 * @private 11109 */ 11110 _setSVGHeader: function (markup, options) { 11111 var width = options.width || this.width, 11112 height = options.height || this.height, 11113 vpt,
vendor: 1,234 bytes, lines 11114-11154
11114 viewBox = 'viewBox="0 0 ' + this.width + " " + this.height + '" ', 11115 NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; 11116 11117 if (options.viewBox) { 11118 viewBox = 11119 'viewBox="' + 11120 options.viewBox.x + 11121 " " + 11122 options.viewBox.y + 11123 " " + 11124 options.viewBox.width + 11125 " " + 11126 options.viewBox.height + 11127 '" '; 11128 } else { 11129 if (this.svgViewportTransformation) { 11130 vpt = this.viewportTransform; 11131 viewBox = 11132 'viewBox="' + 11133 toFixed(-vpt[4] / vpt[0], NUM_FRACTION_DIGITS) + 11134 " " + 11135 toFixed(-vpt[5] / vpt[3], NUM_FRACTION_DIGITS) + 11136 " " + 11137 toFixed(this.width / vpt[0], NUM_FRACTION_DIGITS) + 11138 " " + 11139 toFixed(this.height / vpt[3], NUM_FRACTION_DIGITS) + 11140 '" '; 11141 } 11142 } 11143 11144 markup.push( 11145 "<svg ", 11146 'xmlns="http://www.w3.org/2000/svg" ', 11147 'xmlns:xlink="http://www.w3.org/1999/xlink" ', 11148 'version="1.1" ', 11149 'width="', 11150 width, 11151 '" ', 11152 'height="', 11153 height, 11154 '" ',
11155 viewBox, 11156 'xml:space="preserve">\n', 11157 "<desc>Created with Fabric.js ", 11158 fabric.version, 11159 "</desc>\n", 11160 "<defs>\n", 11161 this.createSVGFontFacesMarkup(), 11162 this.createSVGRefElementsMarkup(), 11163 this.createSVGClipPathMarkup(options), 11164 "</defs>\n", 11165 ); 11166 }, 11167 11168 createSVGClipPathMarkup: function (options) { 11169 var clipPath = this.clipPath; 11170 if (clipPath) { 11171 clipPath.clipPathId = "CLIPPATH_" + fabric.Object.__uid++; 11172 return ( 11173 '<clipPath id="' + 11174 clipPath.clipPathId + 11175 '" >\n' + 11176 this.clipPath.toClipPathSVG(options.reviver) + 11177 "</clipPath>\n" 11178 ); 11179 } 11180 return ""; 11181 }, 11182 11183 /** 11184 * Creates markup containing SVG referenced elements like patterns, gradients etc. 11185 * @return {String} 11186 */ 11187 createSVGRefElementsMarkup: function () { 11188 var _this = this, 11189 markup = ["background", "overlay"].map(function (prop) { 11190 var fill = _this[prop + "Color"]; 11191 if (fill && fill.toLive) { 11192 var shouldTransform = _this[prop + "Vpt"], 11193 vpt = _this.viewportTransform, 11194 object = { 11195 width: _this.width / (shouldTransform ? vpt[0] : 1), 11196 height: _this.height / (shouldTransform ? vpt[3] : 1), 11197 }; 11198 return fill.toSVG(object, { 11199 additionalTransform: shouldTransform 11200 ? fabric.util.matrixToSVG(vpt) 11201 : "", 11202 }); 11203 } 11204 }); 11205 return markup.join(""); 11206 }, 11207 11208 /** 11209 * Creates markup containing SVG font faces, 11210 * font URLs for font faces must be collected by developers 11211 * and are not extracted from the DOM by fabricjs 11212 * @param {Array} objects Array of fabric objects 11213 * @return {String} 11214 */ 11215 createSVGFontFacesMarkup: function () { 11216 var markup = "", 11217 fontList = {}, 11218 obj, 11219 fontFamily, 11220 style, 11221 row, 11222 rowIndex, 11223 _char, 11224 charIndex, 11225 i, 11226 len, 11227 fontPaths = fabric.fontPaths, 11228 objects = []; 11229 11230 this._objects.forEach(function add(object) { 11231 objects.push(object); 11232 if (object._objects) { 11233 object._objects.forEach(add); 11234 } 11235 }); 11236 11237 for (i = 0, len = objects.length; i < len; i++) { 11238 obj = objects[i]; 11239 fontFamily = obj.fontFamily; 11240 /* Add fontfamily in SVG def in every */ 11241 if ( 11242 obj.type.toLowerCase().indexOf("text") === -1 || 11243 fontList[fontFamily] || 11244 !fontPaths[fontFamily] 11245 ) { 11246 continue; 11247 } 11248 fontList[fontFamily] = true; 11249 if (!obj.styles) { 11250 continue; 11251 } 11252 style = obj.styles; 11253 for (rowIndex in style) { 11254 row = style[rowIndex]; 11255 for (charIndex in row) { 11256 _char = row[charIndex]; 11257 fontFamily = _char.fontFamily; 11258 if (!fontList[fontFamily] && fontPaths[fontFamily]) { 11259 fontList[fontFamily] = true; 11260 } 11261 } 11262 } 11263 } 11264 11265 for (var j in fontList) { 11266 markup += [ 11267 "\t\t@font-face {\n", 11268 "\t\t\tfont-family: '", 11269 j, 11270 "';\n", 11271 "\t\t\tsrc: url('", 11272 fontPaths[j], 11273 "');\n", 11274 "\t\t}\n", 11275 ].join(""); 11276 } 11277 11278 if (markup) { 11279 markup = [ 11280 '\t<style type="text/css">', 11281 "<![CDATA[\n", 11282 markup, 11283 "]]>", 11284 "</style>\n", 11285 ].join(""); 11286 } 11287 11288 return markup; 11289 }, 11290 11291 /** 11292 * @private 11293 */ 11294 _setSVGObjects: function (markup, reviver) { 11295 var instance, 11296 i, 11297 len, 11298 objects = this._objects; 11299 for (i = 0, len = objects.length; i < len; i++) { 11300 instance = objects[i]; 11301 if (instance.excludeFromExport) { 11302 continue; 11303 } 11304 this._setSVGObject(markup, instance, reviver); 11305 } 11306 }, 11307 11308 /** 11309 * @private 11310 */ 11311 _setSVGObject: function (markup, instance, reviver) { 11312 markup.push(instance.toSVG(reviver)); 11313 }, 11314 11315 /** 11316 * @private 11317 */ 11318 _setSVGBgOverlayImage: function (markup, property, reviver) { 11319 if ( 11320 this[property] && 11321 !this[property].excludeFromExport && 11322 this[property].toSVG 11323 ) { 11324 markup.push(this[property].toSVG(reviver)); 11325 } 11326 }, 11327 11328 /** 11329 * @private 11330 */ 11331 _setSVGBgOverlayColor: function (markup, property) { 11332 var filler = this[property + "Color"], 11333 vpt = this.viewportTransform, 11334 finalWidth = this.width, 11335 finalHeight = this.height; 11336 if (!filler) { 11337 return; 11338 } 11339 if (filler.toLive) { 11340 var repeat = filler.repeat, 11341 iVpt = fabric.util.invertTransform(vpt), 11342 shouldInvert = this[property + "Vpt"], 11343 additionalTransform = shouldInvert 11344 ? fabric.util.matrixToSVG(iVpt) 11345 : ""; 11346 markup.push( 11347 '<rect transform="' + additionalTransform + " translate(", 11348 finalWidth / 2, 11349 ",", 11350 finalHeight / 2, 11351 ')"', 11352 ' x="',
vendor: 8,373 bytes, lines 11353-11612
11353 filler.offsetX - finalWidth / 2, 11354 '" y="', 11355 filler.offsetY - finalHeight / 2, 11356 '" ', 11357 'width="', 11358 repeat === "repeat-y" || repeat === "no-repeat" 11359 ? filler.source.width 11360 : finalWidth, 11361 '" height="', 11362 repeat === "repeat-x" || repeat === "no-repeat" 11363 ? filler.source.height 11364 : finalHeight, 11365 '" fill="url(#SVGID_' + filler.id + ')"', 11366 "></rect>\n", 11367 ); 11368 } else { 11369 markup.push( 11370 '<rect x="0" y="0" width="100%" height="100%" ', 11371 'fill="', 11372 filler, 11373 '"', 11374 "></rect>\n", 11375 ); 11376 } 11377 }, 11378 /* _TO_SVG_END_ */ 11379 11380 /** 11381 * Moves an object or the objects of a multiple selection 11382 * to the bottom of the stack of drawn objects 11383 * @param {fabric.Object} object Object to send to back 11384 * @return {fabric.Canvas} thisArg 11385 * @chainable 11386 */ 11387 sendToBack: function (object) { 11388 if (!object) { 11389 return this; 11390 } 11391 var activeSelection = this._activeObject, 11392 i, 11393 obj, 11394 objs; 11395 if (object === activeSelection && object.type === "activeSelection") { 11396 objs = activeSelection._objects; 11397 for (i = objs.length; i--; ) { 11398 obj = objs[i]; 11399 removeFromArray(this._objects, obj); 11400 this._objects.unshift(obj); 11401 } 11402 } else { 11403 removeFromArray(this._objects, object); 11404 this._objects.unshift(object); 11405 } 11406 this.renderOnAddRemove && this.requestRenderAll(); 11407 return this; 11408 }, 11409 11410 /** 11411 * Moves an object or the objects of a multiple selection 11412 * to the top of the stack of drawn objects 11413 * @param {fabric.Object} object Object to send 11414 * @return {fabric.Canvas} thisArg 11415 * @chainable 11416 */ 11417 bringToFront: function (object) { 11418 if (!object) { 11419 return this; 11420 } 11421 var activeSelection = this._activeObject, 11422 i, 11423 obj, 11424 objs; 11425 if (object === activeSelection && object.type === "activeSelection") { 11426 objs = activeSelection._objects; 11427 for (i = 0; i < objs.length; i++) { 11428 obj = objs[i]; 11429 removeFromArray(this._objects, obj); 11430 this._objects.push(obj); 11431 } 11432 } else { 11433 removeFromArray(this._objects, object); 11434 this._objects.push(object); 11435 } 11436 this.renderOnAddRemove && this.requestRenderAll(); 11437 return this; 11438 }, 11439 11440 /** 11441 * Moves an object or a selection down in stack of drawn objects 11442 * An optional parameter, intersecting allows to move the object in behind 11443 * the first intersecting object. Where intersection is calculated with 11444 * bounding box. If no intersection is found, there will not be change in the 11445 * stack. 11446 * @param {fabric.Object} object Object to send 11447 * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object 11448 * @return {fabric.Canvas} thisArg 11449 * @chainable 11450 */ 11451 sendBackwards: function (object, intersecting) { 11452 if (!object) { 11453 return this; 11454 } 11455 var activeSelection = this._activeObject, 11456 i, 11457 obj, 11458 idx, 11459 newIdx, 11460 objs, 11461 objsMoved = 0; 11462 11463 if (object === activeSelection && object.type === "activeSelection") { 11464 objs = activeSelection._objects; 11465 for (i = 0; i < objs.length; i++) { 11466 obj = objs[i]; 11467 idx = this._objects.indexOf(obj); 11468 if (idx > 0 + objsMoved) { 11469 newIdx = idx - 1; 11470 removeFromArray(this._objects, obj); 11471 this._objects.splice(newIdx, 0, obj); 11472 } 11473 objsMoved++; 11474 } 11475 } else { 11476 idx = this._objects.indexOf(object); 11477 if (idx !== 0) { 11478 // if object is not on the bottom of stack 11479 newIdx = this._findNewLowerIndex(object, idx, intersecting); 11480 removeFromArray(this._objects, object); 11481 this._objects.splice(newIdx, 0, object); 11482 } 11483 } 11484 this.renderOnAddRemove && this.requestRenderAll(); 11485 return this; 11486 }, 11487 11488 /** 11489 * @private 11490 */ 11491 _findNewLowerIndex: function (object, idx, intersecting) { 11492 var newIdx, i; 11493 11494 if (intersecting) { 11495 newIdx = idx; 11496 11497 // traverse down the stack looking for the nearest intersecting object 11498 for (i = idx - 1; i >= 0; --i) { 11499 var isIntersecting = 11500 object.intersectsWithObject(this._objects[i]) || 11501 object.isContainedWithinObject(this._objects[i]) || 11502 this._objects[i].isContainedWithinObject(object); 11503 11504 if (isIntersecting) { 11505 newIdx = i; 11506 break; 11507 } 11508 } 11509 } else { 11510 newIdx = idx - 1; 11511 } 11512 11513 return newIdx; 11514 }, 11515 11516 /** 11517 * Moves an object or a selection up in stack of drawn objects 11518 * An optional parameter, intersecting allows to move the object in front 11519 * of the first intersecting object. Where intersection is calculated with 11520 * bounding box. If no intersection is found, there will not be change in the 11521 * stack. 11522 * @param {fabric.Object} object Object to send 11523 * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object 11524 * @return {fabric.Canvas} thisArg 11525 * @chainable 11526 */ 11527 bringForward: function (object, intersecting) { 11528 if (!object) { 11529 return this; 11530 } 11531 var activeSelection = this._activeObject, 11532 i, 11533 obj, 11534 idx, 11535 newIdx, 11536 objs, 11537 objsMoved = 0; 11538 11539 if (object === activeSelection && object.type === "activeSelection") { 11540 objs = activeSelection._objects; 11541 for (i = objs.length; i--; ) { 11542 obj = objs[i]; 11543 idx = this._objects.indexOf(obj); 11544 if (idx < this._objects.length - 1 - objsMoved) { 11545 newIdx = idx + 1; 11546 removeFromArray(this._objects, obj); 11547 this._objects.splice(newIdx, 0, obj); 11548 } 11549 objsMoved++; 11550 } 11551 } else { 11552 idx = this._objects.indexOf(object); 11553 if (idx !== this._objects.length - 1) { 11554 // if object is not on top of stack (last item in an array) 11555 newIdx = this._findNewUpperIndex(object, idx, intersecting); 11556 removeFromArray(this._objects, object); 11557 this._objects.splice(newIdx, 0, object); 11558 } 11559 } 11560 this.renderOnAddRemove && this.requestRenderAll(); 11561 return this; 11562 }, 11563 11564 /** 11565 * @private 11566 */ 11567 _findNewUpperIndex: function (object, idx, intersecting) { 11568 var newIdx, i, len; 11569 11570 if (intersecting) { 11571 newIdx = idx; 11572 11573 // traverse up the stack looking for the nearest intersecting object 11574 for (i = idx + 1, len = this._objects.length; i < len; ++i) { 11575 var isIntersecting = 11576 object.intersectsWithObject(this._objects[i]) || 11577 object.isContainedWithinObject(this._objects[i]) || 11578 this._objects[i].isContainedWithinObject(object); 11579 11580 if (isIntersecting) { 11581 newIdx = i; 11582 break; 11583 } 11584 } 11585 } else { 11586 newIdx = idx + 1; 11587 } 11588 11589 return newIdx; 11590 }, 11591 11592 /** 11593 * Moves an object to specified level in stack of drawn objects 11594 * @param {fabric.Object} object Object to send 11595 * @param {Number} index Position to move to 11596 * @return {fabric.Canvas} thisArg 11597 * @chainable 11598 */ 11599 moveTo: function (object, index) { 11600 removeFromArray(this._objects, object); 11601 this._objects.splice(index, 0, object); 11602 return this.renderOnAddRemove && this.requestRenderAll(); 11603 }, 11604 11605 /** 11606 * Clears a canvas element and dispose objects 11607 * @return {fabric.Canvas} thisArg 11608 * @chainable 11609 */ 11610 dispose: function () { 11611 // cancel eventually ongoing renders 11612 if (this.isRendering) {
11613 fabric.util.cancelAnimFrame(this.isRendering); 11614 this.isRendering = 0; 11615 } 11616 this.forEachObject(function (object) { 11617 object.dispose && object.dispose(); 11618 }); 11619 this._objects = []; 11620 if (this.backgroundImage && this.backgroundImage.dispose) { 11621 this.backgroundImage.dispose(); 11622 } 11623 this.backgroundImage = null; 11624 if (this.overlayImage && this.overlayImage.dispose) { 11625 this.overlayImage.dispose(); 11626 } 11627 this.overlayImage = null; 11628 this._iTextInstances = null; 11629 this.contextContainer = null; 11630 // restore canvas style 11631 this.lowerCanvasEl.classList.remove("lower-canvas"); 11632 fabric.util.setStyle(this.lowerCanvasEl, this._originalCanvasStyle); 11633 delete this._originalCanvasStyle; 11634 // restore canvas size to original size in case retina scaling was applied 11635 this.lowerCanvasEl.setAttribute("width", this.width); 11636 this.lowerCanvasEl.setAttribute("height", this.height); 11637 fabric.util.cleanUpJsdomNode(this.lowerCanvasEl); 11638 this.lowerCanvasEl = undefined; 11639 return this; 11640 }, 11641 11642 /** 11643 * Returns a string representation of an instance 11644 * @return {String} string representation of an instance 11645 */ 11646 toString: function () { 11647 return ( 11648 "#<fabric.Canvas (" + 11649 this.complexity() + 11650 "): " + 11651 "{ objects: " + 11652 this._objects.length + 11653 " }>" 11654 ); 11655 }, 11656 }, 11657 ); 11658 11659 extend(fabric.StaticCanvas.prototype, fabric.Observable); 11660 extend(fabric.StaticCanvas.prototype, fabric.Collection); 11661 extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); 11662 11663 extend( 11664 fabric.StaticCanvas, 11665 /** @lends fabric.StaticCanvas */ { 11666 /** 11667 * @static
11668 * @type String 11669 * @default 11670 */ 11671 EMPTY_JSON: '{"objects": [], "background": "white"}', 11672 11673 /** 11674 * Provides a way to check support of some of the canvas methods 11675 * (either those of HTMLCanvasElement itself, or rendering context) 11676 * 11677 * @param {String} methodName Method to check support for; 11678 * Could be one of "setLineDash" 11679 * @return {Boolean | null} `true` if method is supported (or at least exists), 11680 * `null` if canvas element or context can not be initialized 11681 */ 11682 supports: function (methodName) { 11683 var el = createCanvasElement(); 11684 11685 if (!el || !el.getContext) { 11686 return null; 11687 } 11688 11689 var ctx = el.getContext("2d"); 11690 if (!ctx) { 11691 return null; 11692 } 11693 11694 switch (methodName) { 11695 case "setLineDash": 11696 return typeof ctx.setLineDash !== "undefined"; 11697 11698 default: 11699 return null; 11700 } 11701 }, 11702 }, 11703 ); 11704 11705 /** 11706 * Returns Object representation of canvas 11707 * this alias is provided because if you call JSON.stringify on an instance, 11708 * the toJSON object will be invoked if it exists. 11709 * Having a toJSON method means you can do JSON.stringify(myCanvas) 11710 * @function 11711 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 11712 * @return {Object} JSON compatible object 11713 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization} 11714 * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} 11715 * @example <caption>JSON without additional properties</caption> 11716 * var json = canvas.toJSON(); 11717 * @example <caption>JSON with additional properties included</caption> 11718 * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY']); 11719 * @example <caption>JSON without default values</caption> 11720 * canvas.includeDefaultValues = false; 11721 * var json = canvas.toJSON(); 11722 */ 11723 fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; 11724 11725 if (fabric.isLikelyNode) { 11726 fabric.StaticCanvas.prototype.createPNGStream = function () { 11727 var impl = getNodeCanvas(this.lowerCanvasEl); 11728 return impl && impl.createPNGStream(); 11729 }; 11730 fabric.StaticCanvas.prototype.createJPEGStream = function (opts) { 11731 var impl = getNodeCanvas(this.lowerCanvasEl); 11732 return impl && impl.createJPEGStream(opts); 11733 }; 11734 } 11735})(); 11736 11737/** 11738 * BaseBrush class 11739 * @class fabric.BaseBrush 11740 * @see {@link http://fabricjs.com/freedrawing|Freedrawing demo} 11741 */ 11742fabric.BaseBrush = fabric.util.createClass( 11743 /** @lends fabric.BaseBrush.prototype */ { 11744 /** 11745 * Color of a brush
11746 * @type String 11747 * @default 11748 */ 11749 color: "rgb(0, 0, 0)", 11750 11751 /** 11752 * Width of a brush, has to be a Number, no string literals 11753 * @type Number 11754 * @default 11755 */ 11756 width: 1, 11757 11758 /** 11759 * Shadow object representing shadow of this shape. 11760 * <b>Backwards incompatibility note:</b> This property replaces "shadowColor" (String), "shadowOffsetX" (Number), 11761 * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 11762 * @type fabric.Shadow 11763 * @default 11764 */ 11765 shadow: null, 11766 11767 /** 11768 * Line endings style of a brush (one of "butt", "round", "square") 11769 * @type String 11770 * @default 11771 */ 11772 strokeLineCap: "round", 11773 11774 /** 11775 * Corner style of a brush (one of "bevel", "round", "miter") 11776 * @type String 11777 * @default 11778 */ 11779 strokeLineJoin: "round", 11780 11781 /** 11782 * Maximum miter length (used for strokeLineJoin = "miter") of a brush's 11783 * @type Number 11784 * @default 11785 */ 11786 strokeMiterLimit: 10, 11787 11788 /** 11789 * Stroke Dash Array. 11790 * @type Array 11791 * @default 11792 */ 11793 strokeDashArray: null, 11794 11795 /** 11796 * When `true`, the free drawing is limited to the whiteboard size. Default to false. 11797 * @type Boolean 11798 * @default false 11799 */ 11800 11801 limitedToCanvasSize: false, 11802 11803 /** 11804 * Sets brush styles 11805 * @private 11806 * @param {CanvasRenderingContext2D} ctx 11807 */ 11808 _setBrushStyles: function (ctx) { 11809 ctx.strokeStyle = this.color; 11810 ctx.lineWidth = this.width; 11811 ctx.lineCap = this.strokeLineCap; 11812 ctx.miterLimit = this.strokeMiterLimit; 11813 ctx.lineJoin = this.strokeLineJoin; 11814 ctx.setLineDash(this.strokeDashArray || []); 11815 }, 11816 11817 /** 11818 * Sets the transformation on given context 11819 * @param {RenderingContext2d} ctx context to render on 11820 * @private 11821 */ 11822 _saveAndTransform: function (ctx) { 11823 var v = this.canvas.viewportTransform; 11824 ctx.save(); 11825 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 11826 }, 11827 11828 /** 11829 * Sets brush shadow styles 11830 * @private 11831 */ 11832 _setShadow: function () { 11833 if (!this.shadow) { 11834 return; 11835 } 11836 11837 var canvas = this.canvas, 11838 shadow = this.shadow, 11839 ctx = canvas.contextTop, 11840 zoom = canvas.getZoom(); 11841 if (canvas && canvas._isRetinaScaling()) { 11842 zoom *= fabric.devicePixelRatio; 11843 } 11844 11845 ctx.shadowColor = shadow.color; 11846 ctx.shadowBlur = shadow.blur * zoom; 11847 ctx.shadowOffsetX = shadow.offsetX * zoom; 11848 ctx.shadowOffsetY = shadow.offsetY * zoom; 11849 }, 11850 11851 needsFullRender: function () { 11852 var color = new fabric.Color(this.color); 11853 return color.getAlpha() < 1 || !!this.shadow; 11854 }, 11855 11856 /** 11857 * Removes brush shadow styles 11858 * @private 11859 */ 11860 _resetShadow: function () { 11861 var ctx = this.canvas.contextTop; 11862 11863 ctx.shadowColor = ""; 11864 ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; 11865 }, 11866 11867 /** 11868 * Check is pointer is outside canvas boundaries 11869 * @param {Object} pointer 11870 * @private 11871 */ 11872 _isOutSideCanvas: function (pointer) { 11873 return ( 11874 pointer.x < 0 || 11875 pointer.x > this.canvas.getWidth() || 11876 pointer.y < 0 || 11877 pointer.y > this.canvas.getHeight() 11878 ); 11879 }, 11880 }, 11881); 11882 11883(function () { 11884 /** 11885 * PencilBrush class 11886 * @class fabric.PencilBrush 11887 * @extends fabric.BaseBrush 11888 */ 11889 fabric.PencilBrush = fabric.util.createClass( 11890 fabric.BaseBrush, 11891 /** @lends fabric.PencilBrush.prototype */ { 11892 /** 11893 * Discard points that are less than `decimate` pixel distant from each other 11894 * @type Number 11895 * @default 0.4 11896 */ 11897 decimate: 0.4, 11898 11899 /**
11900 * Draws a straight line between last recorded point to current pointer 11901 * Used for `shift` functionality 11902 * 11903 * @type boolean 11904 * @default false 11905 */ 11906 drawStraightLine: false, 11907 11908 /** 11909 * The event modifier key that makes the brush draw a straight line. 11910 * If `null` or 'none' or any other string that is not a modifier key the feature is disabled. 11911 * @type {'altKey' | 'shiftKey' | 'ctrlKey' | 'none' | undefined | null} 11912 */ 11913 straightLineKey: "shiftKey", 11914 11915 /** 11916 * Constructor 11917 * @param {fabric.Canvas} canvas 11918 * @return {fabric.PencilBrush} Instance of a pencil brush 11919 */ 11920 initialize: function (canvas) { 11921 this.canvas = canvas; 11922 this._points = []; 11923 }, 11924 11925 needsFullRender: function () { 11926 return this.callSuper("needsFullRender") || this._hasStraightLine; 11927 }, 11928 11929 /** 11930 * Invoked inside on mouse down and mouse move 11931 * @param {Object} pointer 11932 */ 11933 _drawSegment: function (ctx, p1, p2) { 11934 var midPoint = p1.midPointFrom(p2); 11935 ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); 11936 return midPoint; 11937 }, 11938 11939 /** 11940 * Invoked on mouse down 11941 * @param {Object} pointer 11942 */ 11943 onMouseDown: function (pointer, options) { 11944 if (!this.canvas._isMainEvent(options.e)) { 11945 return; 11946 } 11947 this.drawStraightLine = options.e[this.straightLineKey]; 11948 this._prepareForDrawing(pointer); 11949 // capture coordinates immediately 11950 // this allows to draw dots (when movement never occurs) 11951 this._captureDrawingPath(pointer); 11952 this._render(); 11953 }, 11954 11955 /** 11956 * Invoked on mouse move 11957 * @param {Object} pointer 11958 */ 11959 onMouseMove: function (pointer, options) { 11960 if (!this.canvas._isMainEvent(options.e)) { 11961 return; 11962 } 11963 this.drawStraightLine = options.e[this.straightLineKey]; 11964 if ( 11965 this.limitedToCanvasSize === true && 11966 this._isOutSideCanvas(pointer) 11967 ) { 11968 return; 11969 } 11970 if (this._captureDrawingPath(pointer) && this._points.length > 1) { 11971 if (this.needsFullRender()) { 11972 // redraw curve 11973 // clear top canvas 11974 this.canvas.clearContext(this.canvas.contextTop); 11975 this._render(); 11976 } else { 11977 var points = this._points, 11978 length = points.length, 11979 ctx = this.canvas.contextTop; 11980 // draw the curve update 11981 this._saveAndTransform(ctx); 11982 if (this.oldEnd) { 11983 ctx.beginPath(); 11984 ctx.moveTo(this.oldEnd.x, this.oldEnd.y); 11985 } 11986 this.oldEnd = this._drawSegment( 11987 ctx, 11988 points[length - 2], 11989 points[length - 1], 11990 true, 11991 ); 11992 ctx.stroke(); 11993 ctx.restore(); 11994 } 11995 } 11996 }, 11997 11998 /** 11999 * Invoked on mouse up 12000 */ 12001 onMouseUp: function (options) { 12002 if (!this.canvas._isMainEvent(options.e)) { 12003 return true; 12004 } 12005 this.drawStraightLine = false; 12006 this.oldEnd = undefined; 12007 this._finalizeAndAddPath(); 12008 return false; 12009 }, 12010 12011 /** 12012 * @private 12013 * @param {Object} pointer Actual mouse position related to the canvas. 12014 */ 12015 _prepareForDrawing: function (pointer) { 12016 var p = new fabric.Point(pointer.x, pointer.y); 12017 12018 this._reset(); 12019 this._addPoint(p); 12020 this.canvas.contextTop.moveTo(p.x, p.y); 12021 }, 12022 12023 /** 12024 * @private 12025 * @param {fabric.Point} point Point to be added to points array 12026 */ 12027 _addPoint: function (point) { 12028 if ( 12029 this._points.length > 1 && 12030 point.eq(this._points[this._points.length - 1]) 12031 ) { 12032 return false; 12033 } 12034 if (this.drawStraightLine && this._points.length > 1) { 12035 this._hasStraightLine = true; 12036 this._points.pop(); 12037 } 12038 this._points.push(point); 12039 return true; 12040 }, 12041 12042 /** 12043 * Clear points array and set contextTop canvas style. 12044 * @private 12045 */ 12046 _reset: function () { 12047 this._points = []; 12048 this._setBrushStyles(this.canvas.contextTop); 12049 this._setShadow(); 12050 this._hasStraightLine = false;
vendor: 6,193 bytes, lines 12051-12245
12051 }, 12052 12053 /** 12054 * @private 12055 * @param {Object} pointer Actual mouse position related to the canvas. 12056 */ 12057 _captureDrawingPath: function (pointer) { 12058 var pointerPoint = new fabric.Point(pointer.x, pointer.y); 12059 return this._addPoint(pointerPoint); 12060 }, 12061 12062 /** 12063 * Draw a smooth path on the topCanvas using quadraticCurveTo 12064 * @private 12065 * @param {CanvasRenderingContext2D} [ctx] 12066 */ 12067 _render: function (ctx) { 12068 var i, 12069 len, 12070 p1 = this._points[0], 12071 p2 = this._points[1]; 12072 ctx = ctx || this.canvas.contextTop; 12073 this._saveAndTransform(ctx); 12074 ctx.beginPath(); 12075 //if we only have 2 points in the path and they are the same 12076 //it means that the user only clicked the canvas without moving the mouse 12077 //then we should be drawing a dot. A path isn't drawn between two identical dots 12078 //that's why we set them apart a bit 12079 if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { 12080 var width = this.width / 1000; 12081 p1 = new fabric.Point(p1.x, p1.y); 12082 p2 = new fabric.Point(p2.x, p2.y); 12083 p1.x -= width; 12084 p2.x += width; 12085 } 12086 ctx.moveTo(p1.x, p1.y); 12087 12088 for (i = 1, len = this._points.length; i < len; i++) { 12089 // we pick the point between pi + 1 & pi + 2 as the 12090 // end point and p1 as our control point. 12091 this._drawSegment(ctx, p1, p2); 12092 p1 = this._points[i]; 12093 p2 = this._points[i + 1]; 12094 } 12095 // Draw last line as a straight line while 12096 // we wait for the next point to be able to calculate 12097 // the bezier control point 12098 ctx.lineTo(p1.x, p1.y); 12099 ctx.stroke(); 12100 ctx.restore(); 12101 }, 12102 12103 /** 12104 * Converts points to SVG path 12105 * @param {Array} points Array of points 12106 * @return {(string|number)[][]} SVG path commands 12107 */ 12108 convertPointsToSVGPath: function (points) { 12109 var correction = this.width / 1000; 12110 return fabric.util.getSmoothPathFromPoints(points, correction); 12111 }, 12112 12113 /** 12114 * @private 12115 * @param {(string|number)[][]} pathData SVG path commands 12116 * @returns {boolean} 12117 */ 12118 _isEmptySVGPath: function (pathData) { 12119 var pathString = fabric.util.joinPath(pathData); 12120 return pathString === "M 0 0 Q 0 0 0 0 L 0 0"; 12121 }, 12122 12123 /** 12124 * Creates fabric.Path object to add on canvas 12125 * @param {(string|number)[][]} pathData Path data 12126 * @return {fabric.Path} Path to add on canvas 12127 */ 12128 createPath: function (pathData) { 12129 var path = new fabric.Path(pathData, { 12130 fill: null, 12131 stroke: this.color, 12132 strokeWidth: this.width, 12133 strokeLineCap: this.strokeLineCap, 12134 strokeMiterLimit: this.strokeMiterLimit, 12135 strokeLineJoin: this.strokeLineJoin, 12136 strokeDashArray: this.strokeDashArray, 12137 }); 12138 if (this.shadow) { 12139 this.shadow.affectStroke = true; 12140 path.shadow = new fabric.Shadow(this.shadow); 12141 } 12142 12143 return path; 12144 }, 12145 12146 /** 12147 * Decimate points array with the decimate value 12148 */ 12149 decimatePoints: function (points, distance) { 12150 if (points.length <= 2) { 12151 return points; 12152 } 12153 var zoom = this.canvas.getZoom(), 12154 adjustedDistance = Math.pow(distance / zoom, 2), 12155 i, 12156 l = points.length - 1, 12157 lastPoint = points[0], 12158 newPoints = [lastPoint], 12159 cDistance; 12160 for (i = 1; i < l - 1; i++) { 12161 cDistance = 12162 Math.pow(lastPoint.x - points[i].x, 2) + 12163 Math.pow(lastPoint.y - points[i].y, 2); 12164 if (cDistance >= adjustedDistance) { 12165 lastPoint = points[i]; 12166 newPoints.push(lastPoint); 12167 } 12168 } 12169 /** 12170 * Add the last point from the original line to the end of the array. 12171 * This ensures decimate doesn't delete the last point on the line, and ensures the line is > 1 point. 12172 */ 12173 newPoints.push(points[l]); 12174 return newPoints; 12175 }, 12176 12177 /** 12178 * On mouseup after drawing the path on contextTop canvas 12179 * we use the points captured to create an new fabric path object 12180 * and add it to the fabric canvas. 12181 */ 12182 _finalizeAndAddPath: function () { 12183 var ctx = this.canvas.contextTop; 12184 ctx.closePath(); 12185 if (this.decimate) { 12186 this._points = this.decimatePoints(this._points, this.decimate); 12187 } 12188 var pathData = this.convertPointsToSVGPath(this._points); 12189 if (this._isEmptySVGPath(pathData)) { 12190 // do not create 0 width/height paths, as they are 12191 // rendered inconsistently across browsers 12192 // Firefox 4, for example, renders a dot, 12193 // whereas Chrome 10 renders nothing 12194 this.canvas.requestRenderAll(); 12195 return; 12196 } 12197 12198 var path = this.createPath(pathData); 12199 this.canvas.clearContext(this.canvas.contextTop); 12200 this.canvas.fire("before:path:created", { path: path }); 12201 this.canvas.add(path); 12202 this.canvas.requestRenderAll(); 12203 path.setCoords(); 12204 this._resetShadow(); 12205 12206 // fire event 'path' created 12207 this.canvas.fire("path:created", { path: path }); 12208 }, 12209 }, 12210 ); 12211})(); 12212 12213/** 12214 * CircleBrush class 12215 * @class fabric.CircleBrush 12216 */ 12217fabric.CircleBrush = fabric.util.createClass( 12218 fabric.BaseBrush, 12219 /** @lends fabric.CircleBrush.prototype */ { 12220 /** 12221 * Width of a brush 12222 * @type Number 12223 * @default 12224 */ 12225 width: 10, 12226 12227 /** 12228 * Constructor 12229 * @param {fabric.Canvas} canvas 12230 * @return {fabric.CircleBrush} Instance of a circle brush 12231 */ 12232 initialize: function (canvas) { 12233 this.canvas = canvas; 12234 this.points = []; 12235 }, 12236 12237 /** 12238 * Invoked inside on mouse down and mouse move 12239 * @param {Object} pointer 12240 */ 12241 drawDot: function (pointer) { 12242 var point = this.addPoint(pointer), 12243 ctx = this.canvas.contextTop; 12244 this._saveAndTransform(ctx); 12245 this.dot(ctx, point);
12246 ctx.restore(); 12247 }, 12248 12249 dot: function (ctx, point) { 12250 ctx.fillStyle = point.fill; 12251 ctx.beginPath(); 12252 ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); 12253 ctx.closePath(); 12254 ctx.fill(); 12255 }, 12256 12257 /** 12258 * Invoked on mouse down 12259 */ 12260 onMouseDown: function (pointer) { 12261 this.points.length = 0; 12262 this.canvas.clearContext(this.canvas.contextTop); 12263 this._setShadow(); 12264 this.drawDot(pointer); 12265 }, 12266 12267 /** 12268 * Render the full state of the brush 12269 * @private 12270 */ 12271 _render: function () { 12272 var ctx = this.canvas.contextTop, 12273 i, 12274 len, 12275 points = this.points; 12276 this._saveAndTransform(ctx); 12277 for (i = 0, len = points.length; i < len; i++) { 12278 this.dot(ctx, points[i]); 12279 } 12280 ctx.restore(); 12281 }, 12282 12283 /** 12284 * Invoked on mouse move 12285 * @param {Object} pointer 12286 */ 12287 onMouseMove: function (pointer) { 12288 if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { 12289 return; 12290 } 12291 if (this.needsFullRender()) { 12292 this.canvas.clearContext(this.canvas.contextTop); 12293 this.addPoint(pointer); 12294 this._render(); 12295 } else { 12296 this.drawDot(pointer); 12297 } 12298 }, 12299 12300 /** 12301 * Invoked on mouse up 12302 */ 12303 onMouseUp: function () { 12304 var originalRenderOnAddRemove = this.canvas.renderOnAddRemove, 12305 i, 12306 len; 12307 this.canvas.renderOnAddRemove = false; 12308 12309 var circles = []; 12310 12311 for (i = 0, len = this.points.length; i < len; i++) { 12312 var point = this.points[i], 12313 circle = new fabric.Circle({ 12314 radius: point.radius, 12315 left: point.x, 12316 top: point.y, 12317 originX: "center", 12318 originY: "center", 12319 fill: point.fill, 12320 }); 12321 12322 this.shadow && (circle.shadow = new fabric.Shadow(this.shadow)); 12323 12324 circles.push(circle); 12325 } 12326 var group = new fabric.Group(circles); 12327 group.canvas = this.canvas; 12328 12329 this.canvas.fire("before:path:created", { path: group }); 12330 this.canvas.add(group); 12331 this.canvas.fire("path:created", { path: group }); 12332 12333 this.canvas.clearContext(this.canvas.contextTop); 12334 this._resetShadow(); 12335 this.canvas.renderOnAddRemove = originalRenderOnAddRemove; 12336 this.canvas.requestRenderAll(); 12337 }, 12338 12339 /** 12340 * @param {Object} pointer 12341 * @return {fabric.Point} Just added pointer point 12342 */ 12343 addPoint: function (pointer) { 12344 var pointerPoint = new fabric.Point(pointer.x, pointer.y), 12345 circleRadius = 12346 fabric.util.getRandomInt( 12347 Math.max(0, this.width - 20), 12348 this.width + 20, 12349 ) / 2, 12350 circleColor = new fabric.Color(this.color) 12351 .setAlpha(fabric.util.getRandomInt(0, 100) / 100) 12352 .toRgba(); 12353 12354 pointerPoint.radius = circleRadius; 12355 pointerPoint.fill = circleColor; 12356 12357 this.points.push(pointerPoint); 12358 12359 return pointerPoint; 12360 }, 12361 }, 12362); 12363 12364/** 12365 * SprayBrush class 12366 * @class fabric.SprayBrush 12367 */ 12368fabric.SprayBrush = fabric.util.createClass( 12369 fabric.BaseBrush, 12370 /** @lends fabric.SprayBrush.prototype */ { 12371 /** 12372 * Width of a spray 12373 * @type Number 12374 * @default 12375 */ 12376 width: 10, 12377 12378 /** 12379 * Density of a spray (number of dots per chunk) 12380 * @type Number 12381 * @default 12382 */ 12383 density: 20, 12384 12385 /** 12386 * Width of spray dots 12387 * @type Number 12388 * @default 12389 */ 12390 dotWidth: 1, 12391 12392 /** 12393 * Width variance of spray dots 12394 * @type Number 12395 * @default 12396 */ 12397 dotWidthVariance: 1, 12398 12399 /** 12400 * Whether opacity of a dot should be random 12401 * @type Boolean 12402 * @default 12403 */ 12404 randomOpacity: false, 12405 12406 /** 12407 * Whether overlapping dots (rectangles) should be removed (for performance reasons) 12408 * @type Boolean 12409 * @default 12410 */ 12411 optimizeOverlapping: true, 12412 12413 /** 12414 * Constructor 12415 * @param {fabric.Canvas} canvas 12416 * @return {fabric.SprayBrush} Instance of a spray brush 12417 */ 12418 initialize: function (canvas) { 12419 this.canvas = canvas; 12420 this.sprayChunks = []; 12421 }, 12422 12423 /** 12424 * Invoked on mouse down 12425 * @param {Object} pointer 12426 */ 12427 onMouseDown: function (pointer) { 12428 this.sprayChunks.length = 0; 12429 this.canvas.clearContext(this.canvas.contextTop); 12430 this._setShadow(); 12431 12432 this.addSprayChunk(pointer); 12433 this.render(this.sprayChunkPoints); 12434 }, 12435 12436 /** 12437 * Invoked on mouse move 12438 * @param {Object} pointer 12439 */ 12440 onMouseMove: function (pointer) { 12441 if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { 12442 return; 12443 } 12444 this.addSprayChunk(pointer); 12445 this.render(this.sprayChunkPoints); 12446 }, 12447 12448 /** 12449 * Invoked on mouse up 12450 */ 12451 onMouseUp: function () { 12452 var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; 12453 this.canvas.renderOnAddRemove = false;
12454 12455 var rects = []; 12456 12457 for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { 12458 var sprayChunk = this.sprayChunks[i]; 12459 12460 for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { 12461 var rect = new fabric.Rect({ 12462 width: sprayChunk[j].width, 12463 height: sprayChunk[j].width, 12464 left: sprayChunk[j].x + 1, 12465 top: sprayChunk[j].y + 1, 12466 originX: "center", 12467 originY: "center", 12468 fill: this.color, 12469 }); 12470 rects.push(rect); 12471 } 12472 } 12473 12474 if (this.optimizeOverlapping) { 12475 rects = this._getOptimizedRects(rects); 12476 } 12477 12478 var group = new fabric.Group(rects); 12479 this.shadow && group.set("shadow", new fabric.Shadow(this.shadow)); 12480 this.canvas.fire("before:path:created", { path: group }); 12481 this.canvas.add(group); 12482 this.canvas.fire("path:created", { path: group }); 12483 12484 this.canvas.clearContext(this.canvas.contextTop); 12485 this._resetShadow(); 12486 this.canvas.renderOnAddRemove = originalRenderOnAddRemove; 12487 this.canvas.requestRenderAll(); 12488 }, 12489 12490 /** 12491 * @private 12492 * @param {Array} rects 12493 */ 12494 _getOptimizedRects: function (rects) { 12495 // avoid creating duplicate rects at the same coordinates 12496 var uniqueRects = {}, 12497 key, 12498 i, 12499 len; 12500 12501 for (i = 0, len = rects.length; i < len; i++) { 12502 key = rects[i].left + "" + rects[i].top; 12503 if (!uniqueRects[key]) { 12504 uniqueRects[key] = rects[i]; 12505 } 12506 } 12507 var uniqueRectsArray = []; 12508 for (key in uniqueRects) { 12509 uniqueRectsArray.push(uniqueRects[key]); 12510 } 12511 12512 return uniqueRectsArray; 12513 }, 12514 12515 /** 12516 * Render new chunk of spray brush 12517 */ 12518 render: function (sprayChunk) { 12519 var ctx = this.canvas.contextTop, 12520 i, 12521 len; 12522 ctx.fillStyle = this.color; 12523 12524 this._saveAndTransform(ctx); 12525 12526 for (i = 0, len = sprayChunk.length; i < len; i++) { 12527 var point = sprayChunk[i]; 12528 if (typeof point.opacity !== "undefined") { 12529 ctx.globalAlpha = point.opacity; 12530 } 12531 ctx.fillRect(point.x, point.y, point.width, point.width); 12532 } 12533 ctx.restore(); 12534 }, 12535 12536 /** 12537 * Render all spray chunks 12538 */ 12539 _render: function () { 12540 var ctx = this.canvas.contextTop, 12541 i, 12542 ilen; 12543 ctx.fillStyle = this.color; 12544 12545 this._saveAndTransform(ctx); 12546 12547 for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { 12548 this.render(this.sprayChunks[i]); 12549 } 12550 ctx.restore(); 12551 }, 12552 12553 /** 12554 * @param {Object} pointer 12555 */ 12556 addSprayChunk: function (pointer) { 12557 this.sprayChunkPoints = []; 12558 12559 var x, 12560 y, 12561 width, 12562 radius = this.width / 2, 12563 i; 12564 12565 for (i = 0; i < this.density; i++) { 12566 x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); 12567 y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); 12568 12569 if (this.dotWidthVariance) { 12570 width = fabric.util.getRandomInt( 12571 // bottom clamp width to 1 12572 Math.max(1, this.dotWidth - this.dotWidthVariance), 12573 this.dotWidth + this.dotWidthVariance, 12574 ); 12575 } else { 12576 width = this.dotWidth; 12577 } 12578 12579 var point = new fabric.Point(x, y); 12580 point.width = width; 12581 12582 if (this.randomOpacity) { 12583 point.opacity = fabric.util.getRandomInt(0, 100) / 100; 12584 } 12585 12586 this.sprayChunkPoints.push(point); 12587 } 12588 12589 this.sprayChunks.push(this.sprayChunkPoints); 12590 }, 12591 }, 12592); 12593 12594/** 12595 * PatternBrush class 12596 * @class fabric.PatternBrush 12597 * @extends fabric.BaseBrush 12598 */ 12599fabric.PatternBrush = fabric.util.createClass( 12600 fabric.PencilBrush, 12601 /** @lends fabric.PatternBrush.prototype */ { 12602 getPatternSrc: function () { 12603 var dotWidth = 20, 12604 dotDistance = 5, 12605 patternCanvas = fabric.util.createCanvasElement(), 12606 patternCtx = patternCanvas.getContext("2d"); 12607 12608 patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; 12609 12610 patternCtx.fillStyle = this.color; 12611 patternCtx.beginPath(); 12612 patternCtx.arc( 12613 dotWidth / 2, 12614 dotWidth / 2, 12615 dotWidth / 2, 12616 0, 12617 Math.PI * 2, 12618 false, 12619 ); 12620 patternCtx.closePath(); 12621 patternCtx.fill(); 12622 12623 return patternCanvas; 12624 }, 12625 12626 getPatternSrcFunction: function () { 12627 return String(this.getPatternSrc).replace( 12628 "this.color", 12629 '"' + this.color + '"', 12630 ); 12631 }, 12632 12633 /** 12634 * Creates "pattern" instance property 12635 * @param {CanvasRenderingContext2D} ctx 12636 */ 12637 getPattern: function (ctx) { 12638 return ctx.createPattern(this.source || this.getPatternSrc(), "repeat"); 12639 }, 12640 12641 /** 12642 * Sets brush styles 12643 * @param {CanvasRenderingContext2D} ctx 12644 */ 12645 _setBrushStyles: function (ctx) { 12646 this.callSuper("_setBrushStyles", ctx); 12647 ctx.strokeStyle = this.getPattern(ctx); 12648 }, 12649 12650 /** 12651 * Creates path 12652 */ 12653 createPath: function (pathData) { 12654 var path = this.callSuper("createPath", pathData), 12655 topLeft = path._getLeftTopCoords().scalarAdd(path.strokeWidth / 2); 12656 12657 path.stroke = new fabric.Pattern({ 12658 source: this.source || this.getPatternSrcFunction(), 12659 offsetX: -topLeft.x, 12660 offsetY: -topLeft.y, 12661 }); 12662 return path; 12663 }, 12664 }, 12665); 12666 12667(function () { 12668 var getPointer = fabric.util.getPointer, 12669 degreesToRadians = fabric.util.degreesToRadians, 12670 isTouchEvent = fabric.util.isTouchEvent; 12671 12672 /** 12673 * Canvas class 12674 * @class fabric.Canvas 12675 * @extends fabric.StaticCanvas 12676 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#canvas} 12677 * @see {@link fabric.Canvas#initialize}
12677 for constructor definition 12678 * 12679 * @fires object:modified at the end of a transform or any change when statefull is true 12680 * @fires object:rotating while an object is being rotated from the control 12681 * @fires object:scaling while an object is being scaled by controls 12682 * @fires object:moving while an object is being dragged 12683 * @fires object:skewing while an object is being skewed from the controls 12684 * 12685 * @fires before:transform before a transform is is started 12686 * @fires before:selection:cleared 12687 * @fires selection:cleared 12688 * @fires selection:updated 12689 * @fires selection:created 12690 * 12691 * @fires path:created after a drawing operation ends and the path is added 12692 * @fires mouse:down 12693 * @fires mouse:move 12694 * @fires mouse:up 12695 * @fires mouse:down:before on mouse down, before the inner fabric logic runs 12696 * @fires mouse:move:before on mouse move, before the inner fabric logic runs 12697 * @fires mouse:up:before on mouse up, before the inner fabric logic runs 12698 * @fires mouse:over 12699 * @fires mouse:out 12700 * @fires mouse:dblclick whenever a native dbl click event fires on the canvas. 12701 * 12702 * @fires dragover 12703 * @fires dragenter 12704 * @fires dragleave 12705 * @fires drop:before before drop event. same native event. This is added to handle edge cases 12706 * @fires drop 12707 * @fires after:render at the end of the render process, receives the context in the callback 12708 * @fires before:render at start the render process, receives the context in the callback 12709 * 12710 */ 12711 fabric.Canvas = fabric.util.createClass( 12712 fabric.StaticCanvas, 12713 /** @lends fabric.Canvas.prototype */ { 12714 /** 12715 * Constructor 12716 * @param {HTMLElement | String} el <canvas> element to initialize instance on 12717 * @param {Object} [options] Options object 12718 * @return {Object} thisArg 12719 */ 12720 initialize: function (el, options) { 12721 options || (options = {}); 12722 this.renderAndResetBound = this.renderAndReset.bind(this); 12723 this.requestRenderAllBound = this.requestRenderAll.bind(this); 12724 this._initStatic(el, options); 12725 this._initInteractive(); 12726 this._createCacheCanvas(); 12727 }, 12728 12729 /** 12730 * When true, objects can be transformed by one side (unproportionally) 12731 * when dragged on the corners that normally would not do that. 12732 * @type Boolean 12733 * @default 12734 * @since fabric 4.0 // changed name and default value 12735 */ 12736 uniformScaling: true, 12737 12738 /** 12739 * Indicates which key switches uniform scaling. 12740 * values: 'altKey', 'shiftKey', 'ctrlKey'. 12741 * If `null` or 'none' or any other string that is not a modifier key 12742 * feature is disabled. 12743 * totally wrong named. this sounds like `uniform scaling` 12744 * if Canvas.uniformScaling is true, pressing this will set it to false 12745 * and viceversa. 12746 * @since 1.6.2
12747 * @type String 12748 * @default 12749 */ 12750 uniScaleKey: "shiftKey", 12751 12752 /** 12753 * When true, objects use center point as the origin of scale transformation. 12754 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 12755 * @since 1.3.4 12756 * @type Boolean 12757 * @default 12758 */ 12759 centeredScaling: false, 12760 12761 /** 12762 * When true, objects use center point as the origin of rotate transformation. 12763 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 12764 * @since 1.3.4 12765 * @type Boolean 12766 * @default 12767 */ 12768 centeredRotation: false, 12769 12770 /** 12771 * Indicates which key enable centered Transform 12772 * values: 'altKey', 'shiftKey', 'ctrlKey'. 12773 * If `null` or 'none' or any other string that is not a modifier key 12774 * feature is disabled feature disabled. 12775 * @since 1.6.2 12776 * @type String 12777 * @default 12778 */ 12779 centeredKey: "altKey", 12780 12781 /** 12782 * Indicates which key enable alternate action on corner 12783 * values: 'altKey', 'shiftKey', 'ctrlKey'. 12784 * If `null` or 'none' or any other string that is not a modifier key 12785 * feature is disabled feature disabled. 12786 * @since 1.6.2
12787 * @type String 12788 * @default 12789 */ 12790 altActionKey: "shiftKey", 12791 12792 /** 12793 * Indicates that canvas is interactive. This property should not be changed. 12794 * @type Boolean 12795 * @default 12796 */ 12797 interactive: true, 12798 12799 /** 12800 * Indicates whether group selection should be enabled 12801 * @type Boolean 12802 * @default 12803 */ 12804 selection: true, 12805 12806 /** 12807 * Indicates which key or keys enable multiple click selection 12808 * Pass value as a string or array of strings 12809 * values: 'altKey', 'shiftKey', 'ctrlKey'. 12810 * If `null` or empty or containing any other string that is not a modifier key 12811 * feature is disabled. 12812 * @since 1.6.2 12813 * @type String|Array 12814 * @default 12815 */ 12816 selectionKey: "shiftKey", 12817 12818 /** 12819 * Indicates which key enable alternative selection 12820 * in case of target overlapping with active object 12821 * values: 'altKey', 'shiftKey', 'ctrlKey'. 12822 * For a series of reason that come from the general expectations on how 12823 * things should work, this feature works only for preserveObjectStacking true. 12824 * If `null` or 'none' or any other string that is not a modifier key 12825 * feature is disabled. 12826 * @since 1.6.5 12827 * @type null|String 12828 * @default 12829 */ 12830 altSelectionKey: null, 12831 12832 /** 12833 * Color of selection
12834 * @type String 12835 * @default 12836 */ 12837 selectionColor: "rgba(100, 100, 255, 0.3)", // blue 12838 12839 /** 12840 * Default dash array pattern 12841 * If not empty the selection border is dashed 12842 * @type Array 12843 */ 12844 selectionDashArray: [], 12845 12846 /** 12847 * Color of the border of selection (usually slightly darker than color of selection itself) 12848 * @type String 12849 * @default 12850 */ 12851 selectionBorderColor: "rgba(255, 255, 255, 0.3)", 12852 12853 /** 12854 * Width of a line used in object/group selection 12855 * @type Number 12856 * @default 12857 */ 12858 selectionLineWidth: 1, 12859 12860 /** 12861 * Select only shapes that are fully contained in the dragged selection rectangle. 12862 * @type Boolean 12863 * @default 12864 */ 12865 selectionFullyContained: false, 12866 12867 /** 12868 * Default cursor value used when hovering over an object on canvas 12869 * @type String 12870 * @default 12871 */ 12872 hoverCursor: "move", 12873 12874 /** 12875 * Default cursor value used when moving an object on canvas
12876 * @type String 12877 * @default 12878 */ 12879 moveCursor: "move", 12880 12881 /** 12882 * Default cursor value used for the entire canvas 12883 * @type String 12884 * @default 12885 */ 12886 defaultCursor: "default", 12887 12888 /** 12889 * Cursor value used during free drawing 12890 * @type String 12891 * @default 12892 */ 12893 freeDrawingCursor: "crosshair", 12894 12895 /** 12896 * Cursor value used for disabled elements ( corners with disabled action ) 12897 * @type String 12898 * @since 2.0.0 12899 * @default 12900 */ 12901 notAllowedCursor: "not-allowed", 12902 12903 /** 12904 * Default element class that's given to wrapper (div) element of canvas 12905 * @type String 12906 * @default 12907 */ 12908 containerClass: "canvas-container", 12909 12910 /** 12911 * When true, object detection happens on per-pixel basis rather than on per-bounding-box 12912 * @type Boolean 12913 * @default 12914 */ 12915 perPixelTargetFind: false, 12916 12917 /** 12918 * Number of pixels around target pixel to tolerate (consider active) during object detection 12919 * @type Number 12920 * @default 12921 */ 12922 targetFindTolerance: 0, 12923 12924 /** 12925 * When true, target detection is skipped. Target detection will return always undefined. 12926 * click selection won't work anymore, events will fire with no targets. 12927 * if something is selected before setting it to true, it will be deselected at the first click. 12928 * area selection will still work. check the `selection` property too. 12929 * if you deactivate both, you should look into staticCanvas. 12930 * @type Boolean 12931 * @default 12932 */ 12933 skipTargetFind: false, 12934 12935 /** 12936 * When true, mouse events on canvas (mousedown/mousemove/mouseup) result in free drawing. 12937 * After mousedown, mousemove creates a shape, 12938 * and then mouseup finalizes it and adds an instance of `fabric.Path` onto canvas. 12939 * @tutorial {@link http://fabricjs.com/fabric-intro-part-4#free_drawing} 12940 * @type Boolean 12941 * @default 12942 */ 12943 isDrawingMode: false, 12944 12945 /** 12946 * Indicates whether objects should remain in current stack position when selected. 12947 * When false objects are brought to top and rendered as part of the selection group 12948 * @type Boolean 12949 * @default 12950 */ 12951 preserveObjectStacking: false, 12952 12953 /** 12954 * Indicates the angle that an object will lock to while rotating. 12955 * @type Number 12956 * @since 1.6.7 12957 * @default 12958 */ 12959 snapAngle: 0, 12960 12961 /** 12962 * Indicates the distance from the snapAngle the rotation will lock to the snapAngle. 12963 * When `null`, the snapThreshold will default to the snapAngle. 12964 * @type null|Number 12965 * @since 1.6.7 12966 * @default 12967 */ 12968 snapThreshold: null, 12969 12970 /** 12971 * Indicates if the right click on canvas can output the context menu or not 12972 * @type Boolean 12973 * @since 1.6.5 12974 * @default 12975 */ 12976 stopContextMenu: false, 12977 12978 /** 12979 * Indicates if the canvas can fire right click events 12980 * @type Boolean 12981 * @since 1.6.5 12982 * @default 12983 */ 12984 fireRightClick: false, 12985 12986 /** 12987 * Indicates if the canvas can fire middle click events 12988 * @type Boolean 12989 * @since 1.7.8 12990 * @default 12991 */ 12992 fireMiddleClick: false, 12993 12994 /** 12995 * Keep track of the subTargets for Mouse Events 12996 * @type fabric.Object[] 12997 */ 12998 targets: [], 12999 13000 /** 13001 * When the option is enabled, PointerEvent is used instead of MouseEvent. 13002 * @type Boolean 13003 * @default 13004 */ 13005 enablePointerEvents: false, 13006 13007 /** 13008 * Keep track of the hovered target 13009 * @type fabric.Object 13010 * @private 13011 */ 13012 _hoveredTarget: null, 13013 13014 /** 13015 * hold the list of nested targets hovered 13016 * @type fabric.Object[] 13017 * @private 13018 */ 13019 _hoveredTargets: [], 13020 13021 /** 13022 * @private 13023 */ 13024 _initInteractive: function () { 13025 this._currentTransform = null; 13026 this._groupSelector = null; 13027 this._initWrapperElement(); 13028 this._createUpperCanvas(); 13029 this._initEventListeners(); 13030 13031 this._initRetinaScaling(); 13032 13033 this.freeDrawingBrush = 13034 fabric.PencilBrush && new fabric.PencilBrush(this); 13035 13036 this.calcOffset(); 13037 }, 13038 13039 /** 13040 * Divides objects in two groups, one to render immediately 13041 * and one to render as activeGroup. 13042 * @return {Array} objects to render immediately and pushes the other in the activeGroup. 13043 */ 13044 _chooseObjectsToRender: function () { 13045 var activeObjects = this.getActiveObjects(), 13046 object, 13047 objsToRender, 13048 activeGroupObjects; 13049 13050 if (activeObjects.length > 0 && !this.preserveObjectStacking) { 13051 objsToRender = []; 13052 activeGroupObjects = []; 13053 for (var i = 0, length = this._objects.length; i < length; i++) { 13054 object = this._objects[i]; 13055 if (activeObjects.indexOf(object) === -1) { 13056 objsToRender.push(object); 13057 } else { 13058 activeGroupObjects.push(object); 13059 } 13060 } 13061 if (activeObjects.length > 1) { 13062 this._activeObject._objects = activeGroupObjects; 13063 } 13064 objsToRender.push.apply(objsToRender, activeGroupObjects); 13065 } else { 13066 objsToRender = this._objects; 13067 } 13068 return objsToRender; 13069 }, 13070 13071 /**
13072 * Renders both the top canvas and the secondary container canvas. 13073 * @return {fabric.Canvas} instance 13074 * @chainable 13075 */ 13076 renderAll: function () { 13077 if ( 13078 this.contextTopDirty && 13079 !this._groupSelector && 13080 !this.isDrawingMode 13081 ) { 13082 this.clearContext(this.contextTop); 13083 this.contextTopDirty = false; 13084 } 13085 if (this.hasLostContext) { 13086 this.renderTopLayer(this.contextTop); 13087 this.hasLostContext = false; 13088 } 13089 var canvasToDrawOn = this.contextContainer; 13090 this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender()); 13091 return this; 13092 }, 13093 13094 renderTopLayer: function (ctx) { 13095 ctx.save(); 13096 if (this.isDrawingMode && this._isCurrentlyDrawing) { 13097 this.freeDrawingBrush && this.freeDrawingBrush._render(); 13098 this.contextTopDirty = true; 13099 } 13100 // we render the top context - last object 13101 if (this.selection && this._groupSelector) { 13102 this._drawSelection(ctx); 13103 this.contextTopDirty = true; 13104 } 13105 ctx.restore(); 13106 }, 13107 13108 /** 13109 * Method to render only the top canvas. 13110 * Also used to render the group selection box. 13111 * @return {fabric.Canvas} thisArg 13112 * @chainable 13113 */ 13114 renderTop: function () { 13115 var ctx = this.contextTop; 13116 this.clearContext(ctx); 13117 this.renderTopLayer(ctx); 13118 this.fire("after:render"); 13119 return this; 13120 }, 13121 13122 /** 13123 * @private 13124 */ 13125 _normalizePointer: function (object, pointer) { 13126 var m = object.calcTransformMatrix(), 13127 invertedM = fabric.util.invertTransform(m), 13128 vptPointer = this.restorePointerVpt(pointer); 13129 return fabric.util.transformPoint(vptPointer, invertedM); 13130 }, 13131 13132 /** 13133 * Returns true if object is transparent at a certain location 13134 * @param {fabric.Object} target Object to check 13135 * @param {Number} x Left coordinate 13136 * @param {Number} y Top coordinate 13137 * @return {Boolean} 13138 */ 13139 isTargetTransparent: function (target, x, y) { 13140 // in case the target is the activeObject, we cannot execute this optimization 13141 // because we need to draw controls too. 13142 if ( 13143 target.shouldCache() && 13144 target._cacheCanvas && 13145 target !== this._activeObject 13146 ) { 13147 var normalizedPointer = this._normalizePointer(target, { 13148 x: x, 13149 y: y, 13150 }), 13151 targetRelativeX = Math.max( 13152 target.cacheTranslationX + normalizedPointer.x * target.zoomX, 13153 0, 13154 ), 13155 targetRelativeY = Math.max( 13156 target.cacheTranslationY + normalizedPointer.y * target.zoomY, 13157 0, 13158 ); 13159 13160 var isTransparent = fabric.util.isTransparent( 13161 target._cacheContext, 13162 Math.round(targetRelativeX), 13163 Math.round(targetRelativeY), 13164 this.targetFindTolerance, 13165 ); 13166 13167 return isTransparent; 13168 } 13169 13170 var ctx = this.contextCache, 13171 originalColor = target.selectionBackgroundColor, 13172 v = this.viewportTransform; 13173 13174 target.selectionBackgroundColor = ""; 13175 13176 this.clearContext(ctx); 13177 13178 ctx.save(); 13179 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 13180 target.render(ctx); 13181 ctx.restore(); 13182 13183 target.selectionBackgroundColor = originalColor; 13184 13185 var isTransparent = fabric.util.isTransparent( 13186 ctx, 13187 x, 13188 y, 13189 this.targetFindTolerance, 13190 ); 13191 13192 return isTransparent; 13193 }, 13194 13195 /** 13196 * takes an event and determines if selection key has been pressed 13197 * @private 13198 * @param {Event} e Event object 13199 */ 13200 _isSelectionKeyPressed: function (e) { 13201 var selectionKeyPressed = false; 13202 13203 if (Array.isArray(this.selectionKey)) { 13204 selectionKeyPressed = !!this.selectionKey.find(function (key) { 13205 return e[key] === true; 13206 }); 13207 } else { 13208 selectionKeyPressed = e[this.selectionKey]; 13209 } 13210 13211 return selectionKeyPressed; 13212 }, 13213 13214 /** 13215 * @private 13216 * @param {Event} e Event object 13217 * @param {fabric.Object} target 13218 */ 13219 _shouldClearSelection: function (e, target) { 13220 var activeObjects = this.getActiveObjects(), 13221 activeObject = this._activeObject; 13222 13223 return ( 13224 !target || 13225 (target && 13226 activeObject && 13227 activeObjects.length > 1 && 13228 activeObjects.indexOf(target) === -1 && 13229 activeObject !== target && 13230 !this._isSelectionKeyPressed(e)) || 13231 (target && !target.evented) || 13232 (target && 13233 !target.selectable && 13234 activeObject && 13235 activeObject !== target) 13236 ); 13237 }, 13238 13239 /** 13240 * centeredScaling from object can't override centeredScaling from canvas. 13241 * this should be fixed, since object setting should take precedence over canvas.
13242 * also this should be something that will be migrated in the control properties. 13243 * as ability to define the origin of the transformation that the control provide. 13244 * @private 13245 * @param {fabric.Object} target 13246 * @param {String} action 13247 * @param {Boolean} altKey 13248 */ 13249 _shouldCenterTransform: function (target, action, altKey) { 13250 if (!target) { 13251 return; 13252 } 13253 13254 var centerTransform; 13255 13256 if ( 13257 action === "scale" || 13258 action === "scaleX" || 13259 action === "scaleY" || 13260 action === "resizing" 13261 ) { 13262 centerTransform = this.centeredScaling || target.centeredScaling; 13263 } else if (action === "rotate") { 13264 centerTransform = this.centeredRotation || target.centeredRotation; 13265 } 13266 13267 return centerTransform ? !altKey : altKey; 13268 }, 13269 13270 /** 13271 * should disappear before release 4.0 13272 * @private 13273 */ 13274 _getOriginFromCorner: function (target, corner) { 13275 var origin = { 13276 x: target.originX, 13277 y: target.originY, 13278 }; 13279 13280 if (corner === "ml" || corner === "tl" || corner === "bl") { 13281 origin.x = "right"; 13282 } else if (corner === "mr" || corner === "tr" || corner === "br") { 13283 origin.x = "left"; 13284 } 13285 13286 if (corner === "tl" || corner === "mt" || corner === "tr") { 13287 origin.y = "bottom"; 13288 } else if (corner === "bl" || corner === "mb" || corner === "br") { 13289 origin.y = "top"; 13290 } 13291 return origin; 13292 }, 13293 13294 /** 13295 * @private 13296 * @param {Boolean} alreadySelected true if target is already selected 13297 * @param {String} corner a string representing the corner ml, mr, tl ... 13298 * @param {Event} e Event object 13299 * @param {fabric.Object} [target] inserted back to help overriding. Unused 13300 */ 13301 _getActionFromCorner: function (alreadySelected, corner, e, target) { 13302 if (!corner || !alreadySelected) { 13303 return "drag"; 13304 } 13305 var control = target.controls[corner]; 13306 return control.getActionName(e, control, target); 13307 }, 13308 13309 /** 13310 * @private 13311 * @param {Event} e Event object 13312 * @param {fabric.Object} target 13313 */ 13314 _setupCurrentTransform: function (e, target, alreadySelected) { 13315 if (!target) { 13316 return; 13317 } 13318 13319 var pointer = this.getPointer(e), 13320 corner = target.__corner, 13321 control = target.controls[corner], 13322 actionHandler = 13323 alreadySelected && corner 13324 ? control.getActionHandler(e, target, control) 13325 : fabric.controlsUtils.dragHandler, 13326 action = this._getActionFromCorner( 13327 alreadySelected, 13328 corner, 13329 e, 13330 target, 13331 ), 13332 origin = this._getOriginFromCorner(target, corner), 13333 altKey = e[this.centeredKey], 13334 transform = { 13335 target: target, 13336 action: action, 13337 actionHandler: actionHandler, 13338 corner: corner, 13339 scaleX: target.scaleX, 13340 scaleY: target.scaleY, 13341 skewX: target.skewX, 13342 skewY: target.skewY, 13343 // used by transation 13344 offsetX: pointer.x - target.left, 13345 offsetY: pointer.y - target.top, 13346 originX: origin.x, 13347 originY: origin.y, 13348 ex: pointer.x, 13349 ey: pointer.y, 13350 lastX: pointer.x, 13351 lastY: pointer.y, 13352 // unsure they are useful anymore. 13353 // left: target.left, 13354 // top: target.top, 13355 theta: degreesToRadians(target.angle), 13356 // end of unsure 13357 width: target.width * target.scaleX, 13358 shiftKey: e.shiftKey, 13359 altKey: altKey, 13360 original: fabric.util.saveObjectTransform(target), 13361 }; 13362 13363 if (this._shouldCenterTransform(target, action, altKey)) { 13364 transform.originX = "center"; 13365 transform.originY = "center"; 13366 } 13367 transform.original.originX = origin.x; 13368 transform.original.originY = origin.y; 13369 this._currentTransform = transform; 13370 this._beforeTransform(e); 13371 }, 13372 13373 /** 13374 * Set the cursor type of the canvas element 13375 * @param {String} value Cursor type of the canvas element. 13376 * @see http://www.w3.org/TR/css3-ui/#cursor 13377 */ 13378 setCursor: function (value) { 13379 this.upperCanvasEl.style.cursor = value; 13380 }, 13381 13382 /** 13383 * @private 13384 * @param {CanvasRenderingContext2D} ctx to draw the selection on 13385 */ 13386 _drawSelection: function (ctx) { 13387 var selector = this._groupSelector, 13388 viewportStart = new fabric.Point(selector.ex, selector.ey), 13389 start = fabric.util.transformPoint( 13390 viewportStart, 13391 this.viewportTransform, 13392 ), 13393 viewportExtent = new fabric.Point( 13394 selector.ex + selector.left, 13395 selector.ey + selector.top, 13396 ), 13397 extent = fabric.util.transformPoint( 13398 viewportExtent, 13399 this.viewportTransform, 13400 ), 13401 minX = Math.min(start.x, extent.x), 13402 minY = Math.min(start.y, extent.y), 13403 maxX = Math.max(start.x, extent.x), 13404 maxY = Math.max(start.y, extent.y), 13405 strokeOffset = this.selectionLineWidth / 2; 13406 13407 if (this.selectionColor) { 13408 ctx.fillStyle = this.selectionColor; 13409 ctx.fillRect(minX, minY, maxX - minX, maxY - minY); 13410 } 13411 13412 if (!this.selectionLineWidth || !this.selectionBorderColor) { 13413 return; 13414 } 13415 ctx.lineWidth = this.selectionLineWidth; 13416 ctx.strokeStyle = this.selectionBorderColor; 13417 13418 minX += strokeOffset; 13419 minY += strokeOffset; 13420 maxX -= strokeOffset; 13421 maxY -= strokeOffset; 13422 // selection border 13423 fabric.Object.prototype._setLineDash.call( 13424 this, 13425 ctx, 13426 this.selectionDashArray, 13427 ); 13428 ctx.strokeRect(minX, minY, maxX - minX, maxY - minY); 13429 }, 13430 13431 /** 13432 * Method that determines what object we are clicking on 13433 * the skipGroup parameter is for internal use, is needed for shift+click action 13434 * 11/09/2018 TODO: would be cool if findTarget could discern between being a full target 13435 * or the outside part of the corner. 13436 * @param {Event} e mouse event 13437 * @param {Boolean} skipGroup when true, activeGroup is skipped and only objects are traversed through 13438 * @return {fabric.Object} the target found 13439 */ 13440 findTarget: function (e, skipGroup) { 13441 if (this.skipTargetFind) { 13442 return; 13443 } 13444 13445 var ignoreZoom = true, 13446 pointer = this.getPointer(e, ignoreZoom), 13447 activeObject = this._activeObject, 13448 aObjects = this.getActiveObjects(), 13449 activeTarget, 13450 activeTargetSubs, 13451 isTouch = isTouchEvent(e), 13452 shouldLookForActive = 13453 (aObjects.length > 1 && !skipGroup) || aObjects.length === 1; 13454 13455 // first check current group (if one exists) 13456 // active group does not check sub targets like normal groups. 13457 // if active group just exits. 13458 this.targets = []; 13459 13460 // if we hit the corner of an activeObject, let's return that. 13461 if ( 13462 shouldLookForActive && 13463 activeObject._findTargetCorner(pointer, isTouch) 13464 ) { 13465 return activeObject; 13466 } 13467 if ( 13468 aObjects.length > 1 && 13469 !skipGroup && 13470 activeObject === this._searchPossibleTargets([activeObject], pointer) 13471 ) { 13472 return activeObject; 13473 } 13474 if ( 13475 aObjects.length === 1 && 13476 activeObject === this._searchPossibleTargets([activeObject], pointer) 13477 ) { 13478 if (!this.preserveObjectStacking) { 13479 return activeObject; 13480 } else { 13481 activeTarget = activeObject; 13482 activeTargetSubs = this.targets; 13483 this.targets = []; 13484 } 13485 } 13486 var target = this._searchPossibleTargets(this._objects, pointer); 13487 if ( 13488 e[this.altSelectionKey] && 13489 target && 13490 activeTarget && 13491 target !== activeTarget 13492 ) { 13493 target = activeTarget; 13494 this.targets = activeTargetSubs; 13495 } 13496 return target; 13497 }, 13498 13499 /** 13500 * Checks point is inside the object. 13501 * @param {Object} [pointer] x,y object of point coordinates we want to check. 13502 * @param {fabric.Object} obj Object to test against 13503 * @param {Object} [globalPointer] x,y object of point coordinates relative to canvas used to search per pixel target. 13504 * @return {Boolean} true if point is contained within an area of given object 13505 * @private 13506 */ 13507 _checkTarget: function (pointer, obj, globalPointer) { 13508 if ( 13509 obj && 13510 obj.visible && 13511 obj.evented && 13512 // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html 13513 // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html 13514 obj.containsPoint(pointer) 13515 ) { 13516 if ( 13517 (this.perPixelTargetFind || obj.perPixelTargetFind) && 13518 !obj.isEditing 13519 ) {
13520 var isTransparent = this.isTargetTransparent( 13521 obj, 13522 globalPointer.x, 13523 globalPointer.y, 13524 ); 13525 if (!isTransparent) { 13526 return true; 13527 } 13528 } else { 13529 return true; 13530 } 13531 } 13532 }, 13533 13534 /** 13535 * Function used to search inside objects an object that contains pointer in bounding box or that contains pointerOnCanvas when painted 13536 * @param {Array} [objects] objects array to look into 13537 * @param {Object} [pointer] x,y object of point coordinates we want to check. 13538 * @return {fabric.Object} object that contains pointer 13539 * @private 13540 */ 13541 _searchPossibleTargets: function (objects, pointer) { 13542 // Cache all targets where their bounding box contains point. 13543 var target, 13544 i = objects.length, 13545 subTarget; 13546 // Do not check for currently grouped objects, since we check the parent group itself. 13547 // until we call this function specifically to search inside the activeGroup 13548 while (i--) { 13549 var objToCheck = objects[i]; 13550 var pointerToUse = objToCheck.group 13551 ? this._normalizePointer(objToCheck.group, pointer) 13552 : pointer; 13553 if (this._checkTarget(pointerToUse, objToCheck, pointer)) { 13554 target = objects[i]; 13555 if (target.subTargetCheck && target instanceof fabric.Group) { 13556 subTarget = this._searchPossibleTargets(target._objects, pointer); 13557 subTarget && this.targets.push(subTarget); 13558 } 13559 break; 13560 } 13561 } 13562 return target; 13563 }, 13564 13565 /** 13566 * Returns pointer coordinates without the effect of the viewport 13567 * @param {Object} pointer with "x" and "y" number values 13568 * @return {Object} object with "x" and "y" number values 13569 */ 13570 restorePointerVpt: function (pointer) { 13571 return fabric.util.transformPoint( 13572 pointer, 13573 fabric.util.invertTransform(this.viewportTransform), 13574 ); 13575 }, 13576 13577 /** 13578 * Returns pointer coordinates relative to canvas. 13579 * Can return coordinates with or without viewportTransform. 13580 * ignoreZoom false gives back coordinates that represent 13581 * the point clicked on canvas element. 13582 * ignoreZoom true gives back coordinates after being processed 13583 * by the viewportTransform ( sort of coordinates of what is displayed 13584 * on the canvas where you are clicking. 13585 * ignoreZoom true = HTMLElement coordinates relative to top,left 13586 * ignoreZoom false, default = fabric space coordinates, the same used for shape position 13587 * To interact with your shapes top and left you want to use ignoreZoom true 13588 * most of the time, while ignoreZoom false will give you coordinates 13589 * compatible with the object.oCoords system. 13590 * of the time. 13591 * @param {Event} e 13592 * @param {Boolean} ignoreZoom 13593 * @return {Object} object with "x" and "y" number values 13594 */ 13595 getPointer: function (e, ignoreZoom) { 13596 // return cached values if we are in the event processing chain 13597 if (this._absolutePointer && !ignoreZoom) { 13598 return this._absolutePointer; 13599 } 13600 if (this._pointer && ignoreZoom) { 13601 return this._pointer; 13602 } 13603 13604 var pointer = getPointer(e), 13605 upperCanvasEl = this.upperCanvasEl, 13606 bounds = upperCanvasEl.getBoundingClientRect(), 13607 boundsWidth = bounds.width || 0, 13608 boundsHeight = bounds.height || 0, 13609 cssScale; 13610 13611 if (!boundsWidth || !boundsHeight) { 13612 if ("top" in bounds && "bottom" in bounds) { 13613 boundsHeight = Math.abs(bounds.top - bounds.bottom); 13614 } 13615 if ("right" in bounds && "left" in bounds) { 13616 boundsWidth = Math.abs(bounds.right - bounds.left); 13617 } 13618 } 13619 13620 this.calcOffset(); 13621 pointer.x = pointer.x - this._offset.left; 13622 pointer.y = pointer.y - this._offset.top; 13623 if (!ignoreZoom) { 13624 pointer = this.restorePointerVpt(pointer); 13625 } 13626 13627 var retinaScaling = this.getRetinaScaling(); 13628 if (retinaScaling !== 1) { 13629 pointer.x /= retinaScaling; 13630 pointer.y /= retinaScaling; 13631 } 13632 13633 if (boundsWidth === 0 || boundsHeight === 0) { 13634 // If bounds are not available (i.e. not visible), do not apply scale. 13635 cssScale = { width: 1, height: 1 }; 13636 } else { 13637 cssScale = { 13638 width: upperCanvasEl.width / boundsWidth, 13639 height: upperCanvasEl.height / boundsHeight, 13640 }; 13641 } 13642 13643 return { 13644 x: pointer.x * cssScale.width, 13645 y: pointer.y * cssScale.height, 13646 }; 13647 }, 13648 13649 /** 13650 * @private 13651 * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized 13652 */ 13653 _createUpperCanvas: function () { 13654 var lowerCanvasClass = this.lowerCanvasEl.className.replace( 13655 /\s*lower-canvas\s*/, 13656 "", 13657 ), 13658 lowerCanvasEl = this.lowerCanvasEl,
13659 upperCanvasEl = this.upperCanvasEl; 13660 13661 // there is no need to create a new upperCanvas element if we have already one. 13662 if (upperCanvasEl) { 13663 upperCanvasEl.className = ""; 13664 } else { 13665 upperCanvasEl = this._createCanvasElement(); 13666 this.upperCanvasEl = upperCanvasEl; 13667 } 13668 fabric.util.addClass(upperCanvasEl, "upper-canvas " + lowerCanvasClass); 13669 13670 this.wrapperEl.appendChild(upperCanvasEl); 13671 13672 this._copyCanvasStyle(lowerCanvasEl, upperCanvasEl); 13673 this._applyCanvasStyle(upperCanvasEl); 13674 this.contextTop = upperCanvasEl.getContext("2d"); 13675 }, 13676 13677 /** 13678 * Returns context of top canvas where interactions are drawn 13679 * @returns {CanvasRenderingContext2D} 13680 */ 13681 getTopContext: function () { 13682 return this.contextTop; 13683 }, 13684 13685 /** 13686 * @private 13687 */ 13688 _createCacheCanvas: function () { 13689 this.cacheCanvasEl = this._createCanvasElement(); 13690 this.cacheCanvasEl.setAttribute("width", this.width); 13691 this.cacheCanvasEl.setAttribute("height", this.height); 13692 this.contextCache = this.cacheCanvasEl.getContext("2d"); 13693 }, 13694 13695 /** 13696 * @private 13697 */ 13698 _initWrapperElement: function () { 13699 this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, "div", { 13700 class: this.containerClass, 13701 }); 13702 fabric.util.setStyle(this.wrapperEl, { 13703 width: this.width + "px", 13704 height: this.height + "px", 13705 position: "relative", 13706 }); 13707 fabric.util.makeElementUnselectable(this.wrapperEl); 13708 }, 13709 13710 /** 13711 * @private 13712 * @param {HTMLElement} element canvas element to apply styles on 13713 */ 13714 _applyCanvasStyle: function (element) { 13715 var width = this.width || element.width, 13716 height = this.height || element.height; 13717 13718 fabric.util.setStyle(element, { 13719 position: "absolute", 13720 width: width + "px", 13721 height: height + "px", 13722 left: 0, 13723 top: 0, 13724 "touch-action": this.allowTouchScrolling ? "manipulation" : "none", 13725 "-ms-touch-action": this.allowTouchScrolling 13726 ? "manipulation" 13727 : "none", 13728 }); 13729 element.width = width; 13730 element.height = height; 13731 fabric.util.makeElementUnselectable(element); 13732 }, 13733 13734 /** 13735 * Copy the entire inline style from one element (fromEl) to another (toEl) 13736 * @private 13737 * @param {Element} fromEl Element style is copied from 13738 * @param {Element} toEl Element copied style is applied to 13739 */ 13740 _copyCanvasStyle: function (fromEl, toEl) { 13741 toEl.style.cssText = fromEl.style.cssText; 13742 }, 13743 13744 /** 13745 * Returns context of canvas where object selection is drawn 13746 * @return {CanvasRenderingContext2D} 13747 */ 13748 getSelectionContext: function () { 13749 return this.contextTop; 13750 }, 13751 13752 /** 13753 * Returns <canvas> element on which object selection is drawn 13754 * @return {HTMLCanvasElement} 13755 */ 13756 getSelectionElement: function () {
vendor: 4,326 bytes, lines 13757-13884
13757 return this.upperCanvasEl; 13758 }, 13759 13760 /** 13761 * Returns currently active object 13762 * @return {fabric.Object} active object 13763 */ 13764 getActiveObject: function () { 13765 return this._activeObject; 13766 }, 13767 13768 /** 13769 * Returns an array with the current selected objects 13770 * @return {fabric.Object} active object 13771 */ 13772 getActiveObjects: function () { 13773 var active = this._activeObject; 13774 if (active) { 13775 if (active.type === "activeSelection" && active._objects) { 13776 return active._objects.slice(0); 13777 } else { 13778 return [active]; 13779 } 13780 } 13781 return []; 13782 }, 13783 13784 /** 13785 * @private 13786 * @param {fabric.Object} obj Object that was removed 13787 */ 13788 _onObjectRemoved: function (obj) { 13789 // removing active object should fire "selection:cleared" events 13790 if (obj === this._activeObject) { 13791 this.fire("before:selection:cleared", { target: obj }); 13792 this._discardActiveObject(); 13793 this.fire("selection:cleared", { target: obj }); 13794 obj.fire("deselected"); 13795 } 13796 if (obj === this._hoveredTarget) { 13797 this._hoveredTarget = null; 13798 this._hoveredTargets = []; 13799 } 13800 this.callSuper("_onObjectRemoved", obj); 13801 }, 13802 13803 /** 13804 * @private 13805 * Compares the old activeObject with the current one and fires correct events 13806 * @param {fabric.Object} obj old activeObject 13807 */ 13808 _fireSelectionEvents: function (oldObjects, e) { 13809 var somethingChanged = false, 13810 objects = this.getActiveObjects(), 13811 added = [], 13812 removed = []; 13813 oldObjects.forEach(function (oldObject) { 13814 if (objects.indexOf(oldObject) === -1) { 13815 somethingChanged = true; 13816 oldObject.fire("deselected", { 13817 e: e, 13818 target: oldObject, 13819 }); 13820 removed.push(oldObject); 13821 } 13822 }); 13823 objects.forEach(function (object) { 13824 if (oldObjects.indexOf(object) === -1) { 13825 somethingChanged = true; 13826 object.fire("selected", { 13827 e: e, 13828 target: object, 13829 }); 13830 added.push(object); 13831 } 13832 }); 13833 if (oldObjects.length > 0 && objects.length > 0) { 13834 somethingChanged && 13835 this.fire("selection:updated", { 13836 e: e, 13837 selected: added, 13838 deselected: removed, 13839 }); 13840 } else if (objects.length > 0) { 13841 this.fire("selection:created", { 13842 e: e, 13843 selected: added, 13844 }); 13845 } else if (oldObjects.length > 0) { 13846 this.fire("selection:cleared", { 13847 e: e, 13848 deselected: removed, 13849 }); 13850 } 13851 }, 13852 13853 /** 13854 * Sets given object as the only active object on canvas 13855 * @param {fabric.Object} object Object to set as an active one 13856 * @param {Event} [e] Event (passed along when firing "object:selected") 13857 * @return {fabric.Canvas} thisArg 13858 * @chainable 13859 */ 13860 setActiveObject: function (object, e) { 13861 var currentActives = this.getActiveObjects(); 13862 this._setActiveObject(object, e); 13863 this._fireSelectionEvents(currentActives, e); 13864 // if (object.objType == 'text' || object.type == 'text' || object.type == 'i-text') { 13865 // object.setControlsVisibility({ mb: false, ml: false, mr: false, mt: false }); 13866 // } 13867 // object.set({ 13868 // borderColor: "#6E6EB9", cornerColor: "#6E6EB9", cornerStrokeColor: "#6E6EB9", cornerSize: 13, cornerStyle: "circle", 13869 // transparentCorners: false, hasBorders: true, hasControls: true, 13870 // }); 13871 // object.controls.mtr.offsetY = -25; 13872 return this; 13873 }, 13874 13875 /** 13876 * This is a private method for now. 13877 * This is supposed to be equivalent to setActiveObject but without firing 13878 * any event. There is commitment to have this stay this way. 13879 * This is the functional part of setActiveObject. 13880 * @private 13881 * @param {Object} object to set as active 13882 * @param {Event} [e] Event (passed along when firing "object:selected") 13883 * @return {Boolean} true if the selection happened 13884 */
13885 _setActiveObject: function (object, e) { 13886 if (this._activeObject === object) { 13887 return false; 13888 } 13889 if (!this._discardActiveObject(e, object)) { 13890 return false; 13891 } 13892 if (object.onSelect({ e: e })) { 13893 return false; 13894 } 13895 this._activeObject = object; 13896 return true; 13897 }, 13898 13899 /** 13900 * This is a private method for now. 13901 * This is supposed to be equivalent to discardActiveObject but without firing 13902 * any events. There is commitment to have this stay this way. 13903 * This is the functional part of discardActiveObject. 13904 * @param {Event} [e] Event (passed along when firing "object:deselected") 13905 * @param {Object} object to set as active 13906 * @return {Boolean} true if the selection happened 13907 * @private 13908 */ 13909 _discardActiveObject: function (e, object) { 13910 var obj = this._activeObject; 13911 if (obj) { 13912 // onDeselect return TRUE to cancel selection; 13913 if (obj.onDeselect({ e: e, object: object })) { 13914 return false; 13915 } 13916 this._activeObject = null; 13917 } 13918 return true; 13919 }, 13920 13921 /** 13922 * Discards currently active object and fire events. If the function is called by fabric 13923 * as a consequence of a mouse event, the event is passed as a parameter and 13924 * sent to the fire function for the custom events. When used as a method the 13925 * e param does not have any application. 13926 * @param {event} e 13927 * @return {fabric.Canvas} thisArg 13928 * @chainable 13929 */ 13930 discardActiveObject: function (e) { 13931 var currentActives = this.getActiveObjects(), 13932 activeObject = this.getActiveObject(); 13933 if (currentActives.length) { 13934 this.fire("before:selection:cleared", { target: activeObject, e: e }); 13935 } 13936 this._discardActiveObject(e); 13937 this._fireSelectionEvents(currentActives, e); 13938 return this; 13939 }, 13940 13941 /** 13942 * Clears a canvas element and removes all event listeners 13943 * @return {fabric.Canvas} thisArg 13944 * @chainable 13945 */ 13946 dispose: function () { 13947 var wrapper = this.wrapperEl; 13948 this.removeListeners(); 13949 wrapper.removeChild(this.upperCanvasEl); 13950 wrapper.removeChild(this.lowerCanvasEl); 13951 this.contextCache = null; 13952 this.contextTop = null; 13953 ["upperCanvasEl", "cacheCanvasEl"].forEach( 13954 function (element) { 13955 fabric.util.cleanUpJsdomNode(this[element]); 13956 this[element] = undefined; 13957 }.bind(this), 13958 ); 13959 if (wrapper.parentNode) { 13960 wrapper.parentNode.replaceChild(this.lowerCanvasEl, this.wrapperEl); 13961 } 13962 delete this.wrapperEl; 13963 fabric.StaticCanvas.prototype.dispose.call(this); 13964 return this; 13965 }, 13966 13967 /** 13968 * Clears all contexts (background, main, top) of an instance 13969 * @return {fabric.Canvas} thisArg 13970 * @chainable 13971 */ 13972 clear: function () { 13973 // this.discardActiveGroup(); 13974 this.discardActiveObject(); 13975 this.clearContext(this.contextTop); 13976 return this.callSuper("clear"); 13977 }, 13978 13979 /** 13980 * Draws objects' controls (borders/controls) 13981 * @param {CanvasRenderingContext2D} ctx Context to render controls on 13982 */ 13983 drawControls: function (ctx) { 13984 var activeObject = this._activeObject; 13985 13986 if (activeObject) { 13987 activeObject._renderControls(ctx); 13988 } 13989 }, 13990 13991 /** 13992 * @private 13993 */ 13994 _toObject: function (instance, methodName, propertiesToInclude) { 13995 //If the object is part of the current selection group, it should 13996 //be transformed appropriately 13997 //i.e. it should be serialised as it would appear if the selection group 13998 //were to be destroyed. 13999 var originalProperties = this._realizeGroupTransformOnObject(instance), 14000 object = this.callSuper( 14001 "_toObject", 14002 instance, 14003 methodName, 14004 propertiesToInclude, 14005 ); 14006 //Undo the damage we did by changing all of its properties 14007 this._unwindGroupTransformOnObject(instance, originalProperties); 14008 return object; 14009 }, 14010 14011 /** 14012 * Realises an object's group transformation on it 14013 * @private 14014 * @param {fabric.Object} [instance] the object to transform (gets mutated) 14015 * @returns the original values of instance which were changed 14016 */ 14017 _realizeGroupTransformOnObject: function (instance) { 14018 if ( 14019 instance.group && 14020 instance.group.type === "activeSelection" && 14021 this._activeObject === instance.group 14022 ) { 14023 var layoutProps = [ 14024 "angle", 14025 "flipX", 14026 "flipY", 14027 "left", 14028 "scaleX", 14029 "scaleY", 14030 "skewX", 14031 "skewY", 14032 "top", 14033 ]; 14034 //Copy all the positionally relevant properties across now 14035 var originalValues = {}; 14036 layoutProps.forEach(function (prop) { 14037 originalValues[prop] = instance[prop]; 14038 }); 14039 fabric.util.addTransformToObject( 14040 instance, 14041 this._activeObject.calcOwnMatrix(), 14042 ); 14043 return originalValues; 14044 } else { 14045 return null; 14046 } 14047 }, 14048 14049 /** 14050 * Restores the changed properties of instance 14051 * @private 14052 * @param {fabric.Object} [instance] the object to un-transform (gets mutated) 14053 * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject 14054 */ 14055 _unwindGroupTransformOnObject: function (instance, originalValues) { 14056 if (originalValues) { 14057 instance.set(originalValues); 14058 } 14059 }, 14060 14061 /** 14062 * @private 14063 */ 14064 _setSVGObject: function (markup, instance, reviver) { 14065 //If the object is in a selection group, simulate what would happen to that 14066 //object when the group is deselected 14067 var originalProperties = this._realizeGroupTransformOnObject(instance); 14068 this.callSuper("_setSVGObject", markup, instance, reviver); 14069 this._unwindGroupTransformOnObject(instance, originalProperties); 14070 }, 14071
14072 setViewportTransform: function (vpt) { 14073 if ( 14074 this.renderOnAddRemove && 14075 this._activeObject && 14076 this._activeObject.isEditing 14077 ) { 14078 this._activeObject.clearContextTop(); 14079 } 14080 fabric.StaticCanvas.prototype.setViewportTransform.call(this, vpt); 14081 }, 14082 }, 14083 ); 14084 14085 // copying static properties manually to work around Opera's bug, 14086 // where "prototype" property is enumerable and overrides existing prototype 14087 for (var prop in fabric.StaticCanvas) { 14088 if (prop !== "prototype") { 14089 fabric.Canvas[prop] = fabric.StaticCanvas[prop]; 14090 } 14091 } 14092})(); 14093 14094(function () { 14095 var addListener = fabric.util.addListener, 14096 removeListener = fabric.util.removeListener, 14097 RIGHT_CLICK = 3, 14098 MIDDLE_CLICK = 2, 14099 LEFT_CLICK = 1, 14100 addEventOptions = { passive: false }; 14101 14102 function checkClick(e, value) { 14103 return e.button && e.button === value - 1; 14104 } 14105 14106 fabric.util.object.extend( 14107 fabric.Canvas.prototype, 14108 /** @lends fabric.Canvas.prototype */ { 14109 /** 14110 * Contains the id of the touch event that owns the fabric transform 14111 * @type Number 14112 * @private 14113 */ 14114 mainTouchId: null, 14115 14116 /** 14117 * Adds mouse listeners to canvas 14118 * @private 14119 */ 14120 _initEventListeners: function () { 14121 // in case we initialized the class twice. This should not happen normally 14122 // but in some kind of applications where the canvas element may be changed 14123 // this is a workaround to having double listeners. 14124 this.removeListeners(); 14125 this._bindEvents(); 14126 this.addOrRemove(addListener, "add"); 14127 }, 14128 14129 /** 14130 * return an event prefix pointer or mouse. 14131 * @private 14132 */ 14133 _getEventPrefix: function () { 14134 return this.enablePointerEvents ? "pointer" : "mouse"; 14135 }, 14136 14137 addOrRemove: function (functor, eventjsFunctor) { 14138 var canvasElement = this.upperCanvasEl, 14139 eventTypePrefix = this._getEventPrefix(); 14140 functor(fabric.window, "resize", this._onResize); 14141 functor(canvasElement, eventTypePrefix + "down", this._onMouseDown); 14142 functor( 14143 canvasElement, 14144 eventTypePrefix + "move", 14145 this._onMouseMove, 14146 addEventOptions, 14147 ); 14148 functor(canvasElement, eventTypePrefix + "out", this._onMouseOut); 14149 functor(canvasElement, eventTypePrefix + "enter", this._onMouseEnter); 14150 functor(canvasElement, "wheel", this._onMouseWheel); 14151 functor(canvasElement, "contextmenu", this._onContextMenu); 14152 functor(canvasElement, "dblclick", this._onDoubleClick); 14153 functor(canvasElement, "dragover", this._onDragOver); 14154 functor(canvasElement, "dragenter", this._onDragEnter); 14155 functor(canvasElement, "dragleave", this._onDragLeave); 14156 functor(canvasElement, "drop", this._onDrop); 14157 if (!this.enablePointerEvents) { 14158 functor( 14159 canvasElement, 14160 "touchstart", 14161 this._onTouchStart, 14162 addEventOptions, 14163 ); 14164 } 14165 if (typeof eventjs !== "undefined" && eventjsFunctor in eventjs) { 14166 eventjs[eventjsFunctor](canvasElement, "gesture", this._onGesture); 14167 eventjs[eventjsFunctor](canvasElement, "drag", this._onDrag); 14168 eventjs[eventjsFunctor]( 14169 canvasElement, 14170 "orientation", 14171 this._onOrientationChange, 14172 ); 14173 eventjs[eventjsFunctor](canvasElement, "shake", this._onShake); 14174 eventjs[eventjsFunctor]( 14175 canvasElement, 14176 "longpress", 14177 this._onLongPress, 14178 ); 14179 } 14180 }, 14181 14182 /** 14183 * Removes all event listeners 14184 */ 14185 removeListeners: function () { 14186 this.addOrRemove(removeListener, "remove"); 14187 // if you dispose on a mouseDown, before mouse up, you need to clean document to... 14188 var eventTypePrefix = this._getEventPrefix(); 14189 removeListener( 14190 fabric.document, 14191 eventTypePrefix + "up", 14192 this._onMouseUp, 14193 ); 14194 removeListener( 14195 fabric.document, 14196 "touchend", 14197 this._onTouchEnd, 14198 addEventOptions, 14199 ); 14200 removeListener( 14201 fabric.document, 14202 eventTypePrefix + "move", 14203 this._onMouseMove, 14204 addEventOptions, 14205 ); 14206 removeListener( 14207 fabric.document, 14208 "touchmove", 14209 this._onMouseMove, 14210 addEventOptions, 14211 ); 14212 }, 14213 14214 /** 14215 * @private 14216 */ 14217 _bindEvents: function () { 14218 if (this.eventsBound) { 14219 // for any reason we pass here twice we do not want to bind events twice. 14220 return; 14221 } 14222 this._onMouseDown = this._onMouseDown.bind(this); 14223 this._onTouchStart = this._onTouchStart.bind(this); 14224 this._onMouseMove = this._onMouseMove.bind(this); 14225 this._onMouseUp = this._onMouseUp.bind(this); 14226 this._onTouchEnd = this._onTouchEnd.bind(this); 14227 this._onResize = this._onResize.bind(this); 14228 this._onGesture = this._onGesture.bind(this); 14229 this._onDrag = this._onDrag.bind(this); 14230 this._onShake = this._onShake.bind(this); 14231 this._onLongPress = this._onLongPress.bind(this); 14232 this._onOrientationChange = this._onOrientationChange.bind(this); 14233 this._onMouseWheel = this._onMouseWheel.bind(this); 14234 this._onMouseOut = this._onMouseOut.bind(this); 14235 this._onMouseEnter = this._onMouseEnter.bind(this); 14236 this._onContextMenu = this._onContextMenu.bind(this); 14237 this._onDoubleClick = this._onDoubleClick.bind(this); 14238 this._onDragOver = this._onDragOver.bind(this); 14239 this._onDragEnter = this._simpleEventHandler.bind(this, "dragenter"); 14240 this._onDragLeave = this._simpleEventHandler.bind(this, "dragleave"); 14241 this._onDrop = this._onDrop.bind(this); 14242 this.eventsBound = true; 14243 }, 14244 14245 /** 14246 * @private 14247 * @param {Event} [e] Event object fired on Event.js gesture 14248 * @param {Event} [self] Inner Event object 14249 */ 14250 _onGesture: function (e, self) { 14251 this.__onTransformGesture && this.__onTransformGesture(e, self); 14252 }, 14253 14254 /** 14255 * @private 14256 * @param {Event} [e] Event object fired on Event.js drag 14257 * @param {Event} [self] Inner Event object 14258 */ 14259 _onDrag: function (e, self) { 14260 this.__onDrag && this.__onDrag(e, self); 14261 }, 14262 14263 /** 14264 * @private 14265 * @param {Event} [e] Event object fired on wheel event 14266 */ 14267 _onMouseWheel: function (e) { 14268 this.__onMouseWheel(e); 14269 }, 14270 14271 /** 14272 * @private 14273 * @param {Event} e Event object fired on mousedown 14274 */ 14275 _onMouseOut: function (e) { 14276 var target = this._hoveredTarget; 14277 this.fire("mouse:out", { target: target, e: e }); 14278 this._hoveredTarget = null; 14279 target && target.fire("mouseout", { e: e }); 14280 14281 var _this = this;
vendor: 4,594 bytes, lines 14282-14430
14282 this._hoveredTargets.forEach(function (_target) { 14283 _this.fire("mouse:out", { target: target, e: e }); 14284 _target && target.fire("mouseout", { e: e }); 14285 }); 14286 this._hoveredTargets = []; 14287 }, 14288 14289 /** 14290 * @private 14291 * @param {Event} e Event object fired on mouseenter 14292 */ 14293 _onMouseEnter: function (e) { 14294 // This find target and consequent 'mouse:over' is used to 14295 // clear old instances on hovered target. 14296 // calling findTarget has the side effect of killing target.__corner. 14297 // as a short term fix we are not firing this if we are currently transforming. 14298 // as a long term fix we need to separate the action of finding a target with the 14299 // side effects we added to it. 14300 if (!this._currentTransform && !this.findTarget(e)) { 14301 this.fire("mouse:over", { target: null, e: e }); 14302 this._hoveredTarget = null; 14303 this._hoveredTargets = []; 14304 } 14305 }, 14306 14307 /** 14308 * @private 14309 * @param {Event} [e] Event object fired on Event.js orientation change 14310 * @param {Event} [self] Inner Event object 14311 */ 14312 _onOrientationChange: function (e, self) { 14313 this.__onOrientationChange && this.__onOrientationChange(e, self); 14314 }, 14315 14316 /** 14317 * @private 14318 * @param {Event} [e] Event object fired on Event.js shake 14319 * @param {Event} [self] Inner Event object 14320 */ 14321 _onShake: function (e, self) { 14322 this.__onShake && this.__onShake(e, self); 14323 }, 14324 14325 /** 14326 * @private 14327 * @param {Event} [e] Event object fired on Event.js shake 14328 * @param {Event} [self] Inner Event object 14329 */ 14330 _onLongPress: function (e, self) { 14331 this.__onLongPress && this.__onLongPress(e, self); 14332 }, 14333 14334 /** 14335 * prevent default to allow drop event to be fired 14336 * @private 14337 * @param {Event} [e] Event object fired on Event.js shake 14338 */ 14339 _onDragOver: function (e) { 14340 e.preventDefault(); 14341 var target = this._simpleEventHandler("dragover", e); 14342 this._fireEnterLeaveEvents(target, e); 14343 }, 14344 14345 /** 14346 * `drop:before` is a an event that allow you to schedule logic 14347 * before the `drop` event. Prefer `drop` event always, but if you need 14348 * to run some drop-disabling logic on an event, since there is no way 14349 * to handle event handlers ordering, use `drop:before` 14350 * @param {Event} e 14351 */ 14352 _onDrop: function (e) { 14353 this._simpleEventHandler("drop:before", e); 14354 return this._simpleEventHandler("drop", e); 14355 }, 14356 14357 /** 14358 * @private 14359 * @param {Event} e Event object fired on mousedown 14360 */ 14361 _onContextMenu: function (e) { 14362 if (this.stopContextMenu) { 14363 e.stopPropagation(); 14364 e.preventDefault(); 14365 } 14366 return false; 14367 }, 14368 14369 /** 14370 * @private 14371 * @param {Event} e Event object fired on mousedown 14372 */ 14373 _onDoubleClick: function (e) { 14374 this._cacheTransformEventData(e); 14375 this._handleEvent(e, "dblclick"); 14376 this._resetTransformEventData(e); 14377 }, 14378 14379 /** 14380 * Return a the id of an event. 14381 * returns either the pointerId or the identifier or 0 for the mouse event 14382 * @private 14383 * @param {Event} evt Event object 14384 */ 14385 getPointerId: function (evt) { 14386 var changedTouches = evt.changedTouches; 14387 14388 if (changedTouches) { 14389 return changedTouches[0] && changedTouches[0].identifier; 14390 } 14391 14392 if (this.enablePointerEvents) { 14393 return evt.pointerId; 14394 } 14395 14396 return -1; 14397 }, 14398 14399 /** 14400 * Determines if an event has the id of the event that is considered main 14401 * @private 14402 * @param {evt} event Event object 14403 */ 14404 _isMainEvent: function (evt) { 14405 if (evt.isPrimary === true) { 14406 return true; 14407 } 14408 if (evt.isPrimary === false) { 14409 return false; 14410 } 14411 if (evt.type === "touchend" && evt.touches.length === 0) { 14412 return true; 14413 } 14414 if (evt.changedTouches) { 14415 return evt.changedTouches[0].identifier === this.mainTouchId; 14416 } 14417 return true; 14418 }, 14419 14420 /** 14421 * @private 14422 * @param {Event} e Event object fired on mousedown 14423 */ 14424 _onTouchStart: function (e) { 14425 e.preventDefault(); 14426 if (this.mainTouchId === null) { 14427 this.mainTouchId = this.getPointerId(e); 14428 } 14429 this.__onMouseDown(e); 14430 this._resetTransformEventData();
14431 var canvasElement = this.upperCanvasEl, 14432 eventTypePrefix = this._getEventPrefix(); 14433 addListener( 14434 fabric.document, 14435 "touchend", 14436 this._onTouchEnd, 14437 addEventOptions, 14438 ); 14439 addListener( 14440 fabric.document, 14441 "touchmove", 14442 this._onMouseMove, 14443 addEventOptions, 14444 ); 14445 // Unbind mousedown to prevent double triggers from touch devices 14446 removeListener( 14447 canvasElement, 14448 eventTypePrefix + "down", 14449 this._onMouseDown, 14450 ); 14451 }, 14452 14453 /** 14454 * @private 14455 * @param {Event} e Event object fired on mousedown 14456 */ 14457 _onMouseDown: function (e) { 14458 this.__onMouseDown(e); 14459 this._resetTransformEventData(); 14460 var canvasElement = this.upperCanvasEl, 14461 eventTypePrefix = this._getEventPrefix(); 14462 removeListener( 14463 canvasElement, 14464 eventTypePrefix + "move", 14465 this._onMouseMove, 14466 addEventOptions, 14467 ); 14468 addListener(fabric.document, eventTypePrefix + "up", this._onMouseUp); 14469 addListener( 14470 fabric.document, 14471 eventTypePrefix + "move", 14472 this._onMouseMove, 14473 addEventOptions, 14474 ); 14475 }, 14476 14477 /** 14478 * @private 14479 * @param {Event} e Event object fired on mousedown 14480 */ 14481 _onTouchEnd: function (e) { 14482 if (e.touches.length > 0) { 14483 // if there are still touches stop here 14484 return; 14485 } 14486 this.__onMouseUp(e); 14487 this._resetTransformEventData(); 14488 this.mainTouchId = null; 14489 var eventTypePrefix = this._getEventPrefix(); 14490 removeListener( 14491 fabric.document, 14492 "touchend", 14493 this._onTouchEnd, 14494 addEventOptions, 14495 ); 14496 removeListener( 14497 fabric.document, 14498 "touchmove", 14499 this._onMouseMove, 14500 addEventOptions, 14501 ); 14502 var _this = this; 14503 if (this._willAddMouseDown) { 14504 clearTimeout(this._willAddMouseDown); 14505 } 14506 this._willAddMouseDown = setTimeout(function () { 14507 // Wait 400ms before rebinding mousedown to prevent double triggers 14508 // from touch devices 14509 addListener( 14510 _this.upperCanvasEl, 14511 eventTypePrefix + "down", 14512 _this._onMouseDown, 14513 ); 14514 _this._willAddMouseDown = 0; 14515 }, 400); 14516 }, 14517 14518 /** 14519 * @private 14520 * @param {Event} e Event object fired on mouseup 14521 */ 14522 _onMouseUp: function (e) { 14523 this.__onMouseUp(e); 14524 this._resetTransformEventData(); 14525 var canvasElement = this.upperCanvasEl, 14526 eventTypePrefix = this._getEventPrefix(); 14527 if (this._isMainEvent(e)) { 14528 removeListener( 14529 fabric.document, 14530 eventTypePrefix + "up", 14531 this._onMouseUp, 14532 ); 14533 removeListener( 14534 fabric.document, 14535 eventTypePrefix + "move", 14536 this._onMouseMove, 14537 addEventOptions, 14538 ); 14539 addListener( 14540 canvasElement, 14541 eventTypePrefix + "move", 14542 this._onMouseMove, 14543 addEventOptions, 14544 ); 14545 } 14546 }, 14547 14548 /** 14549 * @private 14550 * @param {Event} e Event object fired on mousemove 14551 */ 14552 _onMouseMove: function (e) { 14553 !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); 14554 this.__onMouseMove(e); 14555 }, 14556 14557 /** 14558 * @private 14559 */ 14560 _onResize: function () { 14561 this.calcOffset(); 14562 }, 14563 14564 /** 14565 * Decides whether the canvas should be redrawn in mouseup and mousedown events. 14566 * @private 14567 * @param {Object} target 14568 */ 14569 _shouldRender: function (target) { 14570 var activeObject = this._activeObject; 14571 14572 if ( 14573 !!activeObject !== !!target || 14574 (activeObject && target && activeObject !== target) 14575 ) { 14576 // this covers: switch of target, from target to no target, selection of target 14577 // multiSelection with key and mouse 14578 return true; 14579 } else if (activeObject && activeObject.isEditing) { 14580 // if we mouse up/down over a editing textbox a cursor change, 14581 // there is no need to re render 14582 return false;
14583 } 14584 return false; 14585 }, 14586 14587 /** 14588 * Method that defines the actions when mouse is released on canvas. 14589 * The method resets the currentTransform parameters, store the image corner 14590 * position in the image object and render the canvas on top. 14591 * @private 14592 * @param {Event} e Event object fired on mouseup 14593 */ 14594 __onMouseUp: function (e) { 14595 var target, 14596 transform = this._currentTransform, 14597 groupSelector = this._groupSelector, 14598 shouldRender = false, 14599 isClick = 14600 !groupSelector || 14601 (groupSelector.left === 0 && groupSelector.top === 0); 14602 this._cacheTransformEventData(e); 14603 target = this._target; 14604 this._handleEvent(e, "up:before"); 14605 // if right/middle click just fire events and return 14606 // target undefined will make the _handleEvent search the target 14607 if (checkClick(e, RIGHT_CLICK)) { 14608 if (this.fireRightClick) { 14609 this._handleEvent(e, "up", RIGHT_CLICK, isClick); 14610 } 14611 return; 14612 } 14613 14614 if (checkClick(e, MIDDLE_CLICK)) { 14615 if (this.fireMiddleClick) { 14616 this._handleEvent(e, "up", MIDDLE_CLICK, isClick); 14617 } 14618 this._resetTransformEventData(); 14619 return; 14620 } 14621 14622 if (this.isDrawingMode && this._isCurrentlyDrawing) { 14623 this._onMouseUpInDrawingMode(e); 14624 return; 14625 } 14626 14627 if (!this._isMainEvent(e)) { 14628 return; 14629 } 14630 if (transform) { 14631 this._finalizeCurrentTransform(e); 14632 shouldRender = transform.actionPerformed; 14633 } 14634 if (!isClick) { 14635 var targetWasActive = target === this._activeObject; 14636 this._maybeGroupObjects(e); 14637 if (!shouldRender) { 14638 shouldRender = 14639 this._shouldRender(target) || 14640 (!targetWasActive && target === this._activeObject); 14641 } 14642 } 14643 var corner, pointer; 14644 if (target) { 14645 corner = target._findTargetCorner( 14646 this.getPointer(e, true), 14647 fabric.util.isTouchEvent(e), 14648 ); 14649 if ( 14650 target.selectable && 14651 target !== this._activeObject && 14652 target.activeOn === "up" 14653 ) { 14654 this.setActiveObject(target, e); 14655 shouldRender = true; 14656 } else { 14657 var control = target.controls[corner], 14658 mouseUpHandler = 14659 control && control.getMouseUpHandler(e, target, control); 14660 if (mouseUpHandler) { 14661 pointer = this.getPointer(e); 14662 mouseUpHandler(e, transform, pointer.x, pointer.y); 14663 } 14664 } 14665 target.isMoving = false; 14666 } 14667 // if we are ending up a transform on a different control or a new object 14668 // fire the original mouse up from the corner that started the transform 14669 if ( 14670 transform && 14671 (transform.target !== target || transform.corner !== corner) 14672 ) { 14673 var originalControl = 14674 transform.target && transform.target.controls[transform.corner], 14675 originalMouseUpHandler = 14676 originalControl && 14677 originalControl.getMouseUpHandler(e, target, control); 14678 pointer = pointer || this.getPointer(e); 14679 originalMouseUpHandler && 14680 originalMouseUpHandler(e, transform, pointer.x, pointer.y); 14681 } 14682 this._setCursorFromEvent(e, target); 14683 this._handleEvent(e, "up", LEFT_CLICK, isClick); 14684 this._groupSelector = null; 14685 this._currentTransform = null; 14686 // reset the target information about which corner is selected 14687 target && (target.__corner = 0); 14688 if (shouldRender) { 14689 this.requestRenderAll(); 14690 } else if (!isClick) { 14691 this.renderTop(); 14692 } 14693 }, 14694 14695 /** 14696 * @private 14697 * Handle event firing for target and subtargets 14698 * @param {Event} e event from mouse 14699 * @param {String} eventType event to fire (up, down or move) 14700 * @return {Fabric.Object} target return the the target found, for internal reasons. 14701 */ 14702 _simpleEventHandler: function (eventType, e) { 14703 var target = this.findTarget(e), 14704 targets = this.targets, 14705 options = { 14706 e: e, 14707 target: target, 14708 subTargets: targets, 14709 }; 14710 this.fire(eventType, options); 14711 target && target.fire(eventType, options); 14712 if (!targets) { 14713 return target; 14714 } 14715 for (var i = 0; i < targets.length; i++) { 14716 targets[i].fire(eventType, options); 14717 } 14718 return target; 14719 }, 14720 14721 /** 14722 * @private 14723 * Handle event firing for target and subtargets 14724 * @param {Event} e event from mouse 14725 * @param {String} eventType event to fire (up, down or move) 14726 * @param {fabric.Object} targetObj receiving event 14727 * @param {Number} [button] button used in the event 1 = left, 2 = middle, 3 = right 14728 * @param {Boolean} isClick for left button only, indicates that the mouse up happened without move. 14729 */ 14730 _handleEvent: function (e, eventType, button, isClick) { 14731 var target = this._target, 14732 targets = this.targets || [], 14733 options = { 14734 e: e, 14735 target: target, 14736 subTargets: targets, 14737 button: button || LEFT_CLICK, 14738 isClick: isClick || false, 14739 pointer: this._pointer, 14740 absolutePointer: this._absolutePointer, 14741 transform: this._currentTransform, 14742 }; 14743 if (eventType === "up") { 14744 options.currentTarget = this.findTarget(e); 14745 options.currentSubTargets = this.targets; 14746 } 14747 this.fire("mouse:" + eventType, options); 14748 target && target.fire("mouse" + eventType, options); 14749 for (var i = 0; i < targets.length; i++) { 14750 targets[i].fire("mouse" + eventType, options); 14751 } 14752 }, 14753 14754 /** 14755 * @private 14756 * @param {Event} e send the mouse event that generate the finalize down, so it can be used in the event 14757 */ 14758 _finalizeCurrentTransform: function (e) { 14759 var transform = this._currentTransform, 14760 target = transform.target, 14761 options = { 14762 e: e, 14763 target: target, 14764 transform: transform, 14765 action: transform.action, 14766 }; 14767 14768 if (target._scaling) { 14769 target._scaling = false;
14770 } 14771 14772 target.setCoords(); 14773 14774 if ( 14775 transform.actionPerformed || 14776 (this.stateful && target.hasStateChanged()) 14777 ) { 14778 this._fire("modified", options); 14779 } 14780 }, 14781 14782 /** 14783 * @private 14784 * @param {Event} e Event object fired on mousedown 14785 */ 14786 _onMouseDownInDrawingMode: function (e) { 14787 this._isCurrentlyDrawing = true; 14788 if (this.getActiveObject()) { 14789 this.discardActiveObject(e).requestRenderAll(); 14790 } 14791 var pointer = this.getPointer(e); 14792 this.freeDrawingBrush.onMouseDown(pointer, { e: e, pointer: pointer }); 14793 this._handleEvent(e, "down"); 14794 }, 14795 14796 /** 14797 * @private 14798 * @param {Event} e Event object fired on mousemove 14799 */ 14800 _onMouseMoveInDrawingMode: function (e) { 14801 if (this._isCurrentlyDrawing) { 14802 var pointer = this.getPointer(e); 14803 this.freeDrawingBrush.onMouseMove(pointer, { 14804 e: e, 14805 pointer: pointer, 14806 }); 14807 } 14808 this.setCursor(this.freeDrawingCursor); 14809 this._handleEvent(e, "move"); 14810 }, 14811 14812 /** 14813 * @private 14814 * @param {Event} e Event object fired on mouseup 14815 */ 14816 _onMouseUpInDrawingMode: function (e) { 14817 var pointer = this.getPointer(e); 14818 this._isCurrentlyDrawing = this.freeDrawingBrush.onMouseUp({ 14819 e: e, 14820 pointer: pointer, 14821 }); 14822 this._handleEvent(e, "up"); 14823 }, 14824 14825 /** 14826 * Method that defines the actions when mouse is clicked on canvas. 14827 * The method inits the currentTransform parameters and renders all the 14828 * canvas so the current image can be placed on the top canvas and the rest 14829 * in on the container one. 14830 * @private 14831 * @param {Event} e Event object fired on mousedown 14832 */ 14833 __onMouseDown: function (e) { 14834 this._cacheTransformEventData(e); 14835 this._handleEvent(e, "down:before"); 14836 var target = this._target; 14837 // if right click just fire events 14838 if (checkClick(e, RIGHT_CLICK)) { 14839 if (this.fireRightClick) { 14840 this._handleEvent(e, "down", RIGHT_CLICK); 14841 } 14842 return; 14843 } 14844 14845 if (checkClick(e, MIDDLE_CLICK)) { 14846 if (this.fireMiddleClick) { 14847 this._handleEvent(e, "down", MIDDLE_CLICK); 14848 } 14849 return; 14850 } 14851 14852 if (this.isDrawingMode) { 14853 this._onMouseDownInDrawingMode(e); 14854 return; 14855 } 14856 14857 if (!this._isMainEvent(e)) { 14858 return; 14859 } 14860 14861 // ignore if some object is being transformed at this moment 14862 if (this._currentTransform) { 14863 return; 14864 } 14865 14866 var pointer = this._pointer; 14867 // save pointer for check in __onMouseUp event 14868 this._previousPointer = pointer; 14869 var shouldRender = this._shouldRender(target), 14870 shouldGroup = this._shouldGroup(e, target); 14871 if (this._shouldClearSelection(e, target)) { 14872 this.discardActiveObject(e); 14873 } else if (shouldGroup) { 14874 this._handleGrouping(e, target); 14875 target = this._activeObject; 14876 } 14877 14878 if ( 14879 this.selection && 14880 (!target || 14881 (!target.selectable && 14882 !target.isEditing && 14883 target !== this._activeObject)) 14884 ) { 14885 this._groupSelector = { 14886 ex: this._absolutePointer.x, 14887 ey: this._absolutePointer.y, 14888 top: 0, 14889 left: 0, 14890 }; 14891 } 14892 14893 if (target) { 14894 var alreadySelected = target === this._activeObject; 14895 if (target.selectable && target.activeOn === "down") { 14896 this.setActiveObject(target, e); 14897 } 14898 var corner = target._findTargetCorner( 14899 this.getPointer(e, true), 14900 fabric.util.isTouchEvent(e), 14901 ); 14902 target.__corner = corner; 14903 if (target === this._activeObject && (corner || !shouldGroup)) { 14904 this._setupCurrentTransform(e, target, alreadySelected); 14905 var control = target.controls[corner], 14906 pointer = this.getPointer(e), 14907 mouseDownHandler = 14908 control && control.getMouseDownHandler(e, target, control); 14909 if (mouseDownHandler) { 14910 mouseDownHandler(e, this._currentTransform, pointer.x, pointer.y); 14911 } 14912 } 14913 } 14914 this._handleEvent(e, "down");
vendor: 17,610 bytes, lines 14915-15425
14915 // we must renderAll so that we update the visuals 14916 (shouldRender || shouldGroup) && this.requestRenderAll(); 14917 }, 14918 14919 /** 14920 * reset cache form common information needed during event processing 14921 * @private 14922 */ 14923 _resetTransformEventData: function () { 14924 this._target = null; 14925 this._pointer = null; 14926 this._absolutePointer = null; 14927 }, 14928 14929 /** 14930 * Cache common information needed during event processing 14931 * @private 14932 * @param {Event} e Event object fired on event 14933 */ 14934 _cacheTransformEventData: function (e) { 14935 // reset in order to avoid stale caching 14936 this._resetTransformEventData(); 14937 this._pointer = this.getPointer(e, true); 14938 this._absolutePointer = this.restorePointerVpt(this._pointer); 14939 this._target = this._currentTransform 14940 ? this._currentTransform.target 14941 : this.findTarget(e) || null; 14942 }, 14943 14944 /** 14945 * @private 14946 */ 14947 _beforeTransform: function (e) { 14948 var t = this._currentTransform; 14949 this.stateful && t.target.saveState(); 14950 this.fire("before:transform", { 14951 e: e, 14952 transform: t, 14953 }); 14954 }, 14955 14956 /** 14957 * Method that defines the actions when mouse is hovering the canvas. 14958 * The currentTransform parameter will define whether the user is rotating/scaling/translating 14959 * an image or neither of them (only hovering). A group selection is also possible and would cancel 14960 * all any other type of action. 14961 * In case of an image transformation only the top canvas will be rendered. 14962 * @private 14963 * @param {Event} e Event object fired on mousemove 14964 */ 14965 __onMouseMove: function (e) { 14966 this._handleEvent(e, "move:before"); 14967 this._cacheTransformEventData(e); 14968 var target, pointer; 14969 14970 if (this.isDrawingMode) { 14971 this._onMouseMoveInDrawingMode(e); 14972 return; 14973 } 14974 14975 if (!this._isMainEvent(e)) { 14976 return; 14977 } 14978 14979 var groupSelector = this._groupSelector; 14980 14981 // We initially clicked in an empty area, so we draw a box for multiple selection 14982 if (groupSelector) { 14983 pointer = this._absolutePointer; 14984 14985 groupSelector.left = pointer.x - groupSelector.ex; 14986 groupSelector.top = pointer.y - groupSelector.ey; 14987 14988 this.renderTop(); 14989 } else if (!this._currentTransform) { 14990 target = this.findTarget(e) || null; 14991 this._setCursorFromEvent(e, target); 14992 this._fireOverOutEvents(target, e); 14993 } else { 14994 this._transformObject(e); 14995 } 14996 this._handleEvent(e, "move"); 14997 this._resetTransformEventData(); 14998 }, 14999 15000 /** 15001 * Manage the mouseout, mouseover events for the fabric object on the canvas 15002 * @param {Fabric.Object} target the target where the target from the mousemove event 15003 * @param {Event} e Event object fired on mousemove 15004 * @private 15005 */ 15006 _fireOverOutEvents: function (target, e) { 15007 var _hoveredTarget = this._hoveredTarget, 15008 _hoveredTargets = this._hoveredTargets, 15009 targets = this.targets, 15010 length = Math.max(_hoveredTargets.length, targets.length); 15011 15012 this.fireSyntheticInOutEvents(target, e, { 15013 oldTarget: _hoveredTarget, 15014 evtOut: "mouseout", 15015 canvasEvtOut: "mouse:out", 15016 evtIn: "mouseover", 15017 canvasEvtIn: "mouse:over", 15018 }); 15019 for (var i = 0; i < length; i++) { 15020 this.fireSyntheticInOutEvents(targets[i], e, { 15021 oldTarget: _hoveredTargets[i], 15022 evtOut: "mouseout", 15023 evtIn: "mouseover", 15024 }); 15025 } 15026 this._hoveredTarget = target; 15027 this._hoveredTargets = this.targets.concat(); 15028 }, 15029 15030 /** 15031 * Manage the dragEnter, dragLeave events for the fabric objects on the canvas 15032 * @param {Fabric.Object} target the target where the target from the onDrag event 15033 * @param {Event} e Event object fired on ondrag 15034 * @private 15035 */ 15036 _fireEnterLeaveEvents: function (target, e) { 15037 var _draggedoverTarget = this._draggedoverTarget, 15038 _hoveredTargets = this._hoveredTargets, 15039 targets = this.targets, 15040 length = Math.max(_hoveredTargets.length, targets.length); 15041 15042 this.fireSyntheticInOutEvents(target, e, { 15043 oldTarget: _draggedoverTarget, 15044 evtOut: "dragleave", 15045 evtIn: "dragenter", 15046 }); 15047 for (var i = 0; i < length; i++) { 15048 this.fireSyntheticInOutEvents(targets[i], e, { 15049 oldTarget: _hoveredTargets[i], 15050 evtOut: "dragleave", 15051 evtIn: "dragenter", 15052 }); 15053 } 15054 this._draggedoverTarget = target; 15055 }, 15056 15057 /** 15058 * Manage the synthetic in/out events for the fabric objects on the canvas 15059 * @param {Fabric.Object} target the target where the target from the supported events 15060 * @param {Event} e Event object fired 15061 * @param {Object} config configuration for the function to work 15062 * @param {String} config.targetName property on the canvas where the old target is stored 15063 * @param {String} [config.canvasEvtOut] name of the event to fire at canvas level for out 15064 * @param {String} config.evtOut name of the event to fire for out 15065 * @param {String} [config.canvasEvtIn] name of the event to fire at canvas level for in 15066 * @param {String} config.evtIn name of the event to fire for in 15067 * @private 15068 */ 15069 fireSyntheticInOutEvents: function (target, e, config) { 15070 var inOpt, 15071 outOpt, 15072 oldTarget = config.oldTarget, 15073 outFires, 15074 inFires, 15075 targetChanged = oldTarget !== target, 15076 canvasEvtIn = config.canvasEvtIn, 15077 canvasEvtOut = config.canvasEvtOut; 15078 if (targetChanged) { 15079 inOpt = { e: e, target: target, previousTarget: oldTarget }; 15080 outOpt = { e: e, target: oldTarget, nextTarget: target }; 15081 } 15082 inFires = target && targetChanged; 15083 outFires = oldTarget && targetChanged; 15084 if (outFires) { 15085 canvasEvtOut && this.fire(canvasEvtOut, outOpt); 15086 oldTarget.fire(config.evtOut, outOpt); 15087 } 15088 if (inFires) { 15089 canvasEvtIn && this.fire(canvasEvtIn, inOpt); 15090 target.fire(config.evtIn, inOpt); 15091 } 15092 }, 15093 15094 /** 15095 * Method that defines actions when an Event Mouse Wheel 15096 * @param {Event} e Event object fired on mouseup 15097 */ 15098 __onMouseWheel: function (e) { 15099 this._cacheTransformEventData(e); 15100 this._handleEvent(e, "wheel"); 15101 this._resetTransformEventData(); 15102 }, 15103 15104 /** 15105 * @private 15106 * @param {Event} e Event fired on mousemove 15107 */ 15108 _transformObject: function (e) { 15109 var pointer = this.getPointer(e), 15110 transform = this._currentTransform; 15111 15112 transform.reset = false; 15113 transform.shiftKey = e.shiftKey; 15114 transform.altKey = e[this.centeredKey]; 15115 15116 this._performTransformAction(e, transform, pointer); 15117 transform.actionPerformed && this.requestRenderAll(); 15118 }, 15119 15120 /** 15121 * @private 15122 */ 15123 _performTransformAction: function (e, transform, pointer) { 15124 var x = pointer.x, 15125 y = pointer.y, 15126 action = transform.action, 15127 actionPerformed = false, 15128 actionHandler = transform.actionHandler; 15129 // this object could be created from the function in the control handlers 15130 15131 if (actionHandler) { 15132 actionPerformed = actionHandler(e, transform, x, y); 15133 } 15134 if (action === "drag" && actionPerformed) { 15135 transform.target.isMoving = true; 15136 this.setCursor(transform.target.moveCursor || this.moveCursor); 15137 } 15138 transform.actionPerformed = 15139 transform.actionPerformed || actionPerformed; 15140 }, 15141 15142 /** 15143 * @private 15144 */ 15145 _fire: fabric.controlsUtils.fireEvent, 15146 15147 /** 15148 * Sets the cursor depending on where the canvas is being hovered. 15149 * Note: very buggy in Opera 15150 * @param {Event} e Event object 15151 * @param {Object} target Object that the mouse is hovering, if so. 15152 */ 15153 _setCursorFromEvent: function (e, target) { 15154 if (!target) { 15155 this.setCursor(this.defaultCursor); 15156 return false; 15157 } 15158 var hoverCursor = target.hoverCursor || this.hoverCursor, 15159 activeSelection = 15160 this._activeObject && this._activeObject.type === "activeSelection" 15161 ? this._activeObject 15162 : null, 15163 // only show proper corner when group selection is not active 15164 corner = 15165 (!activeSelection || !activeSelection.contains(target)) && 15166 // here we call findTargetCorner always with undefined for the touch parameter. 15167 // we assume that if you are using a cursor you do not need to interact with 15168 // the bigger touch area. 15169 target._findTargetCorner(this.getPointer(e, true)); 15170 15171 if (!corner) { 15172 if (target.subTargetCheck) { 15173 // hoverCursor should come from top-most subTarget, 15174 // so we walk the array backwards 15175 this.targets 15176 .concat() 15177 .reverse() 15178 .map(function (_target) { 15179 hoverCursor = _target.hoverCursor || hoverCursor; 15180 }); 15181 } 15182 this.setCursor(hoverCursor); 15183 } else { 15184 this.setCursor(this.getCornerCursor(corner, target, e)); 15185 } 15186 }, 15187 15188 /** 15189 * @private 15190 */ 15191 getCornerCursor: function (corner, target, e) { 15192 var control = target.controls[corner]; 15193 return control.cursorStyleHandler(e, control, target); 15194 }, 15195 }, 15196 ); 15197})(); 15198 15199(function () { 15200 var min = Math.min, 15201 max = Math.max; 15202 15203 fabric.util.object.extend( 15204 fabric.Canvas.prototype, 15205 /** @lends fabric.Canvas.prototype */ { 15206 /** 15207 * @private 15208 * @param {Event} e Event object 15209 * @param {fabric.Object} target 15210 * @return {Boolean} 15211 */ 15212 _shouldGroup: function (e, target) { 15213 var activeObject = this._activeObject; 15214 return ( 15215 activeObject && 15216 this._isSelectionKeyPressed(e) && 15217 target && 15218 target.selectable && 15219 this.selection && 15220 (activeObject !== target || 15221 activeObject.type === "activeSelection") && 15222 !target.onSelect({ e: e }) 15223 ); 15224 }, 15225 15226 /** 15227 * @private 15228 * @param {Event} e Event object 15229 * @param {fabric.Object} target 15230 */ 15231 _handleGrouping: function (e, target) { 15232 var activeObject = this._activeObject; 15233 // avoid multi select when shift click on a corner 15234 if (activeObject.__corner) { 15235 return; 15236 } 15237 if (target === activeObject) { 15238 // if it's a group, find target again, using activeGroup objects 15239 target = this.findTarget(e, true); 15240 // if even object is not found or we are on activeObjectCorner, bail out 15241 if (!target || !target.selectable) { 15242 return; 15243 } 15244 } 15245 if (activeObject && activeObject.type === "activeSelection") { 15246 this._updateActiveSelection(target, e); 15247 } else { 15248 this._createActiveSelection(target, e); 15249 } 15250 }, 15251 15252 /** 15253 * @private 15254 */ 15255 _updateActiveSelection: function (target, e) { 15256 var activeSelection = this._activeObject, 15257 currentActiveObjects = activeSelection._objects.slice(0); 15258 if (activeSelection.contains(target)) { 15259 activeSelection.removeWithUpdate(target); 15260 this._hoveredTarget = target; 15261 this._hoveredTargets = this.targets.concat(); 15262 if (activeSelection.size() === 1) { 15263 // activate last remaining object 15264 this._setActiveObject(activeSelection.item(0), e); 15265 } 15266 } else { 15267 activeSelection.addWithUpdate(target); 15268 this._hoveredTarget = activeSelection; 15269 this._hoveredTargets = this.targets.concat(); 15270 } 15271 this._fireSelectionEvents(currentActiveObjects, e); 15272 }, 15273 15274 /** 15275 * @private 15276 */ 15277 _createActiveSelection: function (target, e) { 15278 var currentActives = this.getActiveObjects(), 15279 group = this._createGroup(target); 15280 this._hoveredTarget = group; 15281 // ISSUE 4115: should we consider subTargets here? 15282 // this._hoveredTargets = []; 15283 // this._hoveredTargets = this.targets.concat(); 15284 this._setActiveObject(group, e); 15285 this._fireSelectionEvents(currentActives, e); 15286 }, 15287 15288 /** 15289 * @private 15290 * @param {Object} target 15291 */ 15292 _createGroup: function (target) { 15293 var objects = this._objects, 15294 isActiveLower = 15295 objects.indexOf(this._activeObject) < objects.indexOf(target), 15296 groupObjects = isActiveLower 15297 ? [this._activeObject, target] 15298 : [target, this._activeObject]; 15299 this._activeObject.isEditing && this._activeObject.exitEditing(); 15300 return new fabric.ActiveSelection(groupObjects, { 15301 canvas: this, 15302 }); 15303 }, 15304 15305 /** 15306 * @private 15307 * @param {Event} e mouse event 15308 */ 15309 _groupSelectedObjects: function (e) { 15310 var group = this._collectObjects(e), 15311 aGroup; 15312 15313 // do not create group for 1 element only 15314 if (group.length === 1) { 15315 this.setActiveObject(group[0], e); 15316 } else if (group.length > 1) { 15317 aGroup = new fabric.ActiveSelection(group.reverse(), { 15318 canvas: this, 15319 }); 15320 this.setActiveObject(aGroup, e); 15321 } 15322 }, 15323 15324 /** 15325 * @private 15326 */ 15327 _collectObjects: function (e) { 15328 var group = [], 15329 currentObject, 15330 x1 = this._groupSelector.ex, 15331 y1 = this._groupSelector.ey, 15332 x2 = x1 + this._groupSelector.left, 15333 y2 = y1 + this._groupSelector.top, 15334 selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), 15335 selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), 15336 allowIntersect = !this.selectionFullyContained, 15337 isClick = x1 === x2 && y1 === y2; 15338 // we iterate reverse order to collect top first in case of click. 15339 for (var i = this._objects.length; i--; ) { 15340 currentObject = this._objects[i]; 15341 15342 if ( 15343 !currentObject || 15344 !currentObject.selectable || 15345 !currentObject.visible 15346 ) { 15347 continue; 15348 } 15349 15350 if ( 15351 (allowIntersect && 15352 currentObject.intersectsWithRect( 15353 selectionX1Y1, 15354 selectionX2Y2, 15355 true, 15356 )) || 15357 currentObject.isContainedWithinRect( 15358 selectionX1Y1, 15359 selectionX2Y2, 15360 true, 15361 ) || 15362 (allowIntersect && 15363 currentObject.containsPoint(selectionX1Y1, null, true)) || 15364 (allowIntersect && 15365 currentObject.containsPoint(selectionX2Y2, null, true)) 15366 ) { 15367 group.push(currentObject); 15368 // only add one object if it's a click 15369 if (isClick) { 15370 break; 15371 } 15372 } 15373 } 15374 15375 if (group.length > 1) { 15376 group = group.filter(function (object) { 15377 return !object.onSelect({ e: e }); 15378 }); 15379 } 15380 15381 return group; 15382 }, 15383 15384 /** 15385 * @private 15386 */ 15387 _maybeGroupObjects: function (e) { 15388 if (this.selection && this._groupSelector) { 15389 this._groupSelectedObjects(e); 15390 } 15391 this.setCursor(this.defaultCursor); 15392 // clear selection and current transformation 15393 this._groupSelector = null; 15394 }, 15395 }, 15396 ); 15397})(); 15398 15399(function () { 15400 fabric.util.object.extend( 15401 fabric.StaticCanvas.prototype, 15402 /** @lends fabric.StaticCanvas.prototype */ { 15403 /** 15404 * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately 15405 * @param {Object} [options] Options object 15406 * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" 15407 * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. 15408 * @param {Number} [options.multiplier=1] Multiplier to scale by, to have consistent 15409 * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 15410 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 15411 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 15412 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 15413 * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 2.0.0 15414 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format 15415 * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} 15416 * @example <caption>Generate jpeg dataURL with lower quality</caption> 15417 * var dataURL = canvas.toDataURL({ 15418 * format: 'jpeg', 15419 * quality: 0.8 15420 * }); 15421 * @example <caption>Generate cropped png dataURL (clipping of canvas)</caption> 15422 * var dataURL = canvas.toDataURL({ 15423 * format: 'png', 15424 * left: 100, 15425 * top: 100,
15426 * width: 200, 15427 * height: 200 15428 * }); 15429 * @example <caption>Generate double scaled png dataURL</caption> 15430 * var dataURL = canvas.toDataURL({ 15431 * format: 'png', 15432 * multiplier: 2 15433 * }); 15434 */ 15435 toDataURL: function (options) { 15436 options || (options = {}); 15437 15438 var format = options.format || "png", 15439 quality = options.quality || 1, 15440 multiplier = 15441 (options.multiplier || 1) * 15442 (options.enableRetinaScaling ? this.getRetinaScaling() : 1), 15443 canvasEl = this.toCanvasElement(multiplier, options); 15444 return fabric.util.toDataURL(canvasEl, format, quality); 15445 }, 15446 15447 /** 15448 * Create a new HTMLCanvas element painted with the current canvas content. 15449 * No need to resize the actual one or repaint it. 15450 * Will transfer object ownership to a new canvas, paint it, and set everything back. 15451 * This is an intermediary step used to get to a dataUrl but also it is useful to 15452 * create quick image copies of a canvas without passing for the dataUrl string 15453 * @param {Number} [multiplier] a zoom factor. 15454 * @param {Object} [cropping] Cropping informations 15455 * @param {Number} [cropping.left] Cropping left offset. 15456 * @param {Number} [cropping.top] Cropping top offset. 15457 * @param {Number} [cropping.width] Cropping width. 15458 * @param {Number} [cropping.height] Cropping height. 15459 */ 15460 toCanvasElement: function (multiplier, cropping) { 15461 multiplier = multiplier || 1; 15462 cropping = cropping || {}; 15463 var scaledWidth = (cropping.width || this.width) * multiplier, 15464 scaledHeight = (cropping.height || this.height) * multiplier, 15465 zoom = this.getZoom(), 15466 originalWidth = this.width, 15467 originalHeight = this.height, 15468 newZoom = zoom * multiplier, 15469 vp = this.viewportTransform, 15470 translateX = (vp[4] - (cropping.left || 0)) * multiplier, 15471 translateY = (vp[5] - (cropping.top || 0)) * multiplier, 15472 originalInteractive = this.interactive, 15473 newVp = [newZoom, 0, 0, newZoom, translateX, translateY], 15474 originalRetina = this.enableRetinaScaling, 15475 canvasEl = fabric.util.createCanvasElement(), 15476 originalContextTop = this.contextTop; 15477 canvasEl.width = scaledWidth; 15478 canvasEl.height = scaledHeight; 15479 this.contextTop = null; 15480 this.enableRetinaScaling = false; 15481 this.interactive = false; 15482 this.viewportTransform = newVp; 15483 this.width = scaledWidth; 15484 this.height = scaledHeight; 15485 this.calcViewportBoundaries(); 15486 this.renderCanvas(canvasEl.getContext("2d"), this._objects); 15487 this.viewportTransform = vp; 15488 this.width = originalWidth; 15489 this.height = originalHeight; 15490 this.calcViewportBoundaries(); 15491 this.interactive = originalInteractive; 15492 this.enableRetinaScaling = originalRetina; 15493 this.contextTop = originalContextTop; 15494 return canvasEl; 15495 }, 15496 }, 15497 ); 15498})(); 15499 15500fabric.util.object.extend( 15501 fabric.StaticCanvas.prototype, 15502 /** @lends fabric.StaticCanvas.prototype */ { 15503 /** 15504 * Populates canvas with data from the specified dataless JSON. 15505 * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} 15506 * @deprecated since 1.2.2 15507 * @param {String|Object} json JSON string or object 15508 * @param {Function} callback Callback, invoked when json is parsed 15509 * and corresponding objects (e.g: {@link fabric.Image}) 15510 * are initialized 15511 * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. 15512 * @return {fabric.Canvas} instance 15513 * @chainable 15514 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization} 15515 */ 15516 loadFromDatalessJSON: function (json, callback, reviver) { 15517 return this.loadFromJSON(json, callback, reviver); 15518 }, 15519 15520 /** 15521 * Populates canvas with data from the specified JSON. 15522 * JSON format must conform to the one of {@link fabric.Canvas#toJSON} 15523 * @param {String|Object} json JSON string or object 15524 * @param {Function} callback Callback, invoked when json is parsed 15525 * and corresponding objects (e.g: {@link fabric.Image}) 15526 * are initialized 15527 * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. 15528 * @return {fabric.Canvas} instance 15529 * @chainable 15530 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization} 15531 * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} 15532 * @example <caption>loadFromJSON</caption> 15533 * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); 15534 * @example <caption>loadFromJSON with reviver</caption> 15535 * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { 15536 * // `o` = json object 15537 * // `object` = fabric.Object instance 15538 * // ... do some stuff ... 15539 * }); 15540 */ 15541 loadFromJSON: function (json, callback, reviver) { 15542 if (!json) { 15543 return; 15544 } 15545 15546 // serialize if it wasn't already 15547 var serialized = 15548 typeof json === "string" 15549 ? JSON.parse(json) 15550 : fabric.util.object.clone(json); 15551 15552 var _this = this, 15553 clipPath = serialized.clipPath, 15554 renderOnAddRemove = this.renderOnAddRemove; 15555 15556 this.renderOnAddRemove = false;
15557 15558 delete serialized.clipPath; 15559 15560 this._enlivenObjects( 15561 serialized.objects, 15562 function (enlivenedObjects) { 15563 _this.clear(); 15564 _this._setBgOverlay(serialized, function () { 15565 if (clipPath) { 15566 _this._enlivenObjects([clipPath], function (enlivenedCanvasClip) { 15567 _this.clipPath = enlivenedCanvasClip[0]; 15568 _this.__setupCanvas.call( 15569 _this, 15570 serialized, 15571 enlivenedObjects, 15572 renderOnAddRemove, 15573 callback, 15574 ); 15575 }); 15576 } else { 15577 _this.__setupCanvas.call( 15578 _this, 15579 serialized, 15580 enlivenedObjects, 15581 renderOnAddRemove, 15582 callback, 15583 ); 15584 } 15585 }); 15586 }, 15587 reviver, 15588 ); 15589 return this; 15590 }, 15591 15592 /** 15593 * @private 15594 * @param {Object} serialized Object with background and overlay information 15595 * @param {Array} restored canvas objects 15596 * @param {Function} cached renderOnAddRemove callback 15597 * @param {Function} callback Invoked after all background and overlay images/patterns loaded 15598 */ 15599 __setupCanvas: function ( 15600 serialized, 15601 enlivenedObjects, 15602 renderOnAddRemove, 15603 callback, 15604 ) { 15605 var _this = this; 15606 enlivenedObjects.forEach(function (obj, index) { 15607 // we splice the array just in case some custom classes restored from JSON 15608 // will add more object to canvas at canvas init. 15609 _this.insertAt(obj, index); 15610 }); 15611 this.renderOnAddRemove = renderOnAddRemove; 15612 // remove parts i cannot set as options 15613 delete serialized.objects; 15614 delete serialized.backgroundImage; 15615 delete serialized.overlayImage; 15616 delete serialized.background; 15617 delete serialized.overlay; 15618 // this._initOptions does too many things to just 15619 // call it. Normally loading an Object from JSON 15620 // create the Object instance. Here the Canvas is 15621 // already an instance and we are just loading things over it 15622 this._setOptions(serialized); 15623 this.renderAll(); 15624 callback && callback(); 15625 }, 15626 15627 /** 15628 * @private 15629 * @param {Object} serialized Object with background and overlay information 15630 * @param {Function} callback Invoked after all background and overlay images/patterns loaded 15631 */ 15632 _setBgOverlay: function (serialized, callback) { 15633 var loaded = { 15634 backgroundColor: false, 15635 overlayColor: false, 15636 backgroundImage: false, 15637 overlayImage: false, 15638 }; 15639 15640 if ( 15641 !serialized.backgroundImage && 15642 !serialized.overlayImage && 15643 !serialized.background && 15644 !serialized.overlay 15645 ) { 15646 callback && callback(); 15647 return; 15648 } 15649 15650 var cbIfLoaded = function () { 15651 if ( 15652 loaded.backgroundImage && 15653 loaded.overlayImage && 15654 loaded.backgroundColor && 15655 loaded.overlayColor 15656 ) { 15657 callback && callback(); 15658 } 15659 }; 15660 15661 this.__setBgOverlay( 15662 "backgroundImage", 15663 serialized.backgroundImage, 15664 loaded, 15665 cbIfLoaded, 15666 ); 15667 this.__setBgOverlay( 15668 "overlayImage", 15669 serialized.overlayImage, 15670 loaded, 15671 cbIfLoaded, 15672 ); 15673 this.__setBgOverlay( 15674 "backgroundColor", 15675 serialized.background, 15676 loaded, 15677 cbIfLoaded, 15678 ); 15679 this.__setBgOverlay( 15680 "overlayColor", 15681 serialized.overlay, 15682 loaded, 15683 cbIfLoaded, 15684 ); 15685 }, 15686 15687 /** 15688 * @private 15689 * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) 15690 * @param {(Object|String)} value Value to set 15691 * @param {Object} loaded Set loaded property to true if property is set 15692 * @param {Object} callback Callback function to invoke after property is set 15693 */ 15694 __setBgOverlay: function (property, value, loaded, callback) { 15695 var _this = this; 15696 15697 if (!value) { 15698 loaded[property] = true; 15699 callback && callback(); 15700 return; 15701 } 15702 15703 if (property === "backgroundImage" || property === "overlayImage") { 15704 fabric.util.enlivenObjects([value], function (enlivedObject) { 15705 _this[property] = enlivedObject[0]; 15706 loaded[property] = true; 15707 callback && callback(); 15708 }); 15709 } else { 15710 this["set" + fabric.util.string.capitalize(property, true)]( 15711 value, 15712 function () { 15713 loaded[property] = true; 15714 callback && callback(); 15715 }, 15716 ); 15717 } 15718 }, 15719 15720 /** 15721 * @private 15722 * @param {Array} objects 15723 * @param {Function} callback 15724 * @param {Function} [reviver] 15725 */ 15726 _enlivenObjects: function (objects, callback, reviver) { 15727 if (!objects || objects.length === 0) { 15728 callback && callback([]); 15729 return; 15730 } 15731 15732 fabric.util.enlivenObjects( 15733 objects, 15734 function (enlivenedObjects) { 15735 callback && callback(enlivenedObjects); 15736 }, 15737 null, 15738 reviver, 15739 ); 15740 }, 15741 15742 /** 15743 * @private 15744 * @param {String} format 15745 * @param {Function} callback 15746 */ 15747 _toDataURL: function (format, callback) { 15748 this.clone(function (clone) { 15749 callback(clone.toDataURL(format)); 15750 }); 15751 }, 15752 15753 /** 15754 * @private 15755 * @param {String} format 15756 * @param {Number} multiplier 15757 * @param {Function} callback 15758 */ 15759 _toDataURLWithMultiplier: function (format, multiplier, callback) { 15760 this.clone(function (clone) { 15761 callback(clone.toDataURLWithMultiplier(format, multiplier)); 15762 }); 15763 }, 15764 15765 /** 15766 * Clones canvas instance 15767 * @param {Object} [callback] Receives cloned instance as a first argument 15768 * @param {Array} [properties] Array of properties to include in the cloned canvas and children 15769 */ 15770 clone: function (callback, properties) { 15771 var data = JSON.stringify(this.toJSON(properties)); 15772 this.cloneWithoutData(function (clone) { 15773 clone.loadFromJSON(data, function () { 15774 callback && callback(clone); 15775 }); 15776 }); 15777 }, 15778 15779 /** 15780 * Clones canvas instance without cloning existing data. 15781 * This essentially copies canvas dimensions, clipping properties, etc. 15782 * but leaves data empty (so that you can populate it with your own) 15783 * @param {Object} [callback] Receives cloned instance as a first argument 15784 */ 15785 cloneWithoutData: function (callback) { 15786 var el = fabric.util.createCanvasElement(); 15787 15788 el.width = this.width; 15789 el.height = this.height; 15790 15791 var clone = new fabric.Canvas(el); 15792 if (this.backgroundImage) {
15793 clone.setBackgroundImage(this.backgroundImage.src, function () { 15794 clone.renderAll(); 15795 callback && callback(clone); 15796 }); 15797 clone.backgroundImageOpacity = this.backgroundImageOpacity; 15798 clone.backgroundImageStretch = this.backgroundImageStretch; 15799 } else { 15800 callback && callback(clone); 15801 } 15802 }, 15803 }, 15804); 15805 15806(function (global) { 15807 "use strict"; 15808 15809 var fabric = global.fabric || (global.fabric = {}), 15810 extend = fabric.util.object.extend, 15811 clone = fabric.util.object.clone, 15812 toFixed = fabric.util.toFixed, 15813 capitalize = fabric.util.string.capitalize, 15814 degreesToRadians = fabric.util.degreesToRadians, 15815 objectCaching = !fabric.isLikelyNode, 15816 ALIASING_LIMIT = 2; 15817 15818 if (fabric.Object) { 15819 return; 15820 } 15821 15822 /** 15823 * Root object class from which all 2d shape classes inherit from 15824 * @class fabric.Object 15825 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#objects} 15826 * @see {@link fabric.Object#initialize} for constructor definition 15827 * 15828 * @fires added 15829 * @fires removed 15830 * 15831 * @fires selected 15832 * @fires deselected 15833 * @fires modified 15834 * @fires modified 15835 * @fires moved 15836 * @fires scaled 15837 * @fires rotated 15838 * @fires skewed 15839 * 15840 * @fires rotating 15841 * @fires scaling 15842 * @fires moving 15843 * @fires skewing 15844 * 15845 * @fires mousedown 15846 * @fires mouseup 15847 * @fires mouseover 15848 * @fires mouseout 15849 * @fires mousewheel 15850 * @fires mousedblclick 15851 * 15852 * @fires dragover 15853 * @fires dragenter 15854 * @fires dragleave 15855 * @fires drop 15856 */ 15857 fabric.Object = fabric.util.createClass( 15858 fabric.CommonMethods, 15859 /** @lends fabric.Object.prototype */ { 15860 /** 15861 * Type of an object (rect, circle, path, etc.). 15862 * Note that this property is meant to be read-only and not meant to be modified. 15863 * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly.
15864 * @type String 15865 * @default 15866 */ 15867 type: "object", 15868 15869 /** 15870 * Horizontal origin of transformation of an object (one of "left", "right", "center") 15871 * See http://jsfiddle.net/1ow02gea/244/ on how originX/originY affect objects in groups 15872 * @type String 15873 * @default 15874 */ 15875 originX: "left", 15876 15877 /** 15878 * Vertical origin of transformation of an object (one of "top", "bottom", "center") 15879 * See http://jsfiddle.net/1ow02gea/244/ on how originX/originY affect objects in groups 15880 * @type String 15881 * @default 15882 */ 15883 originY: "top", 15884 15885 /** 15886 * Top position of an object. Note that by default it's relative to object top. You can change this by setting originY={top/center/bottom} 15887 * @type Number 15888 * @default 15889 */ 15890 top: 0, 15891 15892 /** 15893 * Left position of an object. Note that by default it's relative to object left. You can change this by setting originX={left/center/right} 15894 * @type Number 15895 * @default 15896 */ 15897 left: 0, 15898 15899 /** 15900 * Object width 15901 * @type Number 15902 * @default 15903 */ 15904 width: 0, 15905 15906 /** 15907 * Object height 15908 * @type Number 15909 * @default 15910 */ 15911 height: 0, 15912 15913 /** 15914 * Object scale factor (horizontal) 15915 * @type Number 15916 * @default 15917 */ 15918 scaleX: 1, 15919 15920 /** 15921 * Object scale factor (vertical) 15922 * @type Number 15923 * @default 15924 */ 15925 scaleY: 1, 15926 15927 /** 15928 * When true, an object is rendered as flipped horizontally 15929 * @type Boolean 15930 * @default 15931 */ 15932 flipX: false, 15933 15934 /** 15935 * When true, an object is rendered as flipped vertically 15936 * @type Boolean 15937 * @default 15938 */ 15939 flipY: false, 15940 15941 /** 15942 * Opacity of an object 15943 * @type Number 15944 * @default 15945 */ 15946 opacity: 1, 15947 15948 /** 15949 * Angle of rotation of an object (in degrees) 15950 * @type Number 15951 * @default 15952 */ 15953 angle: 0, 15954 15955 /** 15956 * Angle of skew on x axes of an object (in degrees) 15957 * @type Number 15958 * @default 15959 */ 15960 skewX: 0, 15961 15962 /** 15963 * Angle of skew on y axes of an object (in degrees) 15964 * @type Number 15965 * @default 15966 */ 15967 skewY: 0, 15968 15969 /** 15970 * Size of object's controlling corners (in pixels) 15971 * @type Number 15972 * @default 15973 */ 15974 cornerSize: 13, 15975 15976 /** 15977 * Size of object's controlling corners when touch interaction is detected 15978 * @type Number 15979 * @default 15980 */ 15981 touchCornerSize: 24, 15982 15983 /** 15984 * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) 15985 * @type Boolean 15986 * @default 15987 */ 15988 transparentCorners: true, 15989 15990 /** 15991 * Default cursor value used when hovering over this object on canvas
15992 * @type String 15993 * @default 15994 */ 15995 hoverCursor: null, 15996 15997 /** 15998 * Default cursor value used when moving this object on canvas 15999 * @type String 16000 * @default 16001 */ 16002 moveCursor: null, 16003 16004 /** 16005 * Padding between object and its controlling borders (in pixels) 16006 * @type Number 16007 * @default 16008 */ 16009 padding: 0, 16010 16011 /** 16012 * Color of controlling borders of an object (when it's active) 16013 * @type String 16014 * @default 16015 */ 16016 borderColor: "rgb(178,204,255)", 16017 16018 /** 16019 * Array specifying dash pattern of an object's borders (hasBorder must be true) 16020 * @since 1.6.2 16021 * @type Array 16022 */ 16023 borderDashArray: null, 16024 16025 /** 16026 * Color of controlling corners of an object (when it's active) 16027 * @type String 16028 * @default 16029 */ 16030 cornerColor: "rgb(178,204,255)", 16031 16032 /** 16033 * Color of controlling corners of an object (when it's active and transparentCorners false) 16034 * @since 1.6.2
16035 * @type String 16036 * @default 16037 */ 16038 cornerStrokeColor: null, 16039 16040 /** 16041 * Specify style of control, 'rect' or 'circle' 16042 * @since 1.6.2 16043 * @type String 16044 */ 16045 cornerStyle: "rect", 16046 16047 /** 16048 * Array specifying dash pattern of an object's control (hasBorder must be true) 16049 * @since 1.6.2 16050 * @type Array 16051 */ 16052 cornerDashArray: null, 16053 16054 /** 16055 * When true, this object will use center point as the origin of transformation 16056 * when being scaled via the controls. 16057 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 16058 * @since 1.3.4 16059 * @type Boolean 16060 * @default 16061 */ 16062 centeredScaling: false, 16063 16064 /** 16065 * When true, this object will use center point as the origin of transformation 16066 * when being rotated via the controls. 16067 * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean). 16068 * @since 1.3.4 16069 * @type Boolean 16070 * @default 16071 */ 16072 centeredRotation: true, 16073 16074 /** 16075 * Color of object's fill 16076 * takes css colors https://www.w3.org/TR/css-color-3/
16077 * @type String 16078 * @default 16079 */ 16080 fill: "rgb(0,0,0)", 16081 16082 /** 16083 * Fill rule used to fill an object 16084 * accepted values are nonzero, evenodd 16085 * <b>Backwards incompatibility note:</b> This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead) 16086 * @type String 16087 * @default 16088 */ 16089 fillRule: "nonzero", 16090 16091 /** 16092 * Composite rule used for canvas globalCompositeOperation 16093 * @type String 16094 * @default 16095 */ 16096 globalCompositeOperation: "source-over", 16097 16098 /** 16099 * Background color of an object. 16100 * takes css colors https://www.w3.org/TR/css-color-3/ 16101 * @type String 16102 * @default 16103 */ 16104 backgroundColor: "", 16105 16106 /** 16107 * Selection Background color of an object. colored layer behind the object when it is active. 16108 * does not mix good with globalCompositeOperation methods. 16109 * @type String 16110 * @default 16111 */ 16112 selectionBackgroundColor: "", 16113 16114 /** 16115 * When defined, an object is rendered via stroke and this property specifies its color 16116 * takes css colors https://www.w3.org/TR/css-color-3/
16117 * @type String 16118 * @default 16119 */ 16120 stroke: null, 16121 16122 /** 16123 * Width of a stroke used to render this object 16124 * @type Number 16125 * @default 16126 */ 16127 strokeWidth: 1, 16128 16129 /** 16130 * Array specifying dash pattern of an object's stroke (stroke must be defined) 16131 * @type Array 16132 */ 16133 strokeDashArray: null, 16134 16135 /** 16136 * Line offset of an object's stroke 16137 * @type Number 16138 * @default 16139 */ 16140 strokeDashOffset: 0, 16141 16142 /** 16143 * Line endings style of an object's stroke (one of "butt", "round", "square") 16144 * @type String 16145 * @default 16146 */ 16147 strokeLineCap: "butt", 16148 16149 /** 16150 * Corner style of an object's stroke (one of "bevel", "round", "miter") 16151 * @type String 16152 * @default 16153 */ 16154 strokeLineJoin: "miter", 16155 16156 /** 16157 * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke 16158 * @type Number 16159 * @default 16160 */ 16161 strokeMiterLimit: 4, 16162 16163 /** 16164 * Shadow object representing shadow of this shape 16165 * @type fabric.Shadow 16166 * @default 16167 */ 16168 shadow: null, 16169 16170 /** 16171 * Opacity of object's controlling borders when object is active and moving 16172 * @type Number 16173 * @default 16174 */ 16175 borderOpacityWhenMoving: 0.4, 16176 16177 /** 16178 * Scale factor of object's controlling borders 16179 * bigger number will make a thicker border 16180 * border is 1, so this is basically a border thickness 16181 * since there is no way to change the border itself. 16182 * @type Number 16183 * @default 16184 */ 16185 borderScaleFactor: 1, 16186 16187 /** 16188 * Minimum allowed scale value of an object 16189 * @type Number 16190 * @default 16191 */ 16192 minScaleLimit: 0, 16193 16194 /** 16195 * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). 16196 * But events still fire on it. 16197 * @type Boolean 16198 * @default 16199 */ 16200 selectable: true, 16201 16202 /** 16203 * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 16204 * @type Boolean 16205 * @default 16206 */ 16207 evented: true, 16208 16209 /** 16210 * When set to `false`, an object is not rendered on canvas 16211 * @type Boolean 16212 * @default 16213 */ 16214 visible: true, 16215 16216 /** 16217 * When set to `false`, object's controls are not displayed and can not be used to manipulate object 16218 * @type Boolean 16219 * @default 16220 */ 16221 hasControls: true, 16222 16223 /** 16224 * When set to `false`, object's controlling borders are not rendered 16225 * @type Boolean 16226 * @default 16227 */ 16228 hasBorders: true, 16229 16230 /** 16231 * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box 16232 * @type Boolean 16233 * @default 16234 */ 16235 perPixelTargetFind: false, 16236 16237 /** 16238 * When `false`, default object's values are not included in its serialization 16239 * @type Boolean 16240 * @default 16241 */ 16242 includeDefaultValues: true, 16243 16244 /** 16245 * When `true`, object horizontal movement is locked 16246 * @type Boolean 16247 * @default 16248 */ 16249 lockMovementX: false, 16250 16251 /** 16252 * When `true`, object vertical movement is locked 16253 * @type Boolean 16254 * @default 16255 */ 16256 lockMovementY: false, 16257 16258 /** 16259 * When `true`, object rotation is locked 16260 * @type Boolean 16261 * @default 16262 */ 16263 lockRotation: false, 16264 16265 /** 16266 * When `true`, object horizontal scaling is locked 16267 * @type Boolean 16268 * @default 16269 */ 16270 lockScalingX: false, 16271 16272 /** 16273 * When `true`, object vertical scaling is locked 16274 * @type Boolean 16275 * @default 16276 */ 16277 lockScalingY: false, 16278 16279 /** 16280 * When `true`, object horizontal skewing is locked 16281 * @type Boolean 16282 * @default 16283 */ 16284 lockSkewingX: false, 16285 16286 /** 16287 * When `true`, object vertical skewing is locked 16288 * @type Boolean 16289 * @default 16290 */ 16291 lockSkewingY: false, 16292 16293 /** 16294 * When `true`, object cannot be flipped by scaling into negative values 16295 * @type Boolean 16296 * @default 16297 */ 16298 lockScalingFlip: false, 16299 16300 /** 16301 * When `true`, object is not exported in OBJECT/JSON 16302 * @since 1.6.3 16303 * @type Boolean 16304 * @default 16305 */ 16306 excludeFromExport: false, 16307 16308 /** 16309 * When `true`, object is cached on an additional canvas. 16310 * When `false`, object is not cached unless necessary ( clipPath ) 16311 * default to true 16312 * @since 1.7.0 16313 * @type Boolean 16314 * @default true 16315 */ 16316 objectCaching: objectCaching, 16317 16318 /** 16319 * When `true`, object properties are checked for cache invalidation. In some particular 16320 * situation you may want this to be disabled ( spray brush, very big, groups) 16321 * or if your application does not allow you to modify properties for groups child you want 16322 * to disable it for groups. 16323 * default to false 16324 * since 1.7.0 16325 * @type Boolean 16326 * @default false 16327 */ 16328 statefullCache: false, 16329 16330 /** 16331 * When `true`, cache does not get updated during scaling. The picture will get blocky if scaled 16332 * too much and will be redrawn with correct details at the end of scaling. 16333 * this setting is performance and application dependant. 16334 * default to true 16335 * since 1.7.0 16336 * @type Boolean 16337 * @default true 16338 */ 16339 noScaleCache: true, 16340 16341 /** 16342 * When `false`, the stoke width will scale with the object. 16343 * When `true`, the stroke will always match the exact pixel size entered for stroke width. 16344 * this Property does not work on Text classes or drawing call that uses strokeText,fillText methods 16345 * default to false 16346 * @since 2.6.0 16347 * @type Boolean 16348 * @default false 16349 * @type Boolean 16350 * @default false 16351 */ 16352 strokeUniform: false, 16353 16354 /** 16355 * When set to `true`, object's cache will be rerendered next render call. 16356 * since 1.7.0 16357 * @type Boolean 16358 * @default true 16359 */ 16360 dirty: true, 16361 16362 /** 16363 * keeps the value of the last hovered corner during mouse move. 16364 * 0 is no corner, or 'mt', 'ml', 'mtr' etc.. 16365 * It should be private, but there is no harm in using it as 16366 * a read-only property. 16367 * @type number|string|any 16368 * @default 0 16369 */ 16370 __corner: 0, 16371 16372 /** 16373 * Determines if the fill or the stroke is drawn first (one of "fill" or "stroke")
16374 * @type String 16375 * @default 16376 */ 16377 paintFirst: "fill", 16378 16379 /** 16380 * When 'down', object is set to active on mousedown/touchstart 16381 * When 'up', object is set to active on mouseup/touchend 16382 * Experimental. Let's see if this breaks anything before supporting officially 16383 * @private 16384 * since 4.4.0 16385 * @type String 16386 * @default 'down' 16387 */ 16388 activeOn: "down", 16389 16390 /** Custom property attribute added : Suraj 16391 */ 16392 newHeight: 0, 16393 newWidth: 0, 16394 objID: 0, 16395 objType: "", 16396 uploadBaseType: "", 16397 pastOpacity: 0, 16398 displayFontSize: 0, 16399 baseFonts: "", 16400 isDesignerUpload: 0, 16401 isManuallyGroupped: 0, 16402 patternURL: "", 16403 objPadding: 0, 16404 stepRect: {}, 16405 hasDesignerLock: 0, 16406 textCharLimit: 0, 16407 isNewCrop: false, 16408 custtype: "", 16409 picAreaScale: undefined, 16410 oldOriginX: "center", 16411 oldOriginY: "center", 16412 oldLeft: undefined, 16413 oldTop: undefined, 16414 oldScaleX: undefined, 16415 oldScaleY: undefined, 16416 oldWidth: undefined, 16417 oldHeight: undefined, 16418 selectedLanguage: "en", 16419 originalText: undefined, 16420 hasUserLock: 0, 16421 isTextHighlight: undefined, 16422 isOutSideObject: false, 16423 origingSVG: "", 16424 csvFileAssetTag: "", 16425 textMaxFont: 0, 16426 textMinFont: 0, 16427 isColorChange: true, 16428 imgDPCount: 0, 16429 imageQuality: "", 16430 isLowResolution: 0, 16431 okWithLowResolution: 0, 16432 svgColours: {}, 16433 originalSVGFill: "", 16434 foundBase64PNG: false, 16435 isSVGColorChange: false, 16436 neroImprovedImage: "", 16437 imageMetaData: null, 16438 qrCodeText: "", 16439 codeImage: "", 16440 wordArt: "", 16441 isAssetHeader: false, 16442 customiseAssetHeader: "", 16443 isFromBulkUpload: false, 16444 fontStyleType: 1, 16445 16446 /** 16447 * List of properties to consider when checking if state 16448 * of an object is changed (fabric.Object#hasStateChanged) 16449 * as well as for history (undo/redo) purposes 16450 * @type Array 16451 */ 16452 stateProperties: ( 16453 "top left width height scaleX scaleY flipX flipY originX originY transformMatrix " + 16454 "stroke strokeWidth strokeDashArray strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit " + 16455 "angle opacity fill globalCompositeOperation shadow visible backgroundColor " + 16456 "skewX skewY fillRule paintFirst clipPath strokeUniform " + 16457 "newHeight newWidth objID objType uploadBaseType pastOpacity displayFontSize baseFonts isDesignerUpload isManuallyGroupped patternURL objPadding stepRect hasDesignerLock textCharLimit isNewCrop custtype picAreaScale oldOriginX oldOriginY oldLeft oldTop oldScaleX oldScaleY oldWidth oldHeight selectedLanguage originalText hasUserLock isTextHighlight isOutSideObject origingSVG " + 16458 "csvFileAssetTag textMaxFont textMinFont isColorChange imgDPCount imageQuality isLowResolution okWithLowResolution svgColours originalSVGFill foundBase64PNG isSVGColorChange neroImprovedImage imageMetaData qrCodeText wordArt codeImage isAssetHeader customiseAssetHeader isFromBulkUpload fontStyleType" 16459 ).split(" "), 16460 16461 /** 16462 * List of properties to consider when checking if cache needs refresh 16463 * Those properties are checked by statefullCache ON ( or lazy mode if we want ) or from single 16464 * calls to Object.set(key, value). If the key is in this list, the object is marked as dirty 16465 * and refreshed at the next render 16466 * @type Array 16467 */ 16468 cacheProperties: ( 16469 "fill stroke strokeWidth strokeDashArray width height paintFirst strokeUniform" + 16470 " strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit backgroundColor clipPath" + 16471 " newHeight newWidth objID objType uploadBaseType pastOpacity displayFontSize baseFonts isDesignerUpload isManuallyGroupped patternURL objPadding stepRect hasDesignerLock textCharLimit isNewCrop custtype picAreaScale oldOriginX oldOriginY oldLeft oldTop oldScaleX oldScaleY oldWidth oldHeight selectedLanguage originalText hasUserLock isTextHighlight isOutSideObject origingSVG " + 16472 " csvFileAssetTag textMaxFont textMinFont isColorChange imgDPCount imageQuality isLowResolution okWithLowResolution svgColours originalSVGFill foundBase64PNG isSVGColorChange neroImprovedImage imageMetaData qrCodeText wordArt codeImage isAssetHeader customiseAssetHeader isFromBulkUpload fontStyleType" 16473 ).split(" "), 16474 16475 /** 16476 * List of properties to consider for animating colors. 16477 * @type Array 16478 */ 16479 colorProperties: "fill stroke backgroundColor".split(" "), 16480 16481 /** 16482 * a fabricObject that, without stroke define a clipping area with their shape. filled
16482in black 16483 * the clipPath object gets used when the object has rendered, and the context is placed in the center 16484 * of the object cacheCanvas. 16485 * If you want 0,0 of a clipPath to align with an object center, use clipPath.originX/Y to 'center' 16486 * @type fabric.Object 16487 */ 16488 clipPath: undefined, 16489 16490 /** 16491 * Meaningful ONLY when the object is used as clipPath. 16492 * if true, the clipPath will make the object clip to the outside of the clipPath 16493 * since 2.4.0 16494 * @type boolean 16495 * @default false 16496 */ 16497 inverted: false, 16498 16499 /** 16500 * Meaningful ONLY when the object is used as clipPath. 16501 * if true, the clipPath will have its top and left relative to canvas, and will 16502 * not be influenced by the object transform. This will make the clipPath relative 16503 * to the canvas, but clipping just a particular object. 16504 * WARNING this is beta, this feature may change or be renamed. 16505 * since 2.4.0 16506 * @type boolean 16507 * @default false 16508 */ 16509 absolutePositioned: false, 16510 16511 /** 16512 * Constructor 16513 * @param {Object} [options] Options object 16514 */ 16515 initialize: function (options) { 16516 if (options) { 16517 this.setOptions(options); 16518 } 16519 }, 16520 16521 /** 16522 * Create a the canvas used to keep the cached copy of the object 16523 * @private 16524 */ 16525 _createCacheCanvas: function () { 16526 this._cacheProperties = {}; 16527 this._cacheCanvas = fabric.util.createCanvasElement(); 16528 this._cacheContext = this._cacheCanvas.getContext("2d"); 16529 this._updateCacheCanvas(); 16530 // if canvas gets created, is empty, so dirty. 16531 this.dirty = true; 16532 }, 16533 16534 /** 16535 * Limit the cache dimensions so that X * Y do not cross fabric.perfLimitSizeTotal 16536 * and each side do not cross fabric.cacheSideLimit 16537 * those numbers are configurable so that you can get as much detail as you want 16538 * making bargain with performances. 16539 * @param {Object} dims 16540 * @param {Object} dims.width width of canvas 16541 * @param {Object} dims.height height of canvas 16542 * @param {Object} dims.zoomX zoomX zoom value to unscale the canvas before drawing cache 16543 * @param {Object} dims.zoomY zoomY zoom value to unscale the canvas before drawing cache 16544 * @return {Object}.width width of canvas 16545 * @return {Object}.height height of canvas 16546 * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache 16547 * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache 16548 */ 16549 _limitCacheSize: function (dims) { 16550 var perfLimitSizeTotal = fabric.perfLimitSizeTotal, 16551 width = dims.width, 16552 height = dims.height, 16553 max = fabric.maxCacheSideLimit, 16554 min = fabric.minCacheSideLimit; 16555 if ( 16556 width <= max && 16557 height <= max && 16558 width * height <= perfLimitSizeTotal 16559 ) { 16560 if (width < min) { 16561 dims.width = min; 16562 } 16563 if (height < min) { 16564 dims.height = min; 16565 } 16566 return dims; 16567 } 16568 var ar = width / height, 16569 limitedDims = fabric.util.limitDimsByArea(ar, perfLimitSizeTotal), 16570 capValue = fabric.util.capValue, 16571 x = capValue(min, limitedDims.x, max), 16572 y = capValue(min, limitedDims.y, max); 16573 if (width > x) { 16574 dims.zoomX /= width / x; 16575 dims.width = x; 16576 dims.capped = true; 16577 } 16578 if (height > y) { 16579 dims.zoomY /= height / y; 16580 dims.height = y; 16581 dims.capped = true; 16582 } 16583 return dims; 16584 }, 16585 16586 /** 16587 * Return the dimension and the zoom level needed to create a cache canvas 16588 * big enough to host the object to be cached. 16589 * @private 16590 * @return {Object}.x width of object to be cached 16591 * @return {Object}.y height of object to be cached 16592 * @return {Object}.width width of canvas 16593 * @return {Object}.height height of canvas 16594 * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache 16595 * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache 16596 */ 16597 _getCacheCanvasDimensions: function () { 16598 var objectScale = this.getTotalObjectScaling(), 16599 // caculate dimensions without skewing 16600 dim = this._getTransformedDimensions(0, 0), 16601 neededX = (dim.x * objectScale.scaleX) / this.scaleX, 16602 neededY = (dim.y * objectScale.scaleY) / this.scaleY; 16603 return { 16604 // for sure this ALIASING_LIMIT is slightly creating problem 16605 // in situation in which the cache canvas gets an upper limit 16606 // also objectScale contains already scaleX and scaleY 16607 width: neededX + ALIASING_LIMIT, 16608 height: neededY + ALIASING_LIMIT, 16609 zoomX: objectScale.scaleX, 16610 zoomY: objectScale.scaleY, 16611 x: neededX, 16612 y: neededY, 16613 }; 16614 }, 16615 16616 /** 16617 * Update width and height of the canvas for cache 16618 * returns true or false if canvas needed resize. 16619 * @private 16620 * @return {Boolean} true if the canvas has been resized 16621 */ 16622 _updateCacheCanvas: function () { 16623 var targetCanvas = this.canvas; 16624 if ( 16625 this.noScaleCache && 16626 targetCanvas && 16627 targetCanvas._currentTransform 16628 ) { 16629 var target = targetCanvas._currentTransform.target, 16630 action = targetCanvas._currentTransform.action; 16631 if ( 16632 this === target && 16633 action.slice && 16634 action.slice(0, 5) === "scale" 16635 ) { 16636 return false;
vendor: 8,107 bytes, lines 16637-16832
16637 } 16638 } 16639 var canvas = this._cacheCanvas, 16640 dims = this._limitCacheSize(this._getCacheCanvasDimensions()), 16641 minCacheSize = fabric.minCacheSideLimit, 16642 width = dims.width, 16643 height = dims.height, 16644 drawingWidth, 16645 drawingHeight, 16646 zoomX = dims.zoomX, 16647 zoomY = dims.zoomY, 16648 dimensionsChanged = 16649 width !== this.cacheWidth || height !== this.cacheHeight, 16650 zoomChanged = this.zoomX !== zoomX || this.zoomY !== zoomY, 16651 shouldRedraw = dimensionsChanged || zoomChanged, 16652 additionalWidth = 0, 16653 additionalHeight = 0, 16654 shouldResizeCanvas = false; 16655 if (dimensionsChanged) { 16656 var canvasWidth = this._cacheCanvas.width, 16657 canvasHeight = this._cacheCanvas.height, 16658 sizeGrowing = width > canvasWidth || height > canvasHeight, 16659 sizeShrinking = 16660 (width < canvasWidth * 0.9 || height < canvasHeight * 0.9) && 16661 canvasWidth > minCacheSize && 16662 canvasHeight > minCacheSize; 16663 shouldResizeCanvas = sizeGrowing || sizeShrinking; 16664 if ( 16665 sizeGrowing && 16666 !dims.capped && 16667 (width > minCacheSize || height > minCacheSize) 16668 ) { 16669 additionalWidth = width * 0.1; 16670 additionalHeight = height * 0.1; 16671 } 16672 } 16673 if (this instanceof fabric.Text && this.path) { 16674 shouldRedraw = true; 16675 shouldResizeCanvas = true; 16676 additionalWidth += this.getHeightOfLine(0) * this.zoomX; 16677 additionalHeight += this.getHeightOfLine(0) * this.zoomY; 16678 } 16679 if (shouldRedraw) { 16680 if (shouldResizeCanvas) { 16681 canvas.width = Math.ceil(width + additionalWidth); 16682 canvas.height = Math.ceil(height + additionalHeight); 16683 } else { 16684 this._cacheContext.setTransform(1, 0, 0, 1, 0, 0); 16685 this._cacheContext.clearRect(0, 0, canvas.width, canvas.height); 16686 } 16687 drawingWidth = dims.x / 2; 16688 drawingHeight = dims.y / 2; 16689 this.cacheTranslationX = 16690 Math.round(canvas.width / 2 - drawingWidth) + drawingWidth; 16691 this.cacheTranslationY = 16692 Math.round(canvas.height / 2 - drawingHeight) + drawingHeight; 16693 this.cacheWidth = width; 16694 this.cacheHeight = height; 16695 this._cacheContext.translate( 16696 this.cacheTranslationX, 16697 this.cacheTranslationY, 16698 ); 16699 this._cacheContext.scale(zoomX, zoomY); 16700 this.zoomX = zoomX; 16701 this.zoomY = zoomY; 16702 return true; 16703 } 16704 return false; 16705 }, 16706 16707 /** 16708 * Sets object's properties from options 16709 * @param {Object} [options] Options object 16710 */ 16711 setOptions: function (options) { 16712 this._setOptions(options); 16713 this._initGradient(options.fill, "fill"); 16714 this._initGradient(options.stroke, "stroke"); 16715 this._initPattern(options.fill, "fill"); 16716 this._initPattern(options.stroke, "stroke"); 16717 }, 16718 16719 /** 16720 * Transforms context when rendering an object 16721 * @param {CanvasRenderingContext2D} ctx Context 16722 */ 16723 transform: function (ctx) { 16724 var needFullTransform = 16725 (this.group && !this.group._transformDone) || 16726 (this.group && this.canvas && ctx === this.canvas.contextTop); 16727 var m = this.calcTransformMatrix(!needFullTransform); 16728 ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); 16729 }, 16730 16731 /** 16732 * Returns an object representation of an instance 16733 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 16734 * @return {Object} Object representation of an instance 16735 */ 16736 toObject: function (propertiesToInclude) { 16737 var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, 16738 object = { 16739 type: this.type, 16740 version: fabric.version, 16741 originX: this.originX, 16742 originY: this.originY, 16743 left: toFixed(this.left, NUM_FRACTION_DIGITS), 16744 top: toFixed(this.top, NUM_FRACTION_DIGITS), 16745 width: toFixed(this.width, NUM_FRACTION_DIGITS), 16746 height: toFixed(this.height, NUM_FRACTION_DIGITS), 16747 fill: 16748 this.fill && this.fill.toObject 16749 ? this.fill.toObject() 16750 : this.fill, 16751 stroke: 16752 this.stroke && this.stroke.toObject 16753 ? this.stroke.toObject() 16754 : this.stroke, 16755 strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), 16756 strokeDashArray: this.strokeDashArray 16757 ? this.strokeDashArray.concat() 16758 : this.strokeDashArray, 16759 strokeLineCap: this.strokeLineCap, 16760 strokeDashOffset: this.strokeDashOffset, 16761 strokeLineJoin: this.strokeLineJoin, 16762 strokeUniform: this.strokeUniform, 16763 strokeMiterLimit: toFixed( 16764 this.strokeMiterLimit, 16765 NUM_FRACTION_DIGITS, 16766 ), 16767 scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), 16768 scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), 16769 angle: toFixed(this.angle, NUM_FRACTION_DIGITS), 16770 flipX: this.flipX, 16771 flipY: this.flipY, 16772 opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), 16773 shadow: 16774 this.shadow && this.shadow.toObject 16775 ? this.shadow.toObject() 16776 : this.shadow, 16777 visible: this.visible, 16778 backgroundColor: this.backgroundColor, 16779 fillRule: this.fillRule, 16780 paintFirst: this.paintFirst, 16781 globalCompositeOperation: this.globalCompositeOperation, 16782 skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS), 16783 skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS), 16784 // Custom property : Suraj 16785 newHeight: this.newHeight, 16786 newWidth: this.newWidth, 16787 objID: this.objID, 16788 objType: this.objType, 16789 uploadBaseType: this.uploadBaseType, 16790 pastOpacity: this.pastOpacity, 16791 displayFontSize: this.displayFontSize, 16792 baseFonts: this.baseFonts, 16793 isDesignerUpload: this.isDesignerUpload, 16794 isManuallyGroupped: this.isManuallyGroupped, 16795 patternURL: this.patternURL, 16796 objPadding: this.objPadding, 16797 stepRect: this.stepRect, 16798 hasDesignerLock: this.hasDesignerLock, 16799 textCharLimit: this.textCharLimit, 16800 isNewCrop: this.isNewCrop, 16801 custtype: this.custtype, 16802 picAreaScale: this.picAreaScale, 16803 oldOriginX: this.oldOriginX, 16804 oldOriginY: this.oldOriginY, 16805 oldLeft: this.oldLeft, 16806 oldTop: this.oldTop, 16807 oldScaleX: this.oldScaleX, 16808 oldScaleY: this.oldScaleY, 16809 oldWidth: this.oldWidth, 16810 oldHeight: this.oldHeight, 16811 selectedLanguage: this.selectedLanguage, 16812 originalText: this.originalText, 16813 hasUserLock: this.hasUserLock, 16814 isTextHighlight: this.isTextHighlight, 16815 isOutSideObject: this.isOutSideObject, 16816 origingSVG: this.origingSVG, 16817 imgDPCount: this.imgDPCount, 16818 csvFileAssetTag: this.csvFileAssetTag, 16819 textMaxFont: this.textMaxFont, 16820 textMinFont: this.textMinFont, 16821 isColorChange: this.isColorChange, 16822 imgDPCount: this.imgDPCount, 16823 imageQuality: this.imageQuality, 16824 isLowResolution: this.isLowResolution, 16825 okWithLowResolution: this.okWithLowResolution, 16826 svgColours: this.svgColours, 16827 originalSVGFill: this.originalSVGFill, 16828 foundBase64PNG: this.foundBase64PNG, 16829 isSVGColorChange: this.isSVGColorChange, 16830 neroImprovedImage: this.neroImprovedImage, 16831 imageMetaData: this.imageMetaData, 16832 qrCodeText: this.qrCodeText,
16833 codeImage: this.codeImage, 16834 isAssetHeader: this.isAssetHeader, 16835 customiseAssetHeader: this.customiseAssetHeader, 16836 wordArt: this.wordArt, 16837 isFromBulkUpload: this.isFromBulkUpload, 16838 fontStyleType: this.fontStyleType 16839 }; 16840 16841 if (this.clipPath && !this.clipPath.excludeFromExport) { 16842 object.clipPath = this.clipPath.toObject(propertiesToInclude); 16843 object.clipPath.inverted = this.clipPath.inverted; 16844 object.clipPath.absolutePositioned = this.clipPath.absolutePositioned; 16845 } 16846 16847 fabric.util.populateWithProperties(this, object, propertiesToInclude); 16848 if (!this.includeDefaultValues) { 16849 object = this._removeDefaultValues(object); 16850 } 16851 16852 return object; 16853 }, 16854 16855 /** 16856 * Returns (dataless) object representation of an instance 16857 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 16858 * @return {Object} Object representation of an instance 16859 */ 16860 toDatalessObject: function (propertiesToInclude) { 16861 // will be overwritten by subclasses 16862 return this.toObject(propertiesToInclude); 16863 }, 16864 16865 /** 16866 * @private 16867 * @param {Object} object 16868 */ 16869 _removeDefaultValues: function (object) { 16870 var prototype = fabric.util.getKlass(object.type).prototype, 16871 stateProperties = prototype.stateProperties; 16872 stateProperties.forEach(function (prop) { 16873 if (prop === "left" || prop === "top") { 16874 return; 16875 } 16876 if (object[prop] === prototype[prop]) { 16877 delete object[prop]; 16878 } 16879 // basically a check for [] === [] 16880 if ( 16881 Array.isArray(object[prop]) && 16882 Array.isArray(prototype[prop]) && 16883 object[prop].length === 0 && 16884 prototype[prop].length === 0 16885 ) { 16886 delete object[prop]; 16887 } 16888 }); 16889 16890 return object; 16891 }, 16892 16893 /** 16894 * Returns a string representation of an instance 16895 * @return {String} 16896 */ 16897 toString: function () { 16898 return "#<fabric." + capitalize(this.type) + ">"; 16899 }, 16900 16901 /** 16902 * Return the object scale factor counting also the group scaling 16903 * @return {Object} object with scaleX and scaleY properties 16904 */ 16905 getObjectScaling: function () { 16906 // if the object is a top level one, on the canvas, we go for simple aritmetic 16907 // otherwise the complex method with angles will return approximations and decimals 16908 // and will likely kill the cache when not needed 16909 // https://github.com/fabricjs/fabric.js/issues/7157 16910 if (!this.group) { 16911 return { 16912 scaleX: this.scaleX, 16913 scaleY: this.scaleY, 16914 }; 16915 } 16916 // if we are inside a group total zoom calculation is complex, we defer to generic matrices 16917 var options = fabric.util.qrDecompose(this.calcTransformMatrix()); 16918 return { 16919 scaleX: Math.abs(options.scaleX), 16920 scaleY: Math.abs(options.scaleY), 16921 }; 16922 }, 16923 16924 /** 16925 * Return the object scale factor counting also the group scaling, zoom and retina 16926 * @return {Object} object with scaleX and scaleY properties 16927 */ 16928 getTotalObjectScaling: function () { 16929 var scale = this.getObjectScaling(), 16930 scaleX = scale.scaleX, 16931 scaleY = scale.scaleY; 16932 if (this.canvas) { 16933 var zoom = this.canvas.getZoom(); 16934 var retina = this.canvas.getRetinaScaling(); 16935 scaleX *= zoom * retina; 16936 scaleY *= zoom * retina; 16937 } 16938 return { scaleX: scaleX, scaleY: scaleY }; 16939 }, 16940 16941 /** 16942 * Return the object opacity counting also the group property 16943 * @return {Number} 16944 */ 16945 getObjectOpacity: function () { 16946 var opacity = this.opacity; 16947 if (this.group) { 16948 opacity *= this.group.getObjectOpacity(); 16949 } 16950 return opacity; 16951 }, 16952 16953 /** 16954 * @private 16955 * @param {String} key 16956 * @param {*} value 16957 * @return {fabric.Object} thisArg 16958 */ 16959 _set: function (key, value) { 16960 var shouldConstrainValue = key === "scaleX" || key === "scaleY", 16961 isChanged = this[key] !== value, 16962 groupNeedsUpdate = false;
16963 16964 if (shouldConstrainValue) { 16965 value = this._constrainScale(value); 16966 } 16967 if (key === "scaleX" && value < 0) { 16968 this.flipX = !this.flipX; 16969 value *= -1; 16970 } else if (key === "scaleY" && value < 0) { 16971 this.flipY = !this.flipY; 16972 value *= -1; 16973 } else if ( 16974 key === "shadow" && 16975 value && 16976 !(value instanceof fabric.Shadow) 16977 ) { 16978 value = new fabric.Shadow(value); 16979 } else if (key === "dirty" && this.group) { 16980 this.group.set("dirty", value); 16981 } 16982 16983 this[key] = value; 16984 16985 if (isChanged) { 16986 groupNeedsUpdate = this.group && this.group.isOnACache(); 16987 if (this.cacheProperties.indexOf(key) > -1) { 16988 this.dirty = true; 16989 groupNeedsUpdate && this.group.set("dirty", true); 16990 } else if ( 16991 groupNeedsUpdate && 16992 this.stateProperties.indexOf(key) > -1 16993 ) { 16994 this.group.set("dirty", true); 16995 } 16996 } 16997 return this; 16998 }, 16999 17000 /** 17001 * This callback function is called by the parent group of an object every 17002 * time a non-delegated property changes on the group. It is passed the key 17003 * and value as parameters. Not adding in this function's signature to avoid 17004 * Travis build error about unused variables. 17005 */ 17006 setOnGroup: function () { 17007 // implemented by sub-classes, as needed. 17008 }, 17009 17010 /** 17011 * Retrieves viewportTransform from Object's canvas if possible 17012 * @method getViewportTransform 17013 * @memberOf fabric.Object.prototype 17014 * @return {Array} 17015 */ 17016 getViewportTransform: function () { 17017 if (this.canvas && this.canvas.viewportTransform) { 17018 return this.canvas.viewportTransform; 17019 } 17020 return fabric.iMatrix.concat(); 17021 }, 17022 17023 /* 17024 * @private 17025 * return if the object would be visible in rendering 17026 * @memberOf fabric.Object.prototype 17027 * @return {Boolean} 17028 */ 17029 isNotVisible: function () { 17030 return ( 17031 this.opacity === 0 || 17032 (!this.width && !this.height && this.strokeWidth === 0) || 17033 !this.visible 17034 ); 17035 }, 17036 17037 /** 17038 * Renders an object on a specified context 17039 * @param {CanvasRenderingContext2D} ctx Context to render on 17040 */ 17041 render: function (ctx) { 17042 // do not render if width/height are zeros or object is not visible 17043 if (this.isNotVisible()) { 17044 return; 17045 } 17046 if ( 17047 this.canvas && 17048 this.canvas.skipOffscreen && 17049 !this.group && 17050 !this.isOnScreen() 17051 ) { 17052 return; 17053 } 17054 ctx.save(); 17055 this._setupCompositeOperation(ctx); 17056 this.drawSelectionBackground(ctx); 17057 this.transform(ctx); 17058 this._setOpacity(ctx); 17059 this._setShadow(ctx, this); 17060 if (this.shouldCache()) { 17061 this.renderCache(); 17062 this.drawCacheOnCanvas(ctx); 17063 } else { 17064 this._removeCacheCanvas(); 17065 this.dirty = false; 17066 this.drawObject(ctx); 17067 if (this.objectCaching && this.statefullCache) { 17068 this.saveState({ propertySet: "cacheProperties" }); 17069 } 17070 } 17071 ctx.restore(); 17072 }, 17073 17074 renderCache: function (options) { 17075 options = options || {}; 17076 if (!this._cacheCanvas || !this._cacheContext) { 17077 this._createCacheCanvas(); 17078 } 17079 if (this.isCacheDirty()) { 17080 this.statefullCache && 17081 this.saveState({ propertySet: "cacheProperties" }); 17082 this.drawObject(this._cacheContext, options.forClipping); 17083 this.dirty = false; 17084 } 17085 }, 17086 17087 /** 17088 * Remove cacheCanvas and its dimensions from the objects 17089 */ 17090 _removeCacheCanvas: function () { 17091 this._cacheCanvas = null; 17092 this._cacheContext = null; 17093 this.cacheWidth = 0; 17094 this.cacheHeight = 0; 17095 }, 17096 17097 /** 17098 * return true if the object will draw a stroke 17099 * Does not consider text styles. This is just a shortcut used at rendering time 17100 * We want it to be an approximation and be fast. 17101 * wrote to avoid extra caching, it has to return true when stroke happens, 17102 * can guess when it will not happen at 100% chance, does not matter if it misses 17103 * some use case where the stroke is invisible. 17104 * @since 3.0.0 17105 * @returns Boolean 17106 */ 17107 hasStroke: function () { 17108 return ( 17109 this.stroke && this.stroke !== "transparent" && this.strokeWidth !== 0 17110 ); 17111 }, 17112 17113 /** 17114 * return true if the object will draw a fill 17115 * Does not consider text styles. This is just a shortcut used at rendering time 17116 * We want it to be an approximation and be fast. 17117 * wrote to avoid extra caching, it has to return true when fill happens, 17118 * can guess when it will not happen at 100% chance, does not matter if it misses 17119 * some use case where the fill is invisible. 17120 * @since 3.0.0 17121 * @returns Boolean 17122 */ 17123 hasFill: function () { 17124 return this.fill && this.fill !== "transparent";
17125 }, 17126 17127 /** 17128 * When set to `true`, force the object to have its own cache, even if it is inside a group 17129 * it may be needed when your object behave in a particular way on the cache and always needs 17130 * its own isolated canvas to render correctly. 17131 * Created to be overridden 17132 * since 1.7.12 17133 * @returns Boolean 17134 */ 17135 needsItsOwnCache: function () { 17136 if ( 17137 this.paintFirst === "stroke" && 17138 this.hasFill() && 17139 this.hasStroke() && 17140 typeof this.shadow === "object" 17141 ) { 17142 return true; 17143 } 17144 if (this.clipPath) { 17145 return true; 17146 } 17147 return false; 17148 }, 17149 17150 /** 17151 * Decide if the object should cache or not. Create its own cache level 17152 * objectCaching is a global flag, wins over everything 17153 * needsItsOwnCache should be used when the object drawing method requires 17154 * a cache step. None of the fabric classes requires it. 17155 * Generally you do not cache objects in groups because the group outside is cached. 17156 * Read as: cache if is needed, or if the feature is enabled but we are not already caching. 17157 * @return {Boolean} 17158 */ 17159 shouldCache: function () { 17160 this.ownCaching = 17161 this.needsItsOwnCache() || 17162 (this.objectCaching && (!this.group || !this.group.isOnACache())); 17163 return this.ownCaching; 17164 }, 17165 17166 /** 17167 * Check if this object or a child object will cast a shadow 17168 * used by Group.shouldCache to know if child has a shadow recursively 17169 * @return {Boolean} 17170 */ 17171 willDrawShadow: function () { 17172 return ( 17173 !!this.shadow && 17174 (this.shadow.offsetX !== 0 || this.shadow.offsetY !== 0) 17175 ); 17176 }, 17177 17178 /** 17179 * Execute the drawing operation for an object clipPath 17180 * @param {CanvasRenderingContext2D} ctx Context to render on 17181 * @param {fabric.Object} clipPath 17182 */ 17183 drawClipPathOnCache: function (ctx, clipPath) { 17184 ctx.save(); 17185 // DEBUG: uncomment this line, comment the following 17186 // ctx.globalAlpha = 0.4 17187 if (clipPath.inverted) { 17188 ctx.globalCompositeOperation = "destination-out"; 17189 } else { 17190 ctx.globalCompositeOperation = "destination-in"; 17191 } 17192 //ctx.scale(1 / 2, 1 / 2); 17193 if (clipPath.absolutePositioned) { 17194 var m = fabric.util.invertTransform(this.calcTransformMatrix()); 17195 ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); 17196 } 17197 clipPath.transform(ctx); 17198 ctx.scale(1 / clipPath.zoomX, 1 / clipPath.zoomY); 17199 ctx.drawImage( 17200 clipPath._cacheCanvas, 17201 -clipPath.cacheTranslationX, 17202 -clipPath.cacheTranslationY, 17203 ); 17204 ctx.restore(); 17205 }, 17206 17207 /** 17208 * Execute the drawing operation for an object on a specified context 17209 * @param {CanvasRenderingContext2D} ctx Context to render on 17210 */ 17211 drawObject: function (ctx, forClipping) { 17212 var originalFill = this.fill, 17213 originalStroke = this.stroke; 17214 if (forClipping) { 17215 this.fill = "black"; 17216 this.stroke = ""; 17217 this._setClippingProperties(ctx); 17218 } else { 17219 this._renderBackground(ctx); 17220 } 17221 this._render(ctx); 17222 this._drawClipPath(ctx, this.clipPath); 17223 this.fill = originalFill; 17224 this.stroke = originalStroke; 17225 }, 17226 17227 /** 17228 * Prepare clipPath state and cache and draw it on instance's cache 17229 * @param {CanvasRenderingContext2D} ctx 17230 * @param {fabric.Object} clipPath 17231 */ 17232 _drawClipPath: function (ctx, clipPath) { 17233 if (!clipPath) { 17234 return; 17235 } 17236 // needed to setup a couple of variables 17237 // path canvas gets overridden with this one. 17238 // TODO find a better solution? 17239 clipPath.canvas = this.canvas; 17240 clipPath.shouldCache(); 17241 clipPath._transformDone = true; 17242 clipPath.renderCache({ forClipping: true }); 17243 this.drawClipPathOnCache(ctx, clipPath); 17244 }, 17245 17246 /** 17247 * Paint the cached copy of the object on the target context. 17248 * @param {CanvasRenderingContext2D} ctx Context to render on 17249 */ 17250 drawCacheOnCanvas: function (ctx) { 17251 ctx.scale(1 / this.zoomX, 1 / this.zoomY); 17252 ctx.drawImage( 17253 this._cacheCanvas, 17254 -this.cacheTranslationX, 17255 -this.cacheTranslationY, 17256 ); 17257 }, 17258 17259 /** 17260 * Check if cache is dirty 17261 * @param {Boolean} skipCanvas skip canvas checks because this object is painted 17262 * on parent canvas. 17263 */ 17264 isCacheDirty: function (skipCanvas) { 17265 if (this.isNotVisible()) { 17266 return false;
vendor: 4,132 bytes, lines 17267-17394
17267 } 17268 if ( 17269 this._cacheCanvas && 17270 this._cacheContext && 17271 !skipCanvas && 17272 this._updateCacheCanvas() 17273 ) { 17274 // in this case the context is already cleared. 17275 return true; 17276 } else { 17277 if ( 17278 this.dirty || 17279 (this.clipPath && this.clipPath.absolutePositioned) || 17280 (this.statefullCache && this.hasStateChanged("cacheProperties")) 17281 ) { 17282 if (this._cacheCanvas && this._cacheContext && !skipCanvas) { 17283 var width = this.cacheWidth / this.zoomX; 17284 var height = this.cacheHeight / this.zoomY; 17285 this._cacheContext.clearRect( 17286 -width / 2, 17287 -height / 2, 17288 width, 17289 height, 17290 ); 17291 } 17292 return true; 17293 } 17294 } 17295 return false; 17296 }, 17297 17298 /** 17299 * Draws a background for the object big as its untransformed dimensions 17300 * @private 17301 * @param {CanvasRenderingContext2D} ctx Context to render on 17302 */ 17303 _renderBackground: function (ctx) { 17304 if (!this.backgroundColor) { 17305 return; 17306 } 17307 var dim = this._getNonTransformedDimensions(); 17308 ctx.fillStyle = this.backgroundColor; 17309 17310 ctx.fillRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y); 17311 // if there is background color no other shadows 17312 // should be casted 17313 this._removeShadow(ctx); 17314 }, 17315 17316 /** 17317 * @private 17318 * @param {CanvasRenderingContext2D} ctx Context to render on 17319 */ 17320 _setOpacity: function (ctx) { 17321 if (this.group && !this.group._transformDone) { 17322 ctx.globalAlpha = this.getObjectOpacity(); 17323 } else { 17324 ctx.globalAlpha *= this.opacity; 17325 } 17326 }, 17327 17328 _setStrokeStyles: function (ctx, decl) { 17329 var stroke = decl.stroke; 17330 if (stroke) { 17331 ctx.lineWidth = decl.strokeWidth; 17332 ctx.lineCap = decl.strokeLineCap; 17333 ctx.lineDashOffset = decl.strokeDashOffset; 17334 ctx.lineJoin = decl.strokeLineJoin; 17335 ctx.miterLimit = decl.strokeMiterLimit; 17336 if (stroke.toLive) { 17337 if ( 17338 stroke.gradientUnits === "percentage" || 17339 stroke.gradientTransform || 17340 stroke.patternTransform 17341 ) { 17342 // need to transform gradient in a pattern. 17343 // this is a slow process. If you are hitting this codepath, and the object 17344 // is not using caching, you should consider switching it on. 17345 // we need a canvas as big as the current object caching canvas. 17346 this._applyPatternForTransformedGradient(ctx, stroke); 17347 } else { 17348 // is a simple gradient or pattern 17349 ctx.strokeStyle = stroke.toLive(ctx, this); 17350 this._applyPatternGradientTransform(ctx, stroke); 17351 } 17352 } else { 17353 // is a color 17354 ctx.strokeStyle = decl.stroke; 17355 } 17356 } 17357 }, 17358 17359 _setFillStyles: function (ctx, decl) { 17360 var fill = decl.fill; 17361 if (fill) { 17362 if (fill.toLive) { 17363 ctx.fillStyle = fill.toLive(ctx, this); 17364 this._applyPatternGradientTransform(ctx, decl.fill); 17365 } else { 17366 ctx.fillStyle = fill; 17367 } 17368 } 17369 }, 17370 17371 _setClippingProperties: function (ctx) { 17372 ctx.globalAlpha = 1; 17373 ctx.strokeStyle = "transparent"; 17374 ctx.fillStyle = "#000000"; 17375 }, 17376 17377 /** 17378 * @private 17379 * Sets line dash 17380 * @param {CanvasRenderingContext2D} ctx Context to set the dash line on 17381 * @param {Array} dashArray array representing dashes 17382 */ 17383 _setLineDash: function (ctx, dashArray) { 17384 if (!dashArray || dashArray.length === 0) { 17385 return; 17386 } 17387 // Spec requires the concatenation of two copies the dash list when the number of elements is odd 17388 if (1 & dashArray.length) { 17389 dashArray.push.apply(dashArray, dashArray); 17390 } 17391 ctx.setLineDash(dashArray); 17392 }, 17393 17394 /**
17395 * Renders controls and borders for the object 17396 * the context here is not transformed 17397 * @param {CanvasRenderingContext2D} ctx Context to render on 17398 * @param {Object} [styleOverride] properties to override the object style 17399 */ 17400 _renderControls: function (ctx, styleOverride) { 17401 var vpt = this.getViewportTransform(), 17402 matrix = this.calcTransformMatrix(), 17403 options, 17404 drawBorders, 17405 drawControls; 17406 styleOverride = styleOverride || {}; 17407 drawBorders = 17408 typeof styleOverride.hasBorders !== "undefined" 17409 ? styleOverride.hasBorders 17410 : this.hasBorders; 17411 drawControls = 17412 typeof styleOverride.hasControls !== "undefined" 17413 ? styleOverride.hasControls 17414 : this.hasControls; 17415 matrix = fabric.util.multiplyTransformMatrices(vpt, matrix); 17416 options = fabric.util.qrDecompose(matrix); 17417 ctx.save(); 17418 ctx.translate(options.translateX, options.translateY); 17419 ctx.lineWidth = 1 * this.borderScaleFactor; 17420 if (!this.group) { 17421 ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; 17422 } 17423 if (this.flipX) { 17424 options.angle -= 180; 17425 } 17426 ctx.rotate(degreesToRadians(this.group ? options.angle : this.angle)); 17427 if (styleOverride.forActiveSelection || this.group) { 17428 drawBorders && this.drawBordersInGroup(ctx, options, styleOverride); 17429 } else { 17430 drawBorders && this.drawBorders(ctx, styleOverride); 17431 } 17432 drawControls && this.drawControls(ctx, styleOverride); 17433 ctx.restore(); 17434 }, 17435 17436 /** 17437 * @private 17438 * @param {CanvasRenderingContext2D} ctx Context to render on 17439 */ 17440 _setShadow: function (ctx) { 17441 if (!this.shadow) { 17442 return; 17443 } 17444 17445 var shadow = this.shadow, 17446 canvas = this.canvas, 17447 scaling, 17448 multX = (canvas && canvas.viewportTransform[0]) || 1, 17449 multY = (canvas && canvas.viewportTransform[3]) || 1; 17450 if (shadow.nonScaling) { 17451 scaling = { scaleX: 1, scaleY: 1 }; 17452 } else { 17453 scaling = this.getObjectScaling(); 17454 } 17455 if (canvas && canvas._isRetinaScaling()) { 17456 multX *= fabric.devicePixelRatio; 17457 multY *= fabric.devicePixelRatio; 17458 } 17459 ctx.shadowColor = shadow.color; 17460 ctx.shadowBlur = 17461 (shadow.blur * 17462 fabric.browserShadowBlurConstant * 17463 (multX + multY) * 17464 (scaling.scaleX + scaling.scaleY)) / 17465 4; 17466 ctx.shadowOffsetX = shadow.offsetX * multX * scaling.scaleX; 17467 ctx.shadowOffsetY = shadow.offsetY * multY * scaling.scaleY; 17468 }, 17469 17470 /** 17471 * @private 17472 * @param {CanvasRenderingContext2D} ctx Context to render on 17473 */ 17474 _removeShadow: function (ctx) { 17475 if (!this.shadow) { 17476 return; 17477 } 17478 17479 ctx.shadowColor = ""; 17480 ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; 17481 }, 17482 17483 /** 17484 * @private 17485 * @param {CanvasRenderingContext2D} ctx Context to render on 17486 * @param {Object} filler fabric.Pattern or fabric.Gradient 17487 * @return {Object} offset.offsetX offset for text rendering 17488 * @return {Object} offset.offsetY offset for text rendering 17489 */ 17490 _applyPatternGradientTransform: function (ctx, filler) { 17491 if (!filler || !filler.toLive) { 17492 return { offsetX: 0, offsetY: 0 }; 17493 } 17494 var t = filler.gradientTransform || filler.patternTransform; 17495 var offsetX = -this.width / 2 + filler.offsetX || 0, 17496 offsetY = -this.height / 2 + filler.offsetY || 0; 17497 17498 if (filler.gradientUnits === "percentage") { 17499 ctx.transform(this.width, 0, 0, this.height, offsetX, offsetY); 17500 } else { 17501 ctx.transform(1, 0, 0, 1, offsetX, offsetY); 17502 } 17503 if (t) { 17504 ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5]); 17505 } 17506 return { offsetX: offsetX, offsetY: offsetY }; 17507 }, 17508 17509 /** 17510 * @private 17511 * @param {CanvasRenderingContext2D} ctx Context to render on 17512 */ 17513 _renderPaintInOrder: function (ctx) { 17514 if (this.paintFirst === "stroke") { 17515 this._renderStroke(ctx); 17516 this._renderFill(ctx); 17517 } else { 17518 this._renderFill(ctx); 17519 this._renderStroke(ctx); 17520 } 17521 }, 17522 17523 /** 17524 * @private 17525 * function that actually render something on the context. 17526 * empty here to allow Obects to work on tests to benchmark fabric functionalites 17527 * not related to rendering 17528 * @param {CanvasRenderingContext2D} ctx Context to render on 17529 */ 17530 _render: function (/* ctx */) {}, 17531 17532 /** 17533 * @private 17534 * @param {CanvasRenderingContext2D} ctx Context to render on 17535 */ 17536 _renderFill: function (ctx) { 17537 if (!this.fill) { 17538 return; 17539 } 17540 17541 ctx.save(); 17542 this._setFillStyles(ctx, this); 17543 if (this.fillRule === "evenodd") { 17544 ctx.fill("evenodd"); 17545 } else { 17546 ctx.fill(); 17547 }
vendor: 8,515 bytes, lines 17548-17736
17548 ctx.restore(); 17549 }, 17550 17551 /** 17552 * @private 17553 * @param {CanvasRenderingContext2D} ctx Context to render on 17554 */ 17555 _renderStroke: function (ctx) { 17556 if (!this.stroke || this.strokeWidth === 0) { 17557 return; 17558 } 17559 17560 if (this.shadow && !this.shadow.affectStroke) { 17561 this._removeShadow(ctx); 17562 } 17563 17564 ctx.save(); 17565 if (this.strokeUniform && this.group) { 17566 var scaling = this.getObjectScaling(); 17567 ctx.scale(1 / scaling.scaleX, 1 / scaling.scaleY); 17568 } else if (this.strokeUniform) { 17569 ctx.scale(1 / this.scaleX, 1 / this.scaleY); 17570 } 17571 this._setLineDash(ctx, this.strokeDashArray); 17572 this._setStrokeStyles(ctx, this); 17573 ctx.stroke(); 17574 ctx.restore(); 17575 }, 17576 17577 /** 17578 * This function try to patch the missing gradientTransform on canvas gradients. 17579 * transforming a context to transform the gradient, is going to transform the stroke too. 17580 * we want to transform the gradient but not the stroke operation, so we create 17581 * a transformed gradient on a pattern and then we use the pattern instead of the gradient. 17582 * this method has drwabacks: is slow, is in low resolution, needs a patch for when the size 17583 * is limited. 17584 * @private 17585 * @param {CanvasRenderingContext2D} ctx Context to render on 17586 * @param {fabric.Gradient} filler a fabric gradient instance 17587 */ 17588 _applyPatternForTransformedGradient: function (ctx, filler) { 17589 var dims = this._limitCacheSize(this._getCacheCanvasDimensions()), 17590 pCanvas = fabric.util.createCanvasElement(), 17591 pCtx, 17592 retinaScaling = this.canvas.getRetinaScaling(), 17593 width = dims.x / this.scaleX / retinaScaling, 17594 height = dims.y / this.scaleY / retinaScaling; 17595 pCanvas.width = width; 17596 pCanvas.height = height; 17597 pCtx = pCanvas.getContext("2d"); 17598 pCtx.beginPath(); 17599 pCtx.moveTo(0, 0); 17600 pCtx.lineTo(width, 0); 17601 pCtx.lineTo(width, height); 17602 pCtx.lineTo(0, height); 17603 pCtx.closePath(); 17604 pCtx.translate(width / 2, height / 2); 17605 pCtx.scale( 17606 dims.zoomX / this.scaleX / retinaScaling, 17607 dims.zoomY / this.scaleY / retinaScaling, 17608 ); 17609 this._applyPatternGradientTransform(pCtx, filler); 17610 pCtx.fillStyle = filler.toLive(ctx); 17611 pCtx.fill(); 17612 ctx.translate( 17613 -this.width / 2 - this.strokeWidth / 2, 17614 -this.height / 2 - this.strokeWidth / 2, 17615 ); 17616 ctx.scale( 17617 (retinaScaling * this.scaleX) / dims.zoomX, 17618 (retinaScaling * this.scaleY) / dims.zoomY, 17619 ); 17620 ctx.strokeStyle = pCtx.createPattern(pCanvas, "no-repeat"); 17621 }, 17622 17623 /** 17624 * This function is an helper for svg import. it returns the center of the object in the svg 17625 * untransformed coordinates 17626 * @private 17627 * @return {Object} center point from element coordinates 17628 */ 17629 _findCenterFromElement: function () { 17630 return { x: this.left + this.width / 2, y: this.top + this.height / 2 }; 17631 }, 17632 17633 /** 17634 * This function is an helper for svg import. it decompose the transformMatrix 17635 * and assign properties to object. 17636 * untransformed coordinates 17637 * @private 17638 * @chainable 17639 */ 17640 _assignTransformMatrixProps: function () { 17641 if (this.transformMatrix) { 17642 var options = fabric.util.qrDecompose(this.transformMatrix); 17643 this.flipX = false; 17644 this.flipY = false; 17645 this.set("scaleX", options.scaleX); 17646 this.set("scaleY", options.scaleY); 17647 this.angle = options.angle; 17648 this.skewX = options.skewX; 17649 this.skewY = 0; 17650 } 17651 }, 17652 17653 /** 17654 * This function is an helper for svg import. it removes the transform matrix 17655 * and set to object properties that fabricjs can handle 17656 * @private 17657 * @param {Object} preserveAspectRatioOptions 17658 * @return {thisArg} 17659 */ 17660 _removeTransformMatrix: function (preserveAspectRatioOptions) { 17661 var center = this._findCenterFromElement(); 17662 if (this.transformMatrix) { 17663 this._assignTransformMatrixProps(); 17664 center = fabric.util.transformPoint(center, this.transformMatrix); 17665 } 17666 this.transformMatrix = null; 17667 if (preserveAspectRatioOptions) { 17668 this.scaleX *= preserveAspectRatioOptions.scaleX; 17669 this.scaleY *= preserveAspectRatioOptions.scaleY; 17670 this.cropX = preserveAspectRatioOptions.cropX; 17671 this.cropY = preserveAspectRatioOptions.cropY; 17672 center.x += preserveAspectRatioOptions.offsetLeft; 17673 center.y += preserveAspectRatioOptions.offsetTop; 17674 this.width = preserveAspectRatioOptions.width; 17675 this.height = preserveAspectRatioOptions.height; 17676 } 17677 this.setPositionByOrigin(center, "center", "center"); 17678 }, 17679 17680 /** 17681 * Clones an instance, using a callback method will work for every object. 17682 * @param {Function} callback Callback is invoked with a clone as a first argument 17683 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 17684 */ 17685 clone: function (callback, propertiesToInclude) { 17686 var objectForm = this.toObject(propertiesToInclude); 17687 if (this.constructor.fromObject) { 17688 this.constructor.fromObject(objectForm, callback); 17689 } else { 17690 fabric.Object._fromObject("Object", objectForm, callback); 17691 } 17692 }, 17693 17694 /** 17695 * Creates an instance of fabric.Image out of an object 17696 * makes use of toCanvasElement. 17697 * Once this method was based on toDataUrl and loadImage, so it also had a quality 17698 * and format option. toCanvasElement is faster and produce no loss of quality. 17699 * If you need to get a real Jpeg or Png from an object, using toDataURL is the right way to do it. 17700 * toCanvasElement and then toBlob from the obtained canvas is also a good option. 17701 * This method is sync now, but still support the callback because we did not want to break. 17702 * When fabricJS 5.0 will be planned, this will probably be changed to not have a callback. 17703 * @param {Function} callback callback, invoked with an instance as a first argument 17704 * @param {Object} [options] for clone as image, passed to toDataURL 17705 * @param {Number} [options.multiplier=1] Multiplier to scale by 17706 * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 17707 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 17708 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 17709 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 17710 * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 17711 * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 17712 * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 17713 * @return {fabric.Object} thisArg 17714 */ 17715 cloneAsImage: function (callback, options) { 17716 var canvasEl = this.toCanvasElement(options); 17717 if (callback) { 17718 callback(new fabric.Image(canvasEl)); 17719 } 17720 return this; 17721 }, 17722 17723 /** 17724 * Converts an object into a HTMLCanvas element 17725 * @param {Object} options Options object 17726 * @param {Number} [options.multiplier=1] Multiplier to scale by 17727 * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 17728 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 17729 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 17730 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 17731 * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 17732 * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 17733 * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 17734 * @return {HTMLCanvasElement} Returns DOM element <canvas> with the fabric.Object 17735 */ 17736 toCanvasElement: function (options) {
17737 options || (options = {}); 17738 17739 var utils = fabric.util, 17740 origParams = utils.saveObjectTransform(this), 17741 originalGroup = this.group, 17742 originalShadow = this.shadow, 17743 abs = Math.abs, 17744 multiplier = 17745 (options.multiplier || 1) * 17746 (options.enableRetinaScaling ? fabric.devicePixelRatio : 1); 17747 delete this.group; 17748 if (options.withoutTransform) { 17749 utils.resetObjectTransform(this); 17750 } 17751 if (options.withoutShadow) { 17752 this.shadow = null; 17753 } 17754 17755 var el = fabric.util.createCanvasElement(), 17756 // skip canvas zoom and calculate with setCoords now. 17757 boundingRect = this.getBoundingRect(true, true), 17758 shadow = this.shadow, 17759 scaling, 17760 shadowOffset = { x: 0, y: 0 }, 17761 shadowBlur, 17762 width, 17763 height; 17764 17765 if (shadow) { 17766 shadowBlur = shadow.blur; 17767 if (shadow.nonScaling) { 17768 scaling = { scaleX: 1, scaleY: 1 }; 17769 } else { 17770 scaling = this.getObjectScaling(); 17771 } 17772 // consider non scaling shadow. 17773 shadowOffset.x = 17774 2 * 17775 Math.round(abs(shadow.offsetX) + shadowBlur) * 17776 abs(scaling.scaleX); 17777 shadowOffset.y = 17778 2 * 17779 Math.round(abs(shadow.offsetY) + shadowBlur) * 17780 abs(scaling.scaleY); 17781 } 17782 width = boundingRect.width + shadowOffset.x; 17783 height = boundingRect.height + shadowOffset.y; 17784 // if the current width/height is not an integer 17785 // we need to make it so. 17786 el.width = Math.ceil(width); 17787 el.height = Math.ceil(height); 17788 var canvas = new fabric.StaticCanvas(el, { 17789 enableRetinaScaling: false, 17790 renderOnAddRemove: false, 17791 skipOffscreen: false, 17792 }); 17793 if (options.format === "jpeg") { 17794 canvas.backgroundColor = "#fff"; 17795 } 17796 this.setPositionByOrigin( 17797 new fabric.Point(canvas.width / 2, canvas.height / 2), 17798 "center", 17799 "center", 17800 ); 17801 17802 var originalCanvas = this.canvas; 17803 canvas.add(this); 17804 var canvasEl = canvas.toCanvasElement(multiplier || 1, options); 17805 this.shadow = originalShadow; 17806 this.set("canvas", originalCanvas); 17807 if (originalGroup) { 17808 this.group = originalGroup; 17809 } 17810 this.set(origParams).setCoords(); 17811 // canvas.dispose will call image.dispose that will nullify the elements 17812 // since this canvas is a simple element for the process, we remove references 17813 // to objects in this way in order to avoid object trashing. 17814 canvas._objects = []; 17815 canvas.dispose(); 17816 canvas = null; 17817 17818 return canvasEl; 17819 }, 17820 17821 /** 17822 * Converts an object into a data-url-like string 17823 * @param {Object} options Options object 17824 * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" 17825 * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. 17826 * @param {Number} [options.multiplier=1] Multiplier to scale by 17827 * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 17828 * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 17829 * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 17830 * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 17831 * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 17832 * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 17833 * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 17834 * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format 17835 */ 17836 toDataURL: function (options) { 17837 options || (options = {}); 17838 return fabric.util.toDataURL( 17839 this.toCanvasElement(options), 17840 options.format || "png", 17841 options.quality || 1, 17842 ); 17843 }, 17844 17845 /** 17846 * Returns true if specified type is identical to the type of an instance 17847 * @param {String} type Type to check against 17848 * @return {Boolean} 17849 */ 17850 isType: function (type) { 17851 return arguments.length > 1 17852 ? Array.from(arguments).includes(this.type) 17853 : this.type === type; 17854 }, 17855 17856 /** 17857 * Returns complexity of an instance 17858 * @return {Number} complexity of this instance (is 1 unless subclassed) 17859 */ 17860 complexity: function () { 17861 return 1; 17862 }, 17863 17864 /** 17865 * Returns a JSON representation of an instance 17866 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 17867 * @return {Object} JSON 17868 */ 17869 toJSON: function (propertiesToInclude) { 17870 // delegate, not alias 17871 return this.toObject(propertiesToInclude); 17872 }, 17873 17874 /** 17875 * Sets "angle" of an instance with centered rotation 17876 * @param {Number} angle Angle value (in degrees) 17877 * @return {fabric.Object} thisArg 17878 * @chainable 17879 */ 17880 rotate: function (angle) { 17881 var shouldCenterOrigin = 17882 (this.originX !== "center" || this.originY !== "center") && 17883 this.centeredRotation; 17884 17885 if (shouldCenterOrigin) { 17886 this._setOriginToCenter(); 17887 } 17888 17889 this.set("angle", angle); 17890 17891 if (shouldCenterOrigin) { 17892 this._resetOrigin(); 17893 } 17894 17895 return this; 17896 }, 17897 17898 /** 17899 * Centers object horizontally on canvas to which it was added last.
17900 * You might need to call `setCoords` on an object after centering, to update controls area. 17901 * @return {fabric.Object} thisArg 17902 * @chainable 17903 */ 17904 centerH: function () { 17905 this.canvas && this.canvas.centerObjectH(this); 17906 return this; 17907 }, 17908 17909 /** 17910 * Centers object horizontally on current viewport of canvas to which it was added last. 17911 * You might need to call `setCoords` on an object after centering, to update controls area. 17912 * @return {fabric.Object} thisArg 17913 * @chainable 17914 */ 17915 viewportCenterH: function () { 17916 this.canvas && this.canvas.viewportCenterObjectH(this); 17917 return this; 17918 }, 17919 17920 /** 17921 * Centers object vertically on canvas to which it was added last. 17922 * You might need to call `setCoords` on an object after centering, to update controls area. 17923 * @return {fabric.Object} thisArg 17924 * @chainable 17925 */ 17926 centerV: function () { 17927 this.canvas && this.canvas.centerObjectV(this); 17928 return this; 17929 }, 17930 17931 /** 17932 * Centers object vertically on current viewport of canvas to which it was added last. 17933 * You might need to call `setCoords` on an object after centering, to update controls area. 17934 * @return {fabric.Object} thisArg 17935 * @chainable 17936 */ 17937 viewportCenterV: function () { 17938 this.canvas && this.canvas.viewportCenterObjectV(this); 17939 return this; 17940 }, 17941 17942 /** 17943 * Centers object vertically and horizontally on canvas to which is was added last 17944 * You might need to call `setCoords` on an object after centering, to update controls area. 17945 * @return {fabric.Object} thisArg 17946 * @chainable 17947 */ 17948 center: function () { 17949 this.canvas && this.canvas.centerObject(this); 17950 return this; 17951 }, 17952 17953 /** 17954 * Centers object on current viewport of canvas to which it was added last. 17955 * You might need to call `setCoords` on an object after centering, to update controls area. 17956 * @return {fabric.Object} thisArg 17957 * @chainable 17958 */ 17959 viewportCenter: function () { 17960 this.canvas && this.canvas.viewportCenterObject(this); 17961 return this; 17962 }, 17963 17964 /** 17965 * Returns coordinates of a pointer relative to an object 17966 * @param {Event} e Event to operate upon 17967 * @param {Object} [pointer] Pointer to operate upon (instead of event) 17968 * @return {Object} Coordinates of a pointer (x, y) 17969 */ 17970 getLocalPointer: function (e, pointer) { 17971 pointer = pointer || this.canvas.getPointer(e); 17972 var pClicked = new fabric.Point(pointer.x, pointer.y), 17973 objectLeftTop = this._getLeftTopCoords(); 17974 if (this.angle) { 17975 pClicked = fabric.util.rotatePoint( 17976 pClicked, 17977 objectLeftTop, 17978 degreesToRadians(-this.angle), 17979 ); 17980 } 17981 return { 17982 x: pClicked.x - objectLeftTop.x, 17983 y: pClicked.y - objectLeftTop.y, 17984 }; 17985 }, 17986 17987 /** 17988 * Sets canvas globalCompositeOperation for specific object 17989 * custom composition operation for the particular object can be specified using globalCompositeOperation property 17990 * @param {CanvasRenderingContext2D} ctx Rendering canvas context 17991 */ 17992 _setupCompositeOperation: function (ctx) { 17993 if (this.globalCompositeOperation) { 17994 ctx.globalCompositeOperation = this.globalCompositeOperation; 17995 } 17996 }, 17997 17998 /** 17999 * cancel instance's running animations 18000 * override if necessary to dispose artifacts such as `clipPath` 18001 */ 18002 dispose: function () { 18003 if (fabric.runningAnimations) { 18004 fabric.runningAnimations.cancelByTarget(this); 18005 } 18006 }, 18007 }, 18008 ); 18009 18010 fabric.util.createAccessors && fabric.util.createAccessors(fabric.Object); 18011 18012 extend(fabric.Object.prototype, fabric.Observable); 18013 18014 /** 18015 * Defines the number of fraction digits to use when serializing object values. 18016 * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. 18017 * @static 18018 * @memberOf fabric.Object 18019 * @constant 18020 * @type Number 18021 */ 18022 fabric.Object.NUM_FRACTION_DIGITS = 2; 18023 18024 /** 18025 * Defines which properties should be enlivened from the object passed to {@link fabric.Object._fromObject} 18026 * @static 18027 * @memberOf fabric.Object 18028 * @constant 18029 * @type string[] 18030 */
18031 fabric.Object.ENLIVEN_PROPS = ["clipPath"]; 18032 18033 fabric.Object._fromObject = function ( 18034 className, 18035 object, 18036 callback, 18037 extraParam, 18038 ) { 18039 var klass = fabric[className]; 18040 object = clone(object, true); 18041 fabric.util.enlivenPatterns( 18042 [object.fill, object.stroke], 18043 function (patterns) { 18044 if (typeof patterns[0] !== "undefined") { 18045 object.fill = patterns[0]; 18046 } 18047 if (typeof patterns[1] !== "undefined") { 18048 object.stroke = patterns[1]; 18049 } 18050 fabric.util.enlivenObjectEnlivables(object, object, function () { 18051 var instance = extraParam 18052 ? new klass(object[extraParam], object) 18053 : new klass(object); 18054 callback && callback(instance); 18055 }); 18056 }, 18057 ); 18058 }; 18059 18060 /** 18061 * Unique id used internally when creating SVG elements 18062 * @static 18063 * @memberOf fabric.Object 18064 * @type Number 18065 */ 18066 fabric.Object.__uid = 0; 18067})(typeof exports !== "undefined" ? exports : this); 18068 18069(function () { 18070 var degreesToRadians = fabric.util.degreesToRadians, 18071 originXOffset = { 18072 left: -0.5, 18073 center: 0, 18074 right: 0.5, 18075 }, 18076 originYOffset = { 18077 top: -0.5, 18078 center: 0, 18079 bottom: 0.5, 18080 }; 18081 18082 fabric.util.object.extend( 18083 fabric.Object.prototype, 18084 /** @lends fabric.Object.prototype */ { 18085 /** 18086 * Translates the coordinates from a set of origin to another (based on the object's dimensions) 18087 * @param {fabric.Point} point The point which corresponds to the originX and originY params 18088 * @param {String} fromOriginX Horizontal origin: 'left', 'center' or 'right' 18089 * @param {String} fromOriginY Vertical origin: 'top', 'center' or 'bottom' 18090 * @param {String} toOriginX Horizontal origin: 'left', 'center' or 'right' 18091 * @param {String} toOriginY Vertical origin: 'top', 'center' or 'bottom' 18092 * @return {fabric.Point} 18093 */ 18094 translateToGivenOrigin: function ( 18095 point, 18096 fromOriginX, 18097 fromOriginY, 18098 toOriginX, 18099 toOriginY, 18100 ) { 18101 var x = point.x, 18102 y = point.y, 18103 offsetX, 18104 offsetY, 18105 dim; 18106 18107 if (typeof fromOriginX === "string") { 18108 fromOriginX = originXOffset[fromOriginX]; 18109 } else { 18110 fromOriginX -= 0.5; 18111 } 18112 18113 if (typeof toOriginX === "string") { 18114 toOriginX = originXOffset[toOriginX]; 18115 } else { 18116 toOriginX -= 0.5; 18117 } 18118 18119 offsetX = toOriginX - fromOriginX; 18120 18121 if (typeof fromOriginY === "string") { 18122 fromOriginY = originYOffset[fromOriginY]; 18123 } else { 18124 fromOriginY -= 0.5; 18125 } 18126 18127 if (typeof toOriginY === "string") { 18128 toOriginY = originYOffset[toOriginY]; 18129 } else { 18130 toOriginY -= 0.5; 18131 } 18132 18133 offsetY = toOriginY - fromOriginY; 18134 18135 if (offsetX || offsetY) { 18136 dim = this._getTransformedDimensions(); 18137 x = point.x + offsetX * dim.x; 18138 y = point.y + offsetY * dim.y; 18139 } 18140 18141 return new fabric.Point(x, y); 18142 }, 18143 18144 /** 18145 * Translates the coordinates from origin to center coordinates (based on the object's dimensions) 18146 * @param {fabric.Point} point The point which corresponds to the originX and originY params 18147 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 18148 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 18149 * @return {fabric.Point} 18150 */ 18151 translateToCenterPoint: function (point, originX, originY) { 18152 var p = this.translateToGivenOrigin( 18153 point, 18154 originX, 18155 originY, 18156 "center", 18157 "center", 18158 ); 18159 if (this.angle) { 18160 return fabric.util.rotatePoint( 18161 p, 18162 point, 18163 degreesToRadians(this.angle), 18164 ); 18165 } 18166 return p; 18167 }, 18168 18169 /** 18170 * Translates the coordinates from center to origin coordinates (based on the object's dimensions) 18171 * @param {fabric.Point} center The point which corresponds to center of the object 18172 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 18173 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 18174 * @return {fabric.Point} 18175 */ 18176 translateToOriginPoint: function (center, originX, originY) { 18177 var p = this.translateToGivenOrigin( 18178 center, 18179 "center", 18180 "center", 18181 originX, 18182 originY, 18183 ); 18184 if (this.angle) { 18185 return fabric.util.rotatePoint( 18186 p, 18187 center, 18188 degreesToRadians(this.angle), 18189 ); 18190 } 18191 return p; 18192 }, 18193 18194 /** 18195 * Returns the real center coordinates of the object 18196 * @return {fabric.Point} 18197 */ 18198 getCenterPoint: function () { 18199 var leftTop = new fabric.Point(this.left, this.top); 18200 return this.translateToCenterPoint(leftTop, this.originX, this.originY); 18201 }, 18202 18203 /** 18204 * Returns the coordinates of the object based on center coordinates 18205 * @param {fabric.Point} point The point which corresponds to the originX and originY params 18206 * @return {fabric.Point} 18207 */ 18208 // getOriginPoint: function(center) { 18209 // return this.translateToOriginPoint(center, this.originX, this.originY); 18210 // }, 18211 18212 /** 18213 * Returns the coordinates of the object as if it has a different origin 18214 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 18215 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 18216 * @return {fabric.Point} 18217 */ 18218 getPointByOrigin: function (originX, originY) { 18219 var center = this.getCenterPoint(); 18220 return this.translateToOriginPoint(center, originX, originY); 18221 }, 18222 18223 /** 18224 * Returns the point in local coordinates 18225 * @param {fabric.Point} point The point relative to the global coordinate system 18226 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 18227 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 18228 * @return {fabric.Point} 18229 */ 18230 toLocalPoint: function (point, originX, originY) { 18231 var center = this.getCenterPoint(), 18232 p, 18233 p2; 18234 18235 if (typeof originX !== "undefined" && typeof originY !== "undefined") { 18236 p = this.translateToGivenOrigin( 18237 center, 18238 "center", 18239 "center", 18240 originX, 18241 originY, 18242 ); 18243 } else { 18244 p = new fabric.Point(this.left, this.top); 18245 } 18246 18247 p2 = new fabric.Point(point.x, point.y); 18248 if (this.angle) { 18249 p2 = fabric.util.rotatePoint( 18250 p2, 18251 center, 18252 -degreesToRadians(this.angle), 18253 ); 18254 } 18255 return p2.subtractEquals(p); 18256 }, 18257 18258 /** 18259 * Returns the point in global coordinates 18260 * @param {fabric.Point} The point relative to the local coordinate system 18261 * @return {fabric.Point} 18262 */ 18263 // toGlobalPoint: function(point) { 18264 // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); 18265 // }, 18266 18267 /** 18268 * Sets the position of the object taking into consideration the object's origin 18269 * @param {fabric.Point} pos The new position of the object 18270 * @param {String} originX Horizontal origin: 'left', 'center' or 'right' 18271 * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' 18272 * @return {void} 18273 */ 18274 setPositionByOrigin: function (pos, originX, originY) { 18275 var center = this.translateToCenterPoint(pos, originX, originY), 18276 position = this.translateToOriginPoint( 18277 center, 18278 this.originX, 18279 this.originY, 18280 ); 18281 this.set("left", position.x); 18282 this.set("top", position.y); 18283 }, 18284 18285 /** 18286 * @param {String} to One of 'left', 'center', 'right' 18287 */ 18288 adjustPosition: function (to) { 18289 var angle = degreesToRadians(this.angle),
18290 hypotFull = this.getScaledWidth(), 18291 xFull = fabric.util.cos(angle) * hypotFull, 18292 yFull = fabric.util.sin(angle) * hypotFull, 18293 offsetFrom, 18294 offsetTo; 18295 18296 //TODO: this function does not consider mixed situation like top, center. 18297 if (typeof this.originX === "string") { 18298 offsetFrom = originXOffset[this.originX]; 18299 } else { 18300 offsetFrom = this.originX - 0.5; 18301 } 18302 if (typeof to === "string") { 18303 offsetTo = originXOffset[to]; 18304 } else { 18305 offsetTo = to - 0.5; 18306 } 18307 this.left += xFull * (offsetTo - offsetFrom); 18308 this.top += yFull * (offsetTo - offsetFrom); 18309 this.setCoords(); 18310 this.originX = to; 18311 }, 18312 18313 /** 18314 * Sets the origin/position of the object to it's center point 18315 * @private 18316 * @return {void} 18317 */ 18318 _setOriginToCenter: function () { 18319 this._originalOriginX = this.originX; 18320 this._originalOriginY = this.originY; 18321 18322 var center = this.getCenterPoint(); 18323 18324 this.originX = "center"; 18325 this.originY = "center"; 18326 18327 this.left = center.x; 18328 this.top = center.y; 18329 }, 18330 18331 /** 18332 * Resets the origin/position of the object to it's original origin 18333 * @private 18334 * @return {void} 18335 */ 18336 _resetOrigin: function () { 18337 var originPoint = this.translateToOriginPoint( 18338 this.getCenterPoint(), 18339 this._originalOriginX, 18340 this._originalOriginY, 18341 ); 18342 18343 this.originX = this._originalOriginX; 18344 this.originY = this._originalOriginY; 18345 18346 this.left = originPoint.x; 18347 this.top = originPoint.y; 18348 18349 this._originalOriginX = null; 18350 this._originalOriginY = null; 18351 }, 18352 18353 /** 18354 * @private 18355 */ 18356 _getLeftTopCoords: function () { 18357 return this.translateToOriginPoint( 18358 this.getCenterPoint(), 18359 "left", 18360 "top", 18361 ); 18362 }, 18363 }, 18364 ); 18365})(); 18366 18367(function () { 18368 function arrayFromCoords(coords) { 18369 return [ 18370 new fabric.Point(coords.tl.x, coords.tl.y),
18371 new fabric.Point(coords.tr.x, coords.tr.y), 18372 new fabric.Point(coords.br.x, coords.br.y), 18373 new fabric.Point(coords.bl.x, coords.bl.y), 18374 ]; 18375 } 18376 18377 var util = fabric.util, 18378 degreesToRadians = util.degreesToRadians, 18379 multiplyMatrices = util.multiplyTransformMatrices, 18380 transformPoint = util.transformPoint; 18381 18382 util.object.extend( 18383 fabric.Object.prototype, 18384 /** @lends fabric.Object.prototype */ { 18385 /** 18386 * Describe object's corner position in canvas element coordinates. 18387 * properties are depending on control keys and padding the main controls. 18388 * each property is an object with x, y and corner. 18389 * The `corner` property contains in a similar manner the 4 points of the 18390 * interactive area of the corner. 18391 * The coordinates depends from the controls positionHandler and are used 18392 * to draw and locate controls 18393 * @memberOf fabric.Object.prototype 18394 */ 18395 oCoords: null, 18396 18397 /** 18398 * Describe object's corner position in canvas object absolute coordinates 18399 * properties are tl,tr,bl,br and describe the four main corner. 18400 * each property is an object with x, y, instance of Fabric.Point. 18401 * The coordinates depends from this properties: width, height, scaleX, scaleY 18402 * skewX, skewY, angle, strokeWidth, top, left. 18403 * Those coordinates are useful to understand where an object is. They get updated 18404 * with oCoords but they do not need to be updated when zoom or panning change. 18405 * The coordinates get updated with @method setCoords. 18406 * You can calculate them without updating with @method calcACoords(); 18407 * @memberOf fabric.Object.prototype 18408 */ 18409 aCoords: null, 18410 18411 /** 18412 * Describe object's corner position in canvas element coordinates. 18413 * includes padding. Used of object detection. 18414 * set and refreshed with setCoords. 18415 * @memberOf fabric.Object.prototype 18416 */ 18417 lineCoords: null, 18418 18419 /** 18420 * storage for object transform matrix 18421 */ 18422 ownMatrixCache: null, 18423 18424 /** 18425 * storage for object full transform matrix 18426 */ 18427 matrixCache: null, 18428 18429 /** 18430 * custom controls interface 18431 * controls are added by default_controls.js 18432 */ 18433 controls: {}, 18434 18435 /** 18436 * return correct set of coordinates for intersection 18437 * this will return either aCoords or lineCoords. 18438 * @param {Boolean} absolute will return aCoords if true or lineCoords 18439 * @return {Object} {tl, tr, br, bl} points 18440 */ 18441 _getCoords: function (absolute, calculate) { 18442 if (calculate) { 18443 return absolute ? this.calcACoords() : this.calcLineCoords(); 18444 } 18445 if (!this.aCoords || !this.lineCoords) { 18446 this.setCoords(true); 18447 } 18448 return absolute ? this.aCoords : this.lineCoords; 18449 }, 18450 18451 /** 18452 * return correct set of coordinates for intersection 18453 * this will return either aCoords or lineCoords. 18454 * The coords are returned in an array. 18455 * @return {Array} [tl, tr, br, bl] of points 18456 */ 18457 getCoords: function (absolute, calculate) { 18458 return arrayFromCoords(this._getCoords(absolute, calculate)); 18459 }, 18460 18461 /** 18462 * Checks if object intersects with an area formed by 2 points 18463 * @param {Object} pointTL top-left point of area 18464 * @param {Object} pointBR bottom-right point of area 18465 * @param {Boolean} [absolute] use coordinates without viewportTransform 18466 * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords 18467 * @return {Boolean} true if object intersects with an area formed by 2 points 18468 */ 18469 intersectsWithRect: function (pointTL, pointBR, absolute, calculate) { 18470 var coords = this.getCoords(absolute, calculate), 18471 intersection = fabric.Intersection.intersectPolygonRectangle( 18472 coords, 18473 pointTL, 18474 pointBR, 18475 ); 18476 return intersection.status === "Intersection"; 18477 }, 18478 18479 /** 18480 * Checks if object intersects with another object 18481 * @param {Object} other Object to test 18482 * @param {Boolean} [absolute] use coordinates without viewportTransform 18483 * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords 18484 * @return {Boolean} true if object intersects with another object 18485 */ 18486 intersectsWithObject: function (other, absolute, calculate) { 18487 var intersection = fabric.Intersection.intersectPolygonPolygon( 18488 this.getCoords(absolute, calculate), 18489 other.getCoords(absolute, calculate), 18490 ); 18491 18492 return ( 18493 intersection.status === "Intersection" || 18494 other.isContainedWithinObject(this, absolute, calculate) || 18495 this.isContainedWithinObject(other, absolute, calculate) 18496 ); 18497 }, 18498 18499 /** 18500 * Checks if object is fully contained within area of another object 18501 * @param {Object} other Object to test 18502 * @param {Boolean} [absolute] use coordinates without viewportTransform 18503 * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords 18504 * @return {Boolean} true if object is fully contained within area of another object 18505 */ 18506 isContainedWithinObject: function (other, absolute, calculate) { 18507 var points = this.getCoords(absolute, calculate), 18508 otherCoords = absolute ? other.aCoords : other.lineCoords, 18509 i = 0, 18510 lines = other._getImageLines(otherCoords); 18511 for (; i < 4; i++) { 18512 if (!other.containsPoint(points[i], lines)) { 18513 return false;
18514 } 18515 } 18516 return true; 18517 }, 18518 18519 /** 18520 * Checks if object is fully contained within area formed by 2 points 18521 * @param {Object} pointTL top-left point of area 18522 * @param {Object} pointBR bottom-right point of area 18523 * @param {Boolean} [absolute] use coordinates without viewportTransform 18524 * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords 18525 * @return {Boolean} true if object is fully contained within area formed by 2 points 18526 */ 18527 isContainedWithinRect: function (pointTL, pointBR, absolute, calculate) { 18528 var boundingRect = this.getBoundingRect(absolute, calculate); 18529 18530 return ( 18531 boundingRect.left >= pointTL.x && 18532 boundingRect.left + boundingRect.width <= pointBR.x && 18533 boundingRect.top >= pointTL.y && 18534 boundingRect.top + boundingRect.height <= pointBR.y 18535 ); 18536 }, 18537 18538 /** 18539 * Checks if point is inside the object 18540 * @param {fabric.Point} point Point to check against 18541 * @param {Object} [lines] object returned from @method _getImageLines 18542 * @param {Boolean} [absolute] use coordinates without viewportTransform 18543 * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords 18544 * @return {Boolean} true if point is inside the object 18545 */ 18546 containsPoint: function (point, lines, absolute, calculate) { 18547 var coords = this._getCoords(absolute, calculate), 18548 lines = lines || this._getImageLines(coords), 18549 xPoints = this._findCrossPoints(point, lines); 18550 // if xPoints is odd then point is inside the object 18551 return xPoints !== 0 && xPoints % 2 === 1; 18552 }, 18553 18554 /** 18555 * Checks if object is contained within the canvas with current viewportTransform 18556 * the check is done stopping at first point that appears on screen 18557 * @param {Boolean} [calculate] use coordinates of current position instead of .aCoords 18558 * @return {Boolean} true if object is fully or partially contained within canvas 18559 */ 18560 isOnScreen: function (calculate) { 18561 if (!this.canvas) { 18562 return false; 18563 } 18564 var pointTL = this.canvas.vptCoords.tl, 18565 pointBR = this.canvas.vptCoords.br; 18566 var points = this.getCoords(true, calculate); 18567 // if some point is on screen, the object is on screen. 18568 if ( 18569 points.some(function (point) { 18570 return ( 18571 point.x <= pointBR.x && 18572 point.x >= pointTL.x && 18573 point.y <= pointBR.y && 18574 point.y >= pointTL.y 18575 ); 18576 }) 18577 ) { 18578 return true; 18579 } 18580 // no points on screen, check intersection with absolute coordinates 18581 if (this.intersectsWithRect(pointTL, pointBR, true, calculate)) { 18582 return true; 18583 } 18584 return this._containsCenterOfCanvas(pointTL, pointBR, calculate); 18585 }, 18586 18587 /** 18588 * Checks if the object contains the midpoint between canvas extremities 18589 * Does not make sense outside the context of isOnScreen and isPartiallyOnScreen 18590 * @private 18591 * @param {Fabric.Point} pointTL Top Left point 18592 * @param {Fabric.Point} pointBR Top Right point 18593 * @param {Boolean} calculate use coordinates of current position instead of .oCoords 18594 * @return {Boolean} true if the object contains the point 18595 */ 18596 _containsCenterOfCanvas: function (pointTL, pointBR, calculate) { 18597 // worst case scenario the object is so big that contains the screen 18598 var centerPoint = { 18599 x: (pointTL.x + pointBR.x) / 2, 18600 y: (pointTL.y + pointBR.y) / 2, 18601 }; 18602 if (this.containsPoint(centerPoint, null, true, calculate)) { 18603 return true; 18604 } 18605 return false; 18606 }, 18607 18608 /** 18609 * Checks if object is partially contained within the canvas with current viewportTransform 18610 * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords 18611 * @return {Boolean} true if object is partially contained within canvas 18612 */ 18613 isPartiallyOnScreen: function (calculate) { 18614 if (!this.canvas) { 18615 return false;
18616 } 18617 var pointTL = this.canvas.vptCoords.tl, 18618 pointBR = this.canvas.vptCoords.br; 18619 if (this.intersectsWithRect(pointTL, pointBR, true, calculate)) { 18620 return true; 18621 } 18622 var allPointsAreOutside = this.getCoords(true, calculate).every( 18623 function (point) { 18624 return ( 18625 (point.x >= pointBR.x || point.x <= pointTL.x) && 18626 (point.y >= pointBR.y || point.y <= pointTL.y) 18627 ); 18628 }, 18629 ); 18630 return ( 18631 allPointsAreOutside && 18632 this._containsCenterOfCanvas(pointTL, pointBR, calculate) 18633 ); 18634 }, 18635 18636 /** 18637 * Method that returns an object with the object edges in it, given the coordinates of the corners 18638 * @private 18639 * @param {Object} oCoords Coordinates of the object corners 18640 */ 18641 _getImageLines: function (oCoords) { 18642 var lines = { 18643 topline: { 18644 o: oCoords.tl, 18645 d: oCoords.tr, 18646 }, 18647 rightline: { 18648 o: oCoords.tr, 18649 d: oCoords.br, 18650 }, 18651 bottomline: { 18652 o: oCoords.br, 18653 d: oCoords.bl, 18654 }, 18655 leftline: { 18656 o: oCoords.bl, 18657 d: oCoords.tl, 18658 }, 18659 }; 18660 18661 // // debugging 18662 // if (this.canvas.contextTop) { 18663 // this.canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); 18664 // this.canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); 18665 // 18666 // this.canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); 18667 // this.canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); 18668 // 18669 // this.canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); 18670 // this.canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); 18671 // 18672 // this.canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); 18673 // this.canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); 18674 // } 18675 18676 return lines; 18677 }, 18678 18679 /** 18680 * Helper method to determine how many cross points are between the 4 object edges 18681 * and the horizontal line determined by a point on canvas 18682 * @private 18683 * @param {fabric.Point} point Point to check 18684 * @param {Object} lines Coordinates of the object being evaluated 18685 */ 18686 // remove yi, not used but left code here just in case. 18687 _findCrossPoints: function (point, lines) { 18688 var b1, 18689 b2, 18690 a1, 18691 a2, 18692 xi, // yi, 18693 xcount = 0, 18694 iLine; 18695 18696 for (var lineKey in lines) { 18697 iLine = lines[lineKey]; 18698 // optimisation 1: line below point. no cross 18699 if (iLine.o.y < point.y && iLine.d.y < point.y) { 18700 continue; 18701 } 18702 // optimisation 2: line above point. no cross 18703 if (iLine.o.y >= point.y && iLine.d.y >= point.y) { 18704 continue; 18705 } 18706 // optimisation 3: vertical line case 18707 if (iLine.o.x === iLine.d.x && iLine.o.x >= point.x) { 18708 xi = iLine.o.x; 18709 // yi = point.y; 18710 } 18711 // calculate the intersection point 18712 else { 18713 b1 = 0; 18714 b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); 18715 a1 = point.y - b1 * point.x; 18716 a2 = iLine.o.y - b2 * iLine.o.x; 18717 18718 xi = -(a1 - a2) / (b1 - b2); 18719 // yi = a1 + b1 * xi; 18720 } 18721 // dont count xi < point.x cases 18722 if (xi >= point.x) { 18723 xcount += 1; 18724 } 18725 // optimisation 4: specific for square images 18726 if (xcount === 2) { 18727 break; 18728 } 18729 } 18730 return xcount; 18731 }, 18732 18733 /** 18734 * Returns coordinates of object's bounding rectangle (left, top, width, height) 18735 * the box is intended as aligned to axis of canvas. 18736 * @param {Boolean} [absolute] use coordinates without viewportTransform 18737 * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords / .aCoords 18738 * @return {Object} Object with left, top, width, height properties 18739 */ 18740 getBoundingRect: function (absolute, calculate) { 18741 var coords = this.getCoords(absolute, calculate); 18742 return util.makeBoundingBoxFromPoints(coords); 18743 }, 18744 18745 /** 18746 * Returns width of an object's bounding box counting transformations 18747 * before 2.0 it was named getWidth(); 18748 * @return {Number} width value 18749 */ 18750 getScaledWidth: function () { 18751 return this._getTransformedDimensions().x; 18752 }, 18753 18754 /** 18755 * Returns height of an object bounding box counting transformations 18756 * before 2.0 it was named getHeight(); 18757 * @return {Number} height value 18758 */ 18759 getScaledHeight: function () { 18760 return this._getTransformedDimensions().y; 18761 }, 18762 18763 /** 18764 * Makes sure the scale is valid and modifies it if necessary 18765 * @private 18766 * @param {Number} value 18767 * @return {Number} 18768 */ 18769 _constrainScale: function (value) { 18770 if (Math.abs(value) < this.minScaleLimit) { 18771 if (value < 0) { 18772 return -this.minScaleLimit; 18773 } else { 18774 return this.minScaleLimit; 18775 } 18776 } else if (value === 0) { 18777 return 0.0001; 18778 } 18779 return value; 18780 }, 18781 18782 /** 18783 * Scales an object (equally by x and y) 18784 * @param {Number} value Scale factor 18785 * @return {fabric.Object} thisArg 18786 * @chainable 18787 */ 18788 scale: function (value) { 18789 this._set("scaleX", value); 18790 this._set("scaleY", value); 18791 return this.setCoords(); 18792 }, 18793 18794 /** 18795 * Scales an object to a given width, with respect to boun
18795ding box (scaling by x/y equally) 18796 * @param {Number} value New width value 18797 * @param {Boolean} absolute ignore viewport 18798 * @return {fabric.Object} thisArg 18799 * @chainable 18800 */ 18801 scaleToWidth: function (value, absolute) { 18802 // adjust to bounding rect factor so that rotated shapes would fit as well 18803 var boundingRectFactor = 18804 this.getBoundingRect(absolute).width / this.getScaledWidth(); 18805 return this.scale(value / this.width / boundingRectFactor); 18806 }, 18807 18808 /** 18809 * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) 18810 * @param {Number} value New height value 18811 * @param {Boolean} absolute ignore viewport 18812 * @return {fabric.Object} thisArg 18813 * @chainable 18814 */ 18815 scaleToHeight: function (value, absolute) { 18816 // adjust to bounding rect factor so that rotated shapes would fit as well 18817 var boundingRectFactor = 18818 this.getBoundingRect(absolute).height / this.getScaledHeight(); 18819 return this.scale(value / this.height / boundingRectFactor); 18820 }, 18821 18822 calcLineCoords: function () { 18823 var vpt = this.getViewportTransform(), 18824 padding = this.padding, 18825 angle = degreesToRadians(this.angle), 18826 cos = util.cos(angle), 18827 sin = util.sin(angle), 18828 cosP = cos * padding, 18829 sinP = sin * padding, 18830 cosPSinP = cosP + sinP, 18831 cosPMinusSinP = cosP - sinP, 18832 aCoords = this.calcACoords(); 18833 18834 var lineCoords = { 18835 tl: transformPoint(aCoords.tl, vpt), 18836 tr: transformPoint(aCoords.tr, vpt), 18837 bl: transformPoint(aCoords.bl, vpt), 18838 br: transformPoint(aCoords.br, vpt), 18839 }; 18840 18841 if (padding) { 18842 lineCoords.tl.x -= cosPMinusSinP; 18843 lineCoords.tl.y -= cosPSinP; 18844 lineCoords.tr.x += cosPSinP; 18845 lineCoords.tr.y -= cosPMinusSinP; 18846 lineCoords.bl.x -= cosPSinP; 18847 lineCoords.bl.y += cosPMinusSinP; 18848 lineCoords.br.x += cosPMinusSinP; 18849 lineCoords.br.y += cosPSinP; 18850 } 18851 18852 return lineCoords; 18853 }, 18854 18855 calcOCoords: function () { 18856 var rotateMatrix = this._calcRotateMatrix(), 18857 translateMatrix = this._calcTranslateMatrix(), 18858 vpt = this.getViewportTransform(), 18859 startMatrix = multiplyMatrices(vpt, translateMatrix), 18860 finalMatrix = multiplyMatrices(startMatrix, rotateMatrix), 18861 finalMatrix = multiplyMatrices(finalMatrix, [ 18862 1 / vpt[0], 18863 0, 18864 0, 18865 1 / vpt[3], 18866 0, 18867 0, 18868 ]), 18869 dim = this._calculateCurrentDimensions(), 18870 coords = {}; 18871 this.forEachControl(function (control, key, fabricObject) { 18872 coords[key] = control.positionHandler(dim, finalMatrix, fabricObject); 18873 }); 18874 18875 // debug code 18876 // var canvas = this.canvas; 18877 // setTimeout(function() { 18878 // canvas.contextTop.clearRect(0, 0, 700, 700); 18879 // canvas.contextTop.fillStyle = 'green';
18880 // Object.keys(coords).forEach(function(key) { 18881 // var control = coords[key]; 18882 // canvas.contextTop.fillRect(control.x, control.y, 3, 3); 18883 // }); 18884 // }, 50); 18885 return coords; 18886 }, 18887 18888 calcACoords: function () { 18889 var rotateMatrix = this._calcRotateMatrix(), 18890 translateMatrix = this._calcTranslateMatrix(), 18891 finalMatrix = multiplyMatrices(translateMatrix, rotateMatrix), 18892 dim = this._getTransformedDimensions(), 18893 w = dim.x / 2, 18894 h = dim.y / 2; 18895 return { 18896 // corners 18897 tl: transformPoint({ x: -w, y: -h }, finalMatrix), 18898 tr: transformPoint({ x: w, y: -h }, finalMatrix), 18899 bl: transformPoint({ x: -w, y: h }, finalMatrix), 18900 br: transformPoint({ x: w, y: h }, finalMatrix), 18901 }; 18902 }, 18903 18904 /** 18905 * Sets corner and controls position coordinates based on current angle, width and height, left and top. 18906 * oCoords are used to find the corners 18907 * aCoords are used to quickly find an object on the canvas 18908 * lineCoords are used to quickly find object during pointer events. 18909 * See {@link https://github.com/fabricjs/fabric.js/wiki/When-to-call-setCoords} and {@link http://fabricjs.com/fabric-gotchas} 18910 * 18911 * @param {Boolean} [skipCorners] skip calculation of oCoords. 18912 * @return {fabric.Object} thisArg 18913 * @chainable 18914 */ 18915 setCoords: function (skipCorners) { 18916 this.aCoords = this.calcACoords(); 18917 // in case we are in a group, for how the inner group target check works, 18918 // lineCoords are exactly aCoords. Since the vpt gets absorbed by the normalized pointer. 18919 this.lineCoords = this.group ? this.aCoords : this.calcLineCoords(); 18920 if (skipCorners) { 18921 return this; 18922 } 18923 // set coordinates of the draggable boxes in the corners used to scale/rotate the image 18924 this.oCoords = this.calcOCoords(); 18925 this._setCornerCoords && this._setCornerCoords(); 18926 return this; 18927 }, 18928 18929 /** 18930 * calculate rotation matrix of an object 18931 * @return {Array} rotation matrix for the object 18932 */ 18933 _calcRotateMatrix: function () { 18934 return util.calcRotateMatrix(this); 18935 }, 18936 18937 /** 18938 * calculate the translation matrix for an object transform 18939 * @return {Array} rotation matrix for the object 18940 */ 18941 _calcTranslateMatrix: function () { 18942 var center = this.getCenterPoint(); 18943 return [1, 0, 0, 1, center.x, center.y]; 18944 }, 18945 18946 transformMatrixKey: function (skipGroup) { 18947 var sep = "_", 18948 prefix = ""; 18949 if (!skipGroup && this.group) { 18950 prefix = this.group.transformMatrixKey(skipGroup) + sep; 18951 } 18952 return ( 18953 prefix + 18954 this.top + 18955 sep + 18956 this.left + 18957 sep + 18958 this.scaleX + 18959 sep + 18960 this.scaleY + 18961 sep + 18962 this.skewX + 18963 sep + 18964 this.skewY + 18965 sep + 18966 this.angle + 18967 sep + 18968 this.originX + 18969 sep + 18970 this.originY + 18971 sep + 18972 this.width + 18973 sep + 18974 this.height + 18975 sep + 18976 this.strokeWidth + 18977 this.flipX + 18978 this.flipY 18979 ); 18980 }, 18981 18982 /** 18983 * calculate transform matrix that represents the current transformations from the 18984 * object's properties. 18985 * @param {Boolean} [skipGroup] return transform matrix for object not counting parent transformations 18986 * There are some situation in which this is useful to avoid the fake rotation. 18987 * @return {Array} transform matrix for the object 18988 */ 18989 calcTransformMatrix: function (skipGroup) { 18990 var matrix = this.calcOwnMatrix(); 18991 if (skipGroup || !this.group) { 18992 return matrix; 18993 } 18994 var key = this.transformMatrixKey(skipGroup), 18995 cache = this.matrixCache || (this.matrixCache = {}); 18996 if (cache.key === key) { 18997 return cache.value; 18998 } 18999 if (this.group) { 19000 matrix = multiplyMatrices( 19001 this.group.calcTransformMatrix(false), 19002 matrix, 19003 ); 19004 } 19005 cache.key = key; 19006 cache.value = matrix; 19007 return matrix; 19008 }, 19009 19010 /** 19011 * calculate transform matrix that represents the current transformations from the 19012 * object's properties, this matrix does not include the group transformation 19013 * @return {Array} transform matrix for the object 19014 */ 19015 calcOwnMatrix: function () { 19016 var key = this.transformMatrixKey(true),
vendor: 7,375 bytes, lines 19017-19253
19017 cache = this.ownMatrixCache || (this.ownMatrixCache = {}); 19018 if (cache.key === key) { 19019 return cache.value; 19020 } 19021 var tMatrix = this._calcTranslateMatrix(), 19022 options = { 19023 angle: this.angle, 19024 translateX: tMatrix[4], 19025 translateY: tMatrix[5], 19026 scaleX: this.scaleX, 19027 scaleY: this.scaleY, 19028 skewX: this.skewX, 19029 skewY: this.skewY, 19030 flipX: this.flipX, 19031 flipY: this.flipY, 19032 }; 19033 cache.key = key; 19034 cache.value = util.composeMatrix(options); 19035 return cache.value; 19036 }, 19037 19038 /* 19039 * Calculate object dimensions from its properties 19040 * @private 19041 * @return {Object} .x width dimension 19042 * @return {Object} .y height dimension 19043 */ 19044 _getNonTransformedDimensions: function () { 19045 var strokeWidth = this.strokeWidth, 19046 w = this.width + strokeWidth, 19047 h = this.height + strokeWidth; 19048 return { x: w, y: h }; 19049 }, 19050 19051 /* 19052 * Calculate object bounding box dimensions from its properties scale, skew. 19053 * @param {Number} skewX, a value to override current skewX 19054 * @param {Number} skewY, a value to override current skewY 19055 * @private 19056 * @return {Object} .x width dimension 19057 * @return {Object} .y height dimension 19058 */ 19059 _getTransformedDimensions: function (skewX, skewY) { 19060 if (typeof skewX === "undefined") { 19061 skewX = this.skewX; 19062 } 19063 if (typeof skewY === "undefined") { 19064 skewY = this.skewY; 19065 } 19066 var dimensions, 19067 dimX, 19068 dimY, 19069 noSkew = skewX === 0 && skewY === 0; 19070 19071 if (this.strokeUniform) { 19072 dimX = this.width; 19073 dimY = this.height; 19074 } else { 19075 dimensions = this._getNonTransformedDimensions(); 19076 dimX = dimensions.x; 19077 dimY = dimensions.y; 19078 } 19079 if (noSkew) { 19080 return this._finalizeDimensions( 19081 dimX * this.scaleX, 19082 dimY * this.scaleY, 19083 ); 19084 } 19085 var bbox = util.sizeAfterTransform(dimX, dimY, { 19086 scaleX: this.scaleX, 19087 scaleY: this.scaleY, 19088 skewX: skewX, 19089 skewY: skewY, 19090 }); 19091 return this._finalizeDimensions(bbox.x, bbox.y); 19092 }, 19093 19094 /* 19095 * Calculate object bounding box dimensions from its properties scale, skew. 19096 * @param Number width width of the bbox 19097 * @param Number height height of the bbox 19098 * @private 19099 * @return {Object} .x finalized width dimension 19100 * @return {Object} .y finalized height dimension 19101 */ 19102 _finalizeDimensions: function (width, height) { 19103 return this.strokeUniform 19104 ? { x: width + this.strokeWidth, y: height + this.strokeWidth } 19105 : { x: width, y: height }; 19106 }, 19107 19108 /* 19109 * Calculate object dimensions for controls box, including padding and canvas zoom. 19110 * and active selection 19111 * private 19112 */ 19113 _calculateCurrentDimensions: function () { 19114 var vpt = this.getViewportTransform(), 19115 dim = this._getTransformedDimensions(), 19116 p = transformPoint(dim, vpt, true); 19117 return p.scalarAdd(2 * this.padding); 19118 }, 19119 }, 19120 ); 19121})(); 19122 19123fabric.util.object.extend( 19124 fabric.Object.prototype, 19125 /** @lends fabric.Object.prototype */ { 19126 /** 19127 * Moves an object to the bottom of the stack of drawn objects 19128 * @return {fabric.Object} thisArg 19129 * @chainable 19130 */ 19131 sendToBack: function () { 19132 if (this.group) { 19133 fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); 19134 } else if (this.canvas) { 19135 this.canvas.sendToBack(this); 19136 } 19137 return this; 19138 }, 19139 19140 /** 19141 * Moves an object to the top of the stack of drawn objects 19142 * @return {fabric.Object} thisArg 19143 * @chainable 19144 */ 19145 bringToFront: function () { 19146 if (this.group) { 19147 fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); 19148 } else if (this.canvas) { 19149 this.canvas.bringToFront(this); 19150 } 19151 return this; 19152 }, 19153 19154 /** 19155 * Moves an object down in stack of drawn objects 19156 * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object 19157 * @return {fabric.Object} thisArg 19158 * @chainable 19159 */ 19160 sendBackwards: function (intersecting) { 19161 if (this.group) { 19162 fabric.StaticCanvas.prototype.sendBackwards.call( 19163 this.group, 19164 this, 19165 intersecting, 19166 ); 19167 } else if (this.canvas) { 19168 this.canvas.sendBackwards(this, intersecting); 19169 } 19170 return this; 19171 }, 19172 19173 /** 19174 * Moves an object up in stack of drawn objects 19175 * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object 19176 * @return {fabric.Object} thisArg 19177 * @chainable 19178 */ 19179 bringForward: function (intersecting) { 19180 if (this.group) { 19181 fabric.StaticCanvas.prototype.bringForward.call( 19182 this.group, 19183 this, 19184 intersecting, 19185 ); 19186 } else if (this.canvas) { 19187 this.canvas.bringForward(this, intersecting); 19188 } 19189 return this; 19190 }, 19191 19192 /** 19193 * Moves an object to specified level in stack of drawn objects 19194 * @param {Number} index New position of object 19195 * @return {fabric.Object} thisArg 19196 * @chainable 19197 */ 19198 moveTo: function (index) { 19199 if (this.group && this.group.type !== "activeSelection") { 19200 fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); 19201 } else if (this.canvas) { 19202 this.canvas.moveTo(this, index); 19203 } 19204 return this; 19205 }, 19206 }, 19207); 19208 19209/* _TO_SVG_START_ */ 19210(function () { 19211 function getSvgColorString(prop, value) { 19212 if (!value) { 19213 return prop + ": none; "; 19214 } else if (value.toLive) { 19215 return prop + ": url(#SVGID_" + value.id + "); "; 19216 } else { 19217 var color = new fabric.Color(value), 19218 str = prop + ": " + color.toRgb() + "; ", 19219 opacity = color.getAlpha(); 19220 if (opacity !== 1) { 19221 //change the color in rgb + opacity 19222 str += prop + "-opacity: " + opacity.toString() + "; "; 19223 } 19224 return str; 19225 } 19226 } 19227 19228 var toFixed = fabric.util.toFixed; 19229 19230 fabric.util.object.extend( 19231 fabric.Object.prototype, 19232 /** @lends fabric.Object.prototype */ { 19233 /** 19234 * Returns styles-string for svg-export 19235 * @param {Boolean} skipShadow a boolean to skip shadow filter output 19236 * @return {String} 19237 */ 19238 getSvgStyles: function (skipShadow) { 19239 var fillRule = this.fillRule ? this.fillRule : "nonzero", 19240 strokeWidth = this.strokeWidth ? this.strokeWidth : "0", 19241 strokeDashArray = this.strokeDashArray 19242 ? this.strokeDashArray.join(" ") 19243 : "none", 19244 strokeDashOffset = this.strokeDashOffset 19245 ? this.strokeDashOffset 19246 : "0", 19247 strokeLineCap = this.strokeLineCap ? this.strokeLineCap : "butt", 19248 strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : "miter", 19249 strokeMiterLimit = this.strokeMiterLimit 19250 ? this.strokeMiterLimit 19251 : "4", 19252 opacity = typeof this.opacity !== "undefined" ? this.opacity : "1", 19253 visibility = this.visible ? "" : " visibility: hidden;",
19254 filter = skipShadow ? "" : this.getSvgFilter(), 19255 fill = getSvgColorString("fill", this.fill), 19256 stroke = getSvgColorString("stroke", this.stroke); 19257 19258 return [ 19259 stroke, 19260 "stroke-width: ", 19261 strokeWidth, 19262 "; ", 19263 "stroke-dasharray: ", 19264 strokeDashArray, 19265 "; ", 19266 "stroke-linecap: ", 19267 strokeLineCap, 19268 "; ", 19269 "stroke-dashoffset: ", 19270 strokeDashOffset, 19271 "; ", 19272 "stroke-linejoin: ", 19273 strokeLineJoin, 19274 "; ", 19275 "stroke-miterlimit: ", 19276 strokeMiterLimit, 19277 "; ", 19278 fill, 19279 "fill-rule: ", 19280 fillRule, 19281 "; ", 19282 "opacity: ", 19283 opacity, 19284 ";", 19285 filter, 19286 visibility, 19287 ].join(""); 19288 }, 19289 19290 /** 19291 * Returns styles-string for svg-export 19292 * @param {Object} style the object from which to retrieve style properties 19293 * @param {Boolean} useWhiteSpace a boolean to include an additional attribute in the style. 19294 * @return {String} 19295 */ 19296 getSvgSpanStyles: function (style, useWhiteSpace) { 19297 var term = "; "; 19298 var fontFamily = style.fontFamily 19299 ? "font-family: " + 19300 (style.fontFamily.indexOf("'") === -1 && 19301 style.fontFamily.indexOf('"') === -1 19302 ? "'" + style.fontFamily + "'" 19303 : style.fontFamily) + 19304 term 19305 : ""; 19306 var strokeWidth = style.strokeWidth 19307 ? "stroke-width: " + style.strokeWidth + term 19308 : "", 19309 fontFamily = fontFamily, 19310 fontSize = style.fontSize 19311 ? "font-size: " + style.fontSize + "px" + term 19312 : "", 19313 fontStyle = style.fontStyle 19314 ? "font-style: " + style.fontStyle + term 19315 : "", 19316 fontWeight = style.fontWeight 19317 ? "font-weight: " + style.fontWeight + term 19318 : "", 19319 fill = style.fill ? getSvgColorString("fill", style.fill) : "", 19320 stroke = style.stroke 19321 ? getSvgColorString("stroke", style.stroke) 19322 : "", 19323 textDecoration = this.getSvgTextDecoration(style), 19324 deltaY = style.deltaY 19325 ? "baseline-shift: " + -style.deltaY + "; " 19326 : ""; 19327 if (textDecoration) { 19328 textDecoration = "text-decoration: " + textDecoration + term; 19329 } 19330 19331 return [ 19332 stroke, 19333 strokeWidth, 19334 fontFamily, 19335 fontSize, 19336 fontStyle, 19337 fontWeight, 19338 textDecoration, 19339 fill, 19340 deltaY, 19341 useWhiteSpace ? "white-space: pre; " : "", 19342 ].join(""); 19343 }, 19344 19345 /** 19346 * Returns text-decoration property for svg-export 19347 * @param {Object} style the object from which to retrieve style properties 19348 * @return {String} 19349 */ 19350 getSvgTextDecoration: function (style) { 19351 return ["overline", "underline", "line-through"] 19352 .filter(function (decoration) { 19353 return style[decoration.replace("-", "")]; 19354 }) 19355 .join(" "); 19356 }, 19357 19358 /** 19359 * Returns filter for svg shadow 19360 * @return {String} 19361 */ 19362 getSvgFilter: function () { 19363 return this.shadow ? "filter: url(#SVGID_" + this.shadow.id + ");" : ""; 19364 }, 19365 19366 /** 19367 * Returns id attribute for svg output 19368 * @return {String} 19369 */ 19370 getSvgCommons: function () { 19371 return [ 19372 this.id ? 'id="' + this.id + '" ' : "", 19373 this.clipPath 19374 ? 'clip-path="url(#' + this.clipPath.clipPathId + ')" ' 19375 : "", 19376 ].join(""); 19377 }, 19378 19379 /** 19380 * Returns transform-string for svg-export 19381 * @param {Boolean} use the full transform or the single object one. 19382 * @return {String} 19383 */ 19384 getSvgTransform: function (full, additionalTransform) { 19385 var transform = full 19386 ? this.calcTransformMatrix() 19387 : this.calcOwnMatrix(), 19388 svgTransform = 'transform="' + fabric.util.matrixToSVG(transform);
19389 return svgTransform + (additionalTransform || "") + '" '; 19390 }, 19391 19392 _setSVGBg: function (textBgRects) { 19393 if (this.backgroundColor) { 19394 var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; 19395 textBgRects.push( 19396 "\t\t<rect ", 19397 this._getFillAttributes(this.backgroundColor), 19398 ' x="', 19399 toFixed(-this.width / 2, NUM_FRACTION_DIGITS), 19400 '" y="', 19401 toFixed(-this.height / 2, NUM_FRACTION_DIGITS), 19402 '" width="', 19403 toFixed(this.width, NUM_FRACTION_DIGITS), 19404 '" height="', 19405 toFixed(this.height, NUM_FRACTION_DIGITS), 19406 '"></rect>\n', 19407 ); 19408 } 19409 }, 19410 19411 /** 19412 * Returns svg representation of an instance 19413 * @param {Function} [reviver] Method for further parsing of svg representation. 19414 * @return {String} svg representation of an instance 19415 */ 19416 toSVG: function (reviver) { 19417 return this._createBaseSVGMarkup(this._toSVG(reviver), { 19418 reviver: reviver, 19419 }); 19420 }, 19421 19422 /** 19423 * Returns svg clipPath representation of an instance 19424 * @param {Function} [reviver] Method for further parsing of svg representation. 19425 * @return {String} svg representation of an instance 19426 */ 19427 toClipPathSVG: function (reviver) { 19428 return ( 19429 "\t" + 19430 this._createBaseClipPathSVGMarkup(this._toSVG(reviver), { 19431 reviver: reviver, 19432 }) 19433 ); 19434 }, 19435 19436 /** 19437 * @private 19438 */ 19439 _createBaseClipPathSVGMarkup: function (objectMarkup, options) { 19440 options = options || {}; 19441 var reviver = options.reviver, 19442 additionalTransform = options.additionalTransform || "", 19443 commonPieces = [
vendor: 6,477 bytes, lines 19444-19638
19444 this.getSvgTransform(true, additionalTransform), 19445 this.getSvgCommons(), 19446 ].join(""), 19447 // insert commons in the markup, style and svgCommons 19448 index = objectMarkup.indexOf("COMMON_PARTS"); 19449 objectMarkup[index] = commonPieces; 19450 return reviver ? reviver(objectMarkup.join("")) : objectMarkup.join(""); 19451 }, 19452 19453 /** 19454 * @private 19455 */ 19456 _createBaseSVGMarkup: function (objectMarkup, options) { 19457 options = options || {}; 19458 var noStyle = options.noStyle, 19459 reviver = options.reviver, 19460 styleInfo = noStyle ? "" : 'style="' + this.getSvgStyles() + '" ', 19461 shadowInfo = options.withShadow 19462 ? 'style="' + this.getSvgFilter() + '" ' 19463 : "", 19464 clipPath = this.clipPath, 19465 vectorEffect = this.strokeUniform 19466 ? 'vector-effect="non-scaling-stroke" ' 19467 : "", 19468 absoluteClipPath = clipPath && clipPath.absolutePositioned, 19469 stroke = this.stroke, 19470 fill = this.fill, 19471 shadow = this.shadow, 19472 commonPieces, 19473 markup = [], 19474 clipPathMarkup, 19475 // insert commons in the markup, style and svgCommons 19476 index = objectMarkup.indexOf("COMMON_PARTS"), 19477 additionalTransform = options.additionalTransform; 19478 if (clipPath) { 19479 clipPath.clipPathId = "CLIPPATH_" + fabric.Object.__uid++; 19480 clipPathMarkup = 19481 '<clipPath id="' + 19482 clipPath.clipPathId + 19483 '" >\n' + 19484 clipPath.toClipPathSVG(reviver) + 19485 "</clipPath>\n"; 19486 } 19487 if (absoluteClipPath) { 19488 markup.push("<g ", shadowInfo, this.getSvgCommons(), " >\n"); 19489 } 19490 markup.push( 19491 "<g ", 19492 this.getSvgTransform(false), 19493 !absoluteClipPath ? shadowInfo + this.getSvgCommons() : "", 19494 " >\n", 19495 ); 19496 commonPieces = [ 19497 styleInfo, 19498 vectorEffect, 19499 noStyle ? "" : this.addPaintOrder(), 19500 " ", 19501 additionalTransform ? 'transform="' + additionalTransform + '" ' : "", 19502 ].join(""); 19503 objectMarkup[index] = commonPieces; 19504 if (fill && fill.toLive) { 19505 markup.push(fill.toSVG(this)); 19506 } 19507 if (stroke && stroke.toLive) { 19508 markup.push(stroke.toSVG(this)); 19509 } 19510 if (shadow) { 19511 markup.push(shadow.toSVG(this)); 19512 } 19513 if (clipPath) { 19514 markup.push(clipPathMarkup); 19515 } 19516 markup.push(objectMarkup.join("")); 19517 markup.push("</g>\n"); 19518 absoluteClipPath && markup.push("</g>\n"); 19519 return reviver ? reviver(markup.join("")) : markup.join(""); 19520 }, 19521 19522 addPaintOrder: function () { 19523 return this.paintFirst !== "fill" 19524 ? ' paint-order="' + this.paintFirst + '" ' 19525 : ""; 19526 }, 19527 }, 19528 ); 19529})(); 19530/* _TO_SVG_END_ */ 19531 19532(function () { 19533 var extend = fabric.util.object.extend, 19534 originalSet = "stateProperties"; 19535 19536 /* 19537 Depends on `stateProperties` 19538 */ 19539 function saveProps(origin, destination, props) { 19540 var tmpObj = {}, 19541 deep = true; 19542 props.forEach(function (prop) { 19543 tmpObj[prop] = origin[prop]; 19544 }); 19545 19546 extend(origin[destination], tmpObj, deep); 19547 } 19548 19549 function _isEqual(origValue, currentValue, firstPass) { 19550 if (origValue === currentValue) { 19551 // if the objects are identical, return 19552 return true; 19553 } else if (Array.isArray(origValue)) { 19554 if ( 19555 !Array.isArray(currentValue) || 19556 origValue.length !== currentValue.length 19557 ) { 19558 return false; 19559 } 19560 for (var i = 0, len = origValue.length; i < len; i++) { 19561 if (!_isEqual(origValue[i], currentValue[i])) { 19562 return false; 19563 } 19564 } 19565 return true; 19566 } else if (origValue && typeof origValue === "object") { 19567 var keys = Object.keys(origValue), 19568 key; 19569 if ( 19570 !currentValue || 19571 typeof currentValue !== "object" || 19572 (!firstPass && keys.length !== Object.keys(currentValue).length) 19573 ) { 19574 return false; 19575 } 19576 for (var i = 0, len = keys.length; i < len; i++) { 19577 key = keys[i]; 19578 // since clipPath is in the statefull cache list and the clipPath objects 19579 // would be iterated as an object, this would lead to possible infinite recursion 19580 // we do not want to compare those. 19581 if (key === "canvas" || key === "group") { 19582 continue; 19583 } 19584 if (!_isEqual(origValue[key], currentValue[key])) { 19585 return false; 19586 } 19587 } 19588 return true; 19589 } 19590 } 19591 19592 fabric.util.object.extend( 19593 fabric.Object.prototype, 19594 /** @lends fabric.Object.prototype */ { 19595 /** 19596 * Returns true if object state (one of its state properties) was changed 19597 * @param {String} [propertySet] optional name for the set of property we want to save 19598 * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called 19599 */ 19600 hasStateChanged: function (propertySet) { 19601 propertySet = propertySet || originalSet; 19602 var dashedPropertySet = "_" + propertySet; 19603 if ( 19604 Object.keys(this[dashedPropertySet]).length < this[propertySet].length 19605 ) { 19606 return true; 19607 } 19608 return !_isEqual(this[dashedPropertySet], this, true); 19609 }, 19610 19611 /** 19612 * Saves state of an object 19613 * @param {Object} [options] Object with additional `stateProperties` array to include when saving state 19614 * @return {fabric.Object} thisArg 19615 */ 19616 saveState: function (options) { 19617 var propertySet = (options && options.propertySet) || originalSet, 19618 destination = "_" + propertySet; 19619 if (!this[destination]) { 19620 return this.setupState(options); 19621 } 19622 saveProps(this, destination, this[propertySet]); 19623 if (options && options.stateProperties) { 19624 saveProps(this, destination, options.stateProperties); 19625 } 19626 return this; 19627 }, 19628 19629 /** 19630 * Setups state of an object 19631 * @param {Object} [options] Object with additional `stateProperties` array to include when saving state 19632 * @return {fabric.Object} thisArg 19633 */ 19634 setupState: function (options) { 19635 options = options || {}; 19636 var propertySet = options.propertySet || originalSet; 19637 options.propertySet = propertySet; 19638 this["_" + propertySet] = {};
19639 this.saveState(options); 19640 return this; 19641 }, 19642 }, 19643 ); 19644})(); 19645 19646(function () { 19647 var degreesToRadians = fabric.util.degreesToRadians; 19648 19649 fabric.util.object.extend( 19650 fabric.Object.prototype, 19651 /** @lends fabric.Object.prototype */ { 19652 /** 19653 * Determines which corner has been clicked 19654 * @private 19655 * @param {Object} pointer The pointer indicating the mouse position 19656 * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found 19657 */ 19658 _findTargetCorner: function (pointer, forTouch) { 19659 // objects in group, anykind, are not self modificable, 19660 // must not return an hovered corner. 19661 if ( 19662 !this.hasControls || 19663 this.group || 19664 !this.canvas || 19665 this.canvas._activeObject !== this 19666 ) { 19667 return false; 19668 } 19669 19670 var ex = pointer.x, 19671 ey = pointer.y, 19672 xPoints, 19673 lines, 19674 keys = Object.keys(this.oCoords), 19675 j = keys.length - 1, 19676 i; 19677 this.__corner = 0; 19678 19679 // cycle in reverse order so we pick first the one on top 19680 for (; j >= 0; j--) { 19681 i = keys[j]; 19682 if (!this.isControlVisible(i)) { 19683 continue; 19684 } 19685 19686 lines = this._getImageLines( 19687 forTouch ? this.oCoords[i].touchCorner : this.oCoords[i].corner, 19688 ); 19689 // // debugging 19690 // 19691 // this.canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); 19692 // this.canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); 19693 // 19694 // this.canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); 19695 // this.canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); 19696 // 19697 // this.canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); 19698 // this.canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); 19699 // 19700 // this.canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); 19701 // this.canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); 19702 19703 xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); 19704 if (xPoints !== 0 && xPoints % 2 === 1) { 19705 this.__corner = i; 19706 return i; 19707 } 19708 } 19709 return false; 19710 }, 19711 19712 /** 19713 * Calls a function for each control. The function gets called, 19714 * with the control, the object that is calling the iterator and the control's key 19715 * @param {Function} fn function to iterate over the controls over 19716 */ 19717 forEachControl: function (fn) { 19718 for (var i in this.controls) { 19719 fn(this.controls[i], i, this); 19720 } 19721 }, 19722 19723 /** 19724 * Sets the coordinates of the draggable boxes in the corners of 19725 * the image used to scale/rotate it. 19726 * note: if we would switch to ROUND corner area, all of this would disappear. 19727 * everything would resolve to a single point and a pythagorean theorem for the distance 19728 * @private 19729 */ 19730 _setCornerCoords: function () { 19731 var coords = this.oCoords; 19732 19733 for (var control in coords) { 19734 var controlObject = this.controls[control]; 19735 coords[control].corner = controlObject.calcCornerCoords( 19736 this.angle, 19737 this.cornerSize, 19738 coords[control].x, 19739 coords[control].y, 19740 false, 19741 ); 19742 coords[control].touchCorner = controlObject.calcCornerCoords( 19743 this.angle, 19744 this.touchCornerSize, 19745 coords[control].x, 19746 coords[control].y, 19747 true, 19748 ); 19749 } 19750 }, 19751 19752 /** 19753 * Draws a colored layer behind the object, inside its selection borders. 19754 * Requires public options: padding, selectionBackgroundColor 19755 * this function is called when the context is transformed 19756 * has checks to be skipped when the object is on a staticCanvas 19757 * @param {CanvasRenderingContext2D} ctx Context to draw on 19758 * @return {fabric.Object} thisArg 19759 * @chainable 19760 */
19761 drawSelectionBackground: function (ctx) { 19762 if ( 19763 !this.selectionBackgroundColor || 19764 (this.canvas && !this.canvas.interactive) || 19765 (this.canvas && this.canvas._activeObject !== this) 19766 ) { 19767 return this; 19768 } 19769 ctx.save(); 19770 var center = this.getCenterPoint(), 19771 wh = this._calculateCurrentDimensions(), 19772 vpt = this.canvas.viewportTransform; 19773 ctx.translate(center.x, center.y); 19774 ctx.scale(1 / vpt[0], 1 / vpt[3]); 19775 ctx.rotate(degreesToRadians(this.angle)); 19776 ctx.fillStyle = this.selectionBackgroundColor; 19777 ctx.fillRect(-wh.x / 2, -wh.y / 2, wh.x, wh.y); 19778 ctx.restore(); 19779 return this; 19780 }, 19781 19782 /** 19783 * Draws borders of an object's bounding box. 19784 * Requires public properties: width, height 19785 * Requires public options: padding, borderColor 19786 * @param {CanvasRenderingContext2D} ctx Context to draw on 19787 * @param {Object} styleOverride object to override the object style 19788 * @return {fabric.Object} thisArg 19789 * @chainable 19790 */ 19791 drawBorders: function (ctx, styleOverride) { 19792 styleOverride = styleOverride || {}; 19793 var wh = this._calculateCurrentDimensions(), 19794 strokeWidth = this.borderScaleFactor, 19795 width = wh.x + strokeWidth, 19796 height = wh.y + strokeWidth, 19797 hasControls = 19798 typeof styleOverride.hasControls !== "undefined" 19799 ? styleOverride.hasControls 19800 : this.hasControls, 19801 shouldStroke = false; 19802 19803 ctx.save(); 19804 ctx.strokeStyle = styleOverride.borderColor || this.borderColor; 19805 this._setLineDash( 19806 ctx, 19807 styleOverride.borderDashArray || this.borderDashArray, 19808 ); 19809 19810 ctx.strokeRect(-width / 2, -height / 2, width, height); 19811 19812 if (hasControls) { 19813 ctx.beginPath(); 19814 this.forEachControl(function (control, key, fabricObject) { 19815 // in this moment, the ctx is centered on the object. 19816 // width and height of the above function are the size of the bbox. 19817 if ( 19818 control.withConnection && 19819 control.getVisibility(fabricObject, key) 19820 ) { 19821 // reset movement for each control 19822 shouldStroke = true; 19823 ctx.moveTo(control.x * width, control.y * height); 19824 ctx.lineTo( 19825 control.x * width + control.offsetX, 19826 control.y * height + control.offsetY, 19827 ); 19828 } 19829 }); 19830 if (shouldStroke) { 19831 ctx.stroke(); 19832 } 19833 } 19834 ctx.restore(); 19835 return this; 19836 }, 19837 19838 /** 19839 * Draws borders of an object's bounding box when it is inside a group. 19840 * Requires public properties: width, height 19841 * Requires public options: padding, borderColor 19842 * @param {CanvasRenderingContext2D} ctx Context to draw on 19843 * @param {object} options object representing current object parameters 19844 * @param {Object} styleOverride object to override the object style 19845 * @return {fabric.Object} thisArg 19846 * @chainable 19847 */ 19848 drawBordersInGroup: function (ctx, options, styleOverride) { 19849 styleOverride = styleOverride || {}; 19850 var bbox = fabric.util.sizeAfterTransform( 19851 this.width, 19852 this.height, 19853 options, 19854 ), 19855 strokeWidth = this.strokeWidth, 19856 strokeUniform = this.strokeUniform, 19857 borderScaleFactor = this.borderScaleFactor, 19858 width = 19859 bbox.x + 19860 strokeWidth * 19861 (strokeUniform ? this.canvas.getZoom() : options.scaleX) + 19862 borderScaleFactor, 19863 height = 19864 bbox.y + 19865 strokeWidth * 19866 (strokeUniform ? this.canvas.getZoom() : options.scaleY) + 19867 borderScaleFactor; 19868 ctx.save(); 19869 this._setLineDash( 19870 ctx, 19871 styleOverride.borderDashArray || this.borderDashArray, 19872 ); 19873 ctx.strokeStyle = styleOverride.borderColor || this.borderColor; 19874 ctx.strokeRect(-width / 2, -height / 2, width, height); 19875
19876 ctx.restore(); 19877 return this; 19878 }, 19879 19880 /** 19881 * Draws corners of an object's bounding box. 19882 * Requires public properties: width, height 19883 * Requires public options: cornerSize, padding 19884 * @param {CanvasRenderingContext2D} ctx Context to draw on 19885 * @param {Object} styleOverride object to override the object style 19886 * @return {fabric.Object} thisArg 19887 * @chainable 19888 */ 19889 drawControls: function (ctx, styleOverride) { 19890 styleOverride = styleOverride || {}; 19891 ctx.save(); 19892 var retinaScaling = this.canvas.getRetinaScaling(), 19893 matrix, 19894 p; 19895 ctx.setTransform(retinaScaling, 0, 0, retinaScaling, 0, 0); 19896 ctx.strokeStyle = ctx.fillStyle = 19897 styleOverride.cornerColor || this.cornerColor; 19898 if (!this.transparentCorners) { 19899 ctx.strokeStyle = 19900 styleOverride.cornerStrokeColor || this.cornerStrokeColor; 19901 } 19902 this._setLineDash( 19903 ctx, 19904 styleOverride.cornerDashArray || this.cornerDashArray, 19905 ); 19906 this.setCoords(); 19907 if (this.group) { 19908 // fabricJS does not really support drawing controls inside groups, 19909 // this piece of code here helps having at least the control in places. 19910 // If an application needs to show some objects as selected because of some UI state 19911 // can still call Object._renderControls() on any object they desire, independently of groups. 19912 // using no padding, circular controls and hiding the rotating cursor is higly suggested, 19913 matrix = this.group.calcTransformMatrix(); 19914 } 19915 this.forEachControl(function (control, key, fabricObject) { 19916 p = fabricObject.oCoords[key]; 19917 if (control.getVisibility(fabricObject, key)) { 19918 if (matrix) { 19919 p = fabric.util.transformPoint(p, matrix); 19920 } 19921 control.render(ctx, p.x, p.y, styleOverride, fabricObject); 19922 } 19923 }); 19924 ctx.restore(); 19925 19926 return this; 19927 }, 19928 19929 /** 19930 * Returns true if the specified control is visible, false otherwise. 19931 * @param {String} controlKey The key of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. 19932 * @returns {Boolean} true if the specified control is visible, false otherwise 19933 */ 19934 isControlVisible: function (controlKey) { 19935 return ( 19936 this.controls[controlKey] && 19937 this.controls[controlKey].getVisibility(this, controlKey) 19938 ); 19939 }, 19940 19941 /** 19942 * Sets the visibility of the specified control. 19943 * @param {String} controlKey The key of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. 19944 * @param {Boolean} visible true to set the specified control visible, false otherwise 19945 * @return {fabric.Object} thisArg 19946 * @chainable 19947 */ 19948 setControlVisible: function (controlKey, visible) { 19949 if (!this._controlsVisibility) { 19950 this._controlsVisibility = {}; 19951 } 19952 this._controlsVisibility[controlKey] = visible; 19953 return this; 19954 }, 19955 19956 /** 19957 * Sets the visibility state of object controls. 19958 * @param {Object} [options] Options object 19959 * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it 19960 * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it 19961 * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it 19962 * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it 19963 * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it 19964 * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it 19965 * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it 19966 * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it 19967 * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it 19968 * @return {fabric.Object} thisArg 19969 * @chainable 19970 */ 19971 setControlsVisibility: function (options) {
19972 options || (options = {}); 19973 19974 for (var p in options) { 19975 this.setControlVisible(p, options[p]); 19976 } 19977 return this; 19978 }, 19979 19980 /** 19981 * This callback function is called every time _discardActiveObject or _setActiveObject 19982 * try to to deselect this object. If the function returns true, the process is cancelled 19983 * @param {Object} [options] options sent from the upper functions 19984 * @param {Event} [options.e] event if the process is generated by an event 19985 */ 19986 onDeselect: function () { 19987 // implemented by sub-classes, as needed. 19988 }, 19989 19990 /** 19991 * This callback function is called every time _discardActiveObject or _setActiveObject 19992 * try to to select this object. If the function returns true, the process is cancelled 19993 * @param {Object} [options] options sent from the upper functions 19994 * @param {Event} [options.e] event if the process is generated by an event 19995 */ 19996 onSelect: function () { 19997 // implemented by sub-classes, as needed. 19998 }, 19999 }, 20000 ); 20001})(); 20002 20003fabric.util.object.extend( 20004 fabric.StaticCanvas.prototype, 20005 /** @lends fabric.StaticCanvas.prototype */ { 20006 /** 20007 * Animation duration (in ms) for fx* methods 20008 * @type Number 20009 * @default 20010 */ 20011 FX_DURATION: 500, 20012 20013 /** 20014 * Centers object horizontally with animation. 20015 * @param {fabric.Object} object Object to center 20016 * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties 20017 * @param {Function} [callbacks.onComplete] Invoked on completion 20018 * @param {Function} [callbacks.onChange] Invoked on every step of animation 20019 * @return {fabric.AnimationContext} context 20020 */ 20021 fxCenterObjectH: function (object, callbacks) { 20022 callbacks = callbacks || {}; 20023 20024 var empty = function () {}, 20025 onComplete = callbacks.onComplete || empty, 20026 onChange = callbacks.onChange || empty, 20027 _this = this; 20028 20029 return fabric.util.animate({ 20030 target: this, 20031 startValue: object.left, 20032 endValue: this.getCenterPoint().x, 20033 duration: this.FX_DURATION, 20034 onChange: function (value) { 20035 object.set("left", value); 20036 _this.requestRenderAll(); 20037 onChange(); 20038 }, 20039 onComplete: function () { 20040 object.setCoords(); 20041 onComplete(); 20042 }, 20043 }); 20044 }, 20045 20046 /** 20047 * Centers object vertically with animation. 20048 * @param {fabric.Object} object Object to center 20049 * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties 20050 * @param {Function} [callbacks.onComplete] Invoked on completion 20051 * @param {Function} [callbacks.onChange] Invoked on every step of animation 20052 * @return {fabric.AnimationContext} context 20053 */ 20054 fxCenterObjectV: function (object, callbacks) { 20055 callbacks = callbacks || {}; 20056 20057 var empty = function () {}, 20058 onComplete = callbacks.onComplete || empty, 20059 onChange = callbacks.onChange || empty, 20060 _this = this; 20061 20062 return fabric.util.animate({ 20063 target: this, 20064 startValue: object.top, 20065 endValue: this.getCenterPoint().y, 20066 duration: this.FX_DURATION, 20067 onChange: function (value) { 20068 object.set("top", value); 20069 _this.requestRenderAll(); 20070 onChange(); 20071 }, 20072 onComplete: function () { 20073 object.setCoords(); 20074 onComplete(); 20075 }, 20076 }); 20077 }, 20078 20079 /** 20080 * Same as `fabric.Canvas#remove` but animated 20081 * @param {fabric.Object} object Object to remove 20082 * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties 20083 * @param {Function} [callbacks.onComplete] Invoked on completion 20084 * @param {Function} [callbacks.onChange] Invoked on every step of animation 20085 * @return {fabric.AnimationContext} context 20086 */ 20087 fxRemove: function (object, callbacks) { 20088 callbacks = callbacks || {}; 20089 20090 var empty = function () {}, 20091 onComplete = callbacks.onComplete || empty, 20092 onChange = callbacks.onChange || empty, 20093 _this = this; 20094 20095 return fabric.util.animate({ 20096 target: this, 20097 startValue: object.opacity, 20098 endValue: 0, 20099 duration: this.FX_DURATION, 20100 onChange: function (value) { 20101 object.set("opacity", value); 20102 _this.requestRenderAll(); 20103 onChange(); 20104 }, 20105 onComplete: function () { 20106 _this.remove(object); 20107 onComplete(); 20108 }, 20109 }); 20110 }, 20111 }, 20112); 20113 20114fabric.util.object.extend( 20115 fabric.Object.prototype, 20116 /** @lends fabric.Object.prototype */ { 20117 /** 20118 * Animates object's properties 20119 * @param {String|Object} property Property to animate (if string) or properties to animate (if object) 20120 * @param {Number|Object} value Value to animate property to (if string was given first) or options object 20121 * @return {fabric.Object} thisArg 20122 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation} 20123 * @return {fabric.AnimationContext | fabric.AnimationContext[]} animation context (or an array if passed multiple properties) 20124 * 20125 * As object — multiple properties 20126 * 20127 * object.animate({ left: ..., top: ... }); 20128 * object.animate({ left: ..., top: ... }, { duration: ... }); 20129 * 20130 * As string — one property 20131 * 20132 * object.animate('left', ...); 20133 * object.animate('left', { duration: ... }); 20134 * 20135 */ 20136 animate: function () { 20137 if (arguments[0] && typeof arguments[0] === "object") { 20138 var propsToAnimate = [], 20139 prop, 20140 skipCallbacks, 20141 out = []; 20142 for (prop in arguments[0]) { 20143 propsToAnimate.push(prop); 20144 } 20145 for (var i = 0, len = propsToAnimate.length; i < len; i++) { 20146 prop = propsToAnimate[i]; 20147 skipCallbacks = i !== len - 1; 20148 out.push( 20149 this._animate( 20150 prop, 20151 arguments[0][prop], 20152 arguments[1], 20153 skipCallbacks, 20154 ), 20155 ); 20156 } 20157 return out; 20158 } else { 20159 return this._animate.apply(this, arguments); 20160 } 20161 }, 20162 20163 /** 20164 * @private 20165 * @param {String} property Property to animate 20166 * @param {String} to Value to animate to 20167 * @param {Object} [options] Options object 20168 * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked 20169 */ 20170 _animate: function (property, to, options, skipCallbacks) { 20171 var _this = this, 20172 propPair; 20173 20174 to = to.toString(); 20175 20176 if (!options) { 20177 options = {}; 20178 } else { 20179 options = fabric.util.object.clone(options); 20180 } 20181 20182 if (~property.indexOf(".")) { 20183 propPair = property.split("."); 20184 } 20185 20186 var propIsColor = 20187 _this.colorProperties.indexOf(property) > -1 || 20188 (propPair && _this.colorProperties.indexOf(propPair[1]) > -1); 20189 20190 var currentValue = propPair 20191 ? this.get(propPair[0])[propPair[1]] 20192 : this.get(property); 20193 20194 if (!("from" in options)) { 20195 options.from = currentValue; 20196 } 20197 20198 if (!propIsColor) { 20199 if (~to.indexOf("=")) { 20200 to = currentValue + parseFloat(to.replace("=", "")); 20201 } else { 20202 to = parseFloat(to); 20203 } 20204 } 20205 20206 var _options = { 20207 target: this, 20208 startValue: options.from, 20209 endValue: to, 20210 byValue: options.by, 20211 easing: options.easing, 20212 duration: options.duration, 20213 abort: 20214 options.abort && 20215 function (value, valueProgress, timeProgress) { 20216 return options.abort.call( 20217 _this, 20218 value, 20219 valueProgress, 20220 timeProgress, 20221 ); 20222 }, 20223 onChange: function (value, valueProgress, timeProgress) { 20224 if (propPair) { 20225 _this[propPair[0]][propPair[1]] = value; 20226 } else { 20227 _this.set(property, value); 20228 } 20229 if (skipCallbacks) { 20230 return; 20231 } 20232 options.onChange && 20233 options.onChange(value, valueProgress, timeProgress); 20234 }, 20235 onComplete: function (value, valueProgress, timeProgress) { 20236 if (skipCallbacks) { 20237 return; 20238 } 20239 20240 _this.setCoords(); 20241 options.onComplete && 20242 options.onComplete(value, valueProgress, timeProgress); 20243 }, 20244 }; 20245 20246 if (propIsColor) { 20247 return fabric.util.animateColor( 20248 _options.startValue, 20249 _options.endValue, 20250 _options.duration, 20251 _options, 20252 ); 20253 } else { 20254 return fabric.util.animate(_options); 20255 } 20256 }, 20257 }, 20258); 20259 20260(function (global) { 20261 "use strict"; 20262 20263 var fabric = global.fabric || (global.fabric = {}), 20264 extend = fabric.util.object.extend, 20265 clone = fabric.util.object.clone, 20266 coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }; 20267 20268 if (fabric.Line) { 20269 fabric.warn("fabric.Line is already defined"); 20270 return; 20271 } 20272 20273 /** 20274 * Line class 20275 * @class fabric.Line 20276 * @extends fabric.Object 20277 * @see {@link fabric.Line#initialize}
20277 for constructor definition 20278 */ 20279 fabric.Line = fabric.util.createClass( 20280 fabric.Object, 20281 /** @lends fabric.Line.prototype */ { 20282 /** 20283 * Type of an object 20284 * @type String 20285 * @default 20286 */ 20287 type: "line", 20288 20289 /** 20290 * x value or first line edge 20291 * @type Number 20292 * @default 20293 */ 20294 x1: 0, 20295 20296 /** 20297 * y value or first line edge 20298 * @type Number 20299 * @default 20300 */ 20301 y1: 0, 20302 20303 /** 20304 * x value or second line edge 20305 * @type Number 20306 * @default 20307 */ 20308 x2: 0, 20309 20310 /** 20311 * y value or second line edge 20312 * @type Number 20313 * @default 20314 */ 20315 y2: 0, 20316 20317 cacheProperties: fabric.Object.prototype.cacheProperties.concat( 20318 "x1", 20319 "x2", 20320 "y1", 20321 "y2", 20322 ), 20323 20324 /** 20325 * Constructor 20326 * @param {Array} [points] Array of points 20327 * @param {Object} [options] Options object 20328 * @return {fabric.Line} thisArg 20329 */ 20330 initialize: function (points, options) { 20331 if (!points) { 20332 points = [0, 0, 0, 0]; 20333 } 20334 20335 this.callSuper("initialize", options); 20336 20337 this.set("x1", points[0]); 20338 this.set("y1", points[1]); 20339 this.set("x2", points[2]); 20340 this.set("y2", points[3]); 20341 20342 this._setWidthHeight(options); 20343 }, 20344 20345 /** 20346 * @private 20347 * @param {Object} [options] Options 20348 */ 20349 _setWidthHeight: function (options) { 20350 options || (options = {}); 20351 20352 this.width = Math.abs(this.x2 - this.x1); 20353 this.height = Math.abs(this.y2 - this.y1); 20354 20355 this.left = "left" in options ? options.left : this._getLeftToOriginX(); 20356 20357 this.top = "top" in options ? options.top : this._getTopToOriginY(); 20358 }, 20359 20360 /** 20361 * @private 20362 * @param {String} key 20363 * @param {*} value 20364 */ 20365 _set: function (key, value) { 20366 this.callSuper("_set", key, value); 20367 if (typeof coordProps[key] !== "undefined") { 20368 this._setWidthHeight(); 20369 } 20370 return this; 20371 }, 20372 20373 /** 20374 * @private 20375 * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. 20376 */ 20377 _getLeftToOriginX: makeEdgeToOriginGetter( 20378 { 20379 // property names 20380 origin: "originX", 20381 axis1: "x1", 20382 axis2: "x2", 20383 dimension: "width", 20384 }, 20385 { 20386 // possible values of origin 20387 nearest: "left", 20388 center: "center", 20389 farthest: "right", 20390 }, 20391 ), 20392 20393 /** 20394 * @private 20395 * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. 20396 */ 20397 _getTopToOriginY: makeEdgeToOriginGetter( 20398 { 20399 // property names 20400 origin: "originY", 20401 axis1: "y1", 20402 axis2: "y2", 20403 dimension: "height", 20404 }, 20405 { 20406 // possible values of origin 20407 nearest: "top", 20408 center: "center", 20409 farthest: "bottom", 20410 }, 20411 ), 20412 20413 /** 20414 * @private 20415 * @param {CanvasRenderingContext2D} ctx Context to render on 20416 */ 20417 _render: function (ctx) { 20418 ctx.beginPath(); 20419 20420 var p = this.calcLinePoints(); 20421 ctx.moveTo(p.x1, p.y1); 20422 ctx.lineTo(p.x2, p.y2); 20423 20424 ctx.lineWidth = this.strokeWidth; 20425 20426 // TODO: test this 20427 // make sure setting "fill" changes color of a line 20428 // (by copying fillStyle to strokeStyle, since line is stroked, not filled) 20429 var origStrokeStyle = ctx.strokeStyle; 20430 ctx.strokeStyle = this.stroke || ctx.fillStyle; 20431 this.stroke && this._renderStroke(ctx); 20432 ctx.strokeStyle = origStrokeStyle; 20433 }, 20434 20435 /** 20436 * This function is an helper for svg import. it returns the center of the object in the svg 20437 * untransformed coordinates 20438 * @private 20439 * @return {Object} center point from element coordinates 20440 */ 20441 _findCenterFromElement: function () { 20442 return { 20443 x: (this.x1 + this.x2) / 2, 20444 y: (this.y1 + this.y2) / 2, 20445 }; 20446 }, 20447 20448 /** 20449 * Returns object representation of an instance 20450 * @method toObject 20451 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 20452 * @return {Object} object representation of an instance 20453 */ 20454 toObject: function (propertiesToInclude) { 20455 return extend( 20456 this.callSuper("toObject", propertiesToInclude), 20457 this.calcLinePoints(), 20458 ); 20459 }, 20460 20461 /* 20462 * Calculate object dimensions from its properties 20463 * @private 20464 */ 20465 _getNonTransformedDimensions: function () { 20466 var dim = this.callSuper("_getNonTransformedDimensions"); 20467 if (this.strokeLineCap === "butt") { 20468 if (this.width === 0) { 20469 dim.y -= this.strokeWidth; 20470 } 20471 if (this.height === 0) { 20472 dim.x -= this.strokeWidth; 20473 } 20474 } 20475 return dim; 20476 }, 20477 20478 /**
20479 * Recalculates line points given width and height 20480 * @private 20481 */ 20482 calcLinePoints: function () { 20483 var xMult = this.x1 <= this.x2 ? -1 : 1, 20484 yMult = this.y1 <= this.y2 ? -1 : 1, 20485 x1 = xMult * this.width * 0.5, 20486 y1 = yMult * this.height * 0.5, 20487 x2 = xMult * this.width * -0.5, 20488 y2 = yMult * this.height * -0.5; 20489 20490 return { 20491 x1: x1, 20492 x2: x2, 20493 y1: y1, 20494 y2: y2, 20495 }; 20496 }, 20497 20498 /* _TO_SVG_START_ */ 20499 /** 20500 * Returns svg representation of an instance 20501 * @return {Array} an array of strings with the specific svg representation 20502 * of the instance 20503 */ 20504 _toSVG: function () { 20505 var p = this.calcLinePoints(); 20506 return [ 20507 "<line ", 20508 "COMMON_PARTS", 20509 'x1="', 20510 p.x1, 20511 '" y1="', 20512 p.y1, 20513 '" x2="', 20514 p.x2, 20515 '" y2="', 20516 p.y2, 20517 '" />\n', 20518 ]; 20519 }, 20520 /* _TO_SVG_END_ */ 20521 }, 20522 ); 20523 20524 /* _FROM_SVG_START_ */ 20525 /** 20526 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) 20527 * @static 20528 * @memberOf fabric.Line 20529 * @see http://www.w3.org/TR/SVG/shapes.html#LineElement 20530 */ 20531 fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( 20532 "x1 y1 x2 y2".split(" "), 20533 ); 20534 20535 /** 20536 * Returns fabric.Line instance from an SVG element 20537 * @static 20538 * @memberOf fabric.Line 20539 * @param {SVGElement} element Element to parse 20540 * @param {Object} [options] Options object 20541 * @param {Function} [callback] callback function invoked after parsing 20542 */ 20543 fabric.Line.fromElement = function (element, callback, options) { 20544 options = options || {}; 20545 var parsedAttributes = fabric.parseAttributes( 20546 element, 20547 fabric.Line.ATTRIBUTE_NAMES, 20548 ), 20549 points = [ 20550 parsedAttributes.x1 || 0, 20551 parsedAttributes.y1 || 0, 20552 parsedAttributes.x2 || 0, 20553 parsedAttributes.y2 || 0, 20554 ]; 20555 callback(new fabric.Line(points, extend(parsedAttributes, options))); 20556 }; 20557 /* _FROM_SVG_END_ */ 20558 20559 /** 20560 * Returns fabric.Line instance from an object representation 20561 * @static 20562 * @memberOf fabric.Line 20563 * @param {Object} object Object to create an instance from 20564 * @param {function} [callback] invoked with new instance as first argument 20565 */ 20566 fabric.Line.fromObject = function (object, callback) { 20567 function _callback(instance) { 20568 delete instance.points; 20569 callback && callback(instance); 20570 } 20571 var options = clone(object, true); 20572 options.points = [object.x1, object.y1, object.x2, object.y2]; 20573 fabric.Object._fromObject("Line", options, _callback, "points"); 20574 }; 20575 20576 /** 20577 * Produces a function that calculates distance from canvas edge to Line origin. 20578 */ 20579 function makeEdgeToOriginGetter(propertyNames, originValues) { 20580 var origin = propertyNames.origin, 20581 axis1 = propertyNames.axis1, 20582 axis2 = propertyNames.axis2, 20583 dimension = propertyNames.dimension, 20584 nearest = originValues.nearest, 20585 center = originValues.center, 20586 farthest = originValues.farthest; 20587 20588 return function () { 20589 switch (this.get(origin)) { 20590 case nearest: 20591 return Math.min(this.get(axis1), this.get(axis2)); 20592 case center: 20593 return ( 20594 Math.min(this.get(axis1), this.get(axis2)) + 20595 0.5 * this.get(dimension) 20596 ); 20597 case farthest: 20598 return Math.max(this.get(axis1), this.get(axis2)); 20599 } 20600 }; 20601 } 20602})(typeof exports !== "undefined" ? exports : this); 20603 20604(function (global) { 20605 "use strict"; 20606 20607 var fabric = global.fabric || (global.fabric = {}), 20608 degreesToRadians = fabric.util.degreesToRadians; 20609 20610 if (fabric.Circle) { 20611 fabric.warn("fabric.Circle is already defined."); 20612 return; 20613 } 20614 20615 /** 20616 * Circle class 20617 * @class fabric.Circle 20618 * @extends fabric.Object 20619 * @see {@link fabric.Circle#initialize} for constructor definition 20620 */ 20621 fabric.Circle = fabric.util.createClass( 20622 fabric.Object, 20623 /** @lends fabric.Circle.prototype */ { 20624 /** 20625 * Type of an object
20626 * @type String 20627 * @default 20628 */ 20629 type: "circle", 20630 20631 /** 20632 * Radius of this circle 20633 * @type Number 20634 * @default 20635 */ 20636 radius: 0, 20637 20638 /** 20639 * degrees of start of the circle. 20640 * probably will change to degrees in next major version 20641 * @type Number 0 - 359 20642 * @default 0 20643 */ 20644 startAngle: 0, 20645 20646 /** 20647 * End angle of the circle 20648 * probably will change to degrees in next major version 20649 * @type Number 1 - 360 20650 * @default 360 20651 */ 20652 endAngle: 360, 20653 20654 cacheProperties: fabric.Object.prototype.cacheProperties.concat( 20655 "radius", 20656 "startAngle", 20657 "endAngle", 20658 ), 20659 20660 /** 20661 * @private 20662 * @param {String} key 20663 * @param {*} value 20664 * @return {fabric.Circle} thisArg 20665 */ 20666 _set: function (key, value) { 20667 this.callSuper("_set", key, value); 20668 20669 if (key === "radius") { 20670 this.setRadius(value); 20671 } 20672 20673 return this; 20674 }, 20675 20676 /** 20677 * Returns object representation of an instance 20678 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 20679 * @return {Object} object representation of an instance 20680 */ 20681 toObject: function (propertiesToInclude) { 20682 return this.callSuper( 20683 "toObject", 20684 ["radius", "startAngle", "endAngle"].concat(propertiesToInclude), 20685 ); 20686 }, 20687 20688 /* _TO_SVG_START_ */ 20689 20690 /** 20691 * Returns svg representation of an instance 20692 * @return {Array} an array of strings with the specific svg representation 20693 * of the instance 20694 */ 20695 _toSVG: function () { 20696 var svgString, 20697 x = 0, 20698 y = 0, 20699 angle = (this.endAngle - this.startAngle) % 360; 20700 20701 if (angle === 0) { 20702 svgString = [ 20703 "<circle ", 20704 "COMMON_PARTS", 20705 'cx="' + x + '" cy="' + y + '" ', 20706 'r="', 20707 this.radius, 20708 '" />\n', 20709 ]; 20710 } else { 20711 var start = degreesToRadians(this.startAngle), 20712 end = degreesToRadians(this.endAngle), 20713 radius = this.radius, 20714 startX = fabric.util.cos(start) * radius, 20715 startY = fabric.util.sin(start) * radius, 20716 endX = fabric.util.cos(end) * radius, 20717 endY = fabric.util.sin(end) * radius, 20718 largeFlag = angle > 180 ? "1" : "0"; 20719 svgString = [ 20720 '<path d="M ' + startX + " " + startY, 20721 " A " + radius + " " + radius, 20722 " 0 ", 20723 +largeFlag + " 1", 20724 " " + endX + " " + endY, 20725 '" ', 20726 "COMMON_PARTS", 20727 " />\n", 20728 ]; 20729 } 20730 return svgString; 20731 }, 20732 /* _TO_SVG_END_ */ 20733 20734 /** 20735 * @private 20736 * @param {CanvasRenderingContext2D} ctx context to render on 20737 */ 20738 _render: function (ctx) { 20739 ctx.beginPath(); 20740 ctx.arc( 20741 0, 20742 0, 20743 this.radius, 20744 degreesToRadians(this.startAngle), 20745 degreesToRadians(this.endAngle), 20746 false, 20747 ); 20748 this._renderPaintInOrder(ctx); 20749 }, 20750 20751 /** 20752 * Returns horizontal radius of an object (according to how an object is scaled) 20753 * @return {Number} 20754 */ 20755 getRadiusX: function () { 20756 return this.get("radius") * this.get("scaleX"); 20757 }, 20758 20759 /** 20760 * Returns vertical radius of an object (according to how an object is scaled) 20761 * @return {Number} 20762 */ 20763 getRadiusY: function () { 20764 return this.get("radius") * this.get("scaleY"); 20765 }, 20766 20767 /** 20768 * Sets radius of an object (and updates width accordingly) 20769 * @return {fabric.Circle} thisArg 20770 */ 20771 setRadius: function (value) { 20772 this.radius = value; 20773 return this.set("width", value * 2).set("height", value * 2); 20774 }, 20775 }, 20776 ); 20777 20778 /* _FROM_SVG_START_ */ 20779 /** 20780 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) 20781 * @static 20782 * @memberOf fabric.Circle
20783 * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement 20784 */ 20785 fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( 20786 "cx cy r".split(" "), 20787 ); 20788 20789 /** 20790 * Returns {@link fabric.Circle} instance from an SVG element 20791 * @static 20792 * @memberOf fabric.Circle 20793 * @param {SVGElement} element Element to parse 20794 * @param {Function} [callback] Options callback invoked after parsing is finished 20795 * @param {Object} [options] Options object 20796 * @throws {Error} If value of `r` attribute is missing or invalid 20797 */ 20798 fabric.Circle.fromElement = function (element, callback) { 20799 var parsedAttributes = fabric.parseAttributes( 20800 element, 20801 fabric.Circle.ATTRIBUTE_NAMES, 20802 ); 20803 20804 if (!isValidRadius(parsedAttributes)) { 20805 throw new Error( 20806 "value of `r` attribute is required and can not be negative", 20807 ); 20808 } 20809 20810 parsedAttributes.left = 20811 (parsedAttributes.left || 0) - parsedAttributes.radius; 20812 parsedAttributes.top = 20813 (parsedAttributes.top || 0) - parsedAttributes.radius; 20814 callback(new fabric.Circle(parsedAttributes)); 20815 }; 20816 20817 /** 20818 * @private 20819 */ 20820 function isValidRadius(attributes) { 20821 return "radius" in attributes && attributes.radius >= 0; 20822 } 20823 /* _FROM_SVG_END_ */ 20824 20825 /** 20826 * Returns {@link fabric.Circle} instance from an object representation 20827 * @static 20828 * @memberOf fabric.Circle 20829 * @param {Object} object Object to create an instance from 20830 * @param {function} [callback] invoked with new instance as first argument 20831 * @return {void} 20832 */ 20833 fabric.Circle.fromObject = function (object, callback) { 20834 fabric.Object._fromObject("Circle", object, callback); 20835 }; 20836})(typeof exports !== "undefined" ? exports : this); 20837 20838(function (global) { 20839 "use strict"; 20840 20841 var fabric = global.fabric || (global.fabric = {}); 20842 20843 if (fabric.Triangle) { 20844 fabric.warn("fabric.Triangle is already defined"); 20845 return; 20846 } 20847 20848 /** 20849 * Triangle class 20850 * @class fabric.Triangle 20851 * @extends fabric.Object 20852 * @return {fabric.Triangle} thisArg 20853 * @see {@link fabric.Triangle#initialize} for constructor definition 20854 */ 20855 fabric.Triangle = fabric.util.createClass( 20856 fabric.Object, 20857 /** @lends fabric.Triangle.prototype */ { 20858 /** 20859 * Type of an object
20860 * @type String 20861 * @default 20862 */ 20863 type: "triangle", 20864 20865 /** 20866 * Width is set to 100 to compensate the old initialize code that was setting it to 100 20867 * @type Number 20868 * @default 20869 */ 20870 width: 100, 20871 20872 /** 20873 * Height is set to 100 to compensate the old initialize code that was setting it to 100 20874 * @type Number 20875 * @default 20876 */ 20877 height: 100, 20878 20879 /** 20880 * @private 20881 * @param {CanvasRenderingContext2D} ctx Context to render on 20882 */ 20883 _render: function (ctx) { 20884 var widthBy2 = this.width / 2, 20885 heightBy2 = this.height / 2; 20886 20887 ctx.beginPath(); 20888 ctx.moveTo(-widthBy2, heightBy2); 20889 ctx.lineTo(0, -heightBy2); 20890 ctx.lineTo(widthBy2, heightBy2); 20891 ctx.closePath(); 20892 20893 this._renderPaintInOrder(ctx); 20894 }, 20895 20896 /* _TO_SVG_START_ */ 20897 /** 20898 * Returns svg representation of an instance 20899 * @return {Array} an array of strings with the specific svg representation 20900 * of the instance 20901 */ 20902 _toSVG: function () { 20903 var widthBy2 = this.width / 2, 20904 heightBy2 = this.height / 2, 20905 points = [ 20906 -widthBy2 + " " + heightBy2, 20907 "0 " + -heightBy2, 20908 widthBy2 + " " + heightBy2, 20909 ].join(","); 20910 return ["<polygon ", "COMMON_PARTS", 'points="', points, '" />']; 20911 }, 20912 /* _TO_SVG_END_ */ 20913 }, 20914 ); 20915 20916 /** 20917 * Returns {@link fabric.Triangle} instance from an object representation 20918 * @static 20919 * @memberOf fabric.Triangle 20920 * @param {Object} object Object to create an instance from 20921 * @param {function} [callback] invoked with new instance as first argument 20922 */ 20923 fabric.Triangle.fromObject = function (object, callback) { 20924 return fabric.Object._fromObject("Triangle", object, callback); 20925 }; 20926})(typeof exports !== "undefined" ? exports : this); 20927 20928(function (global) { 20929 "use strict"; 20930 20931 var fabric = global.fabric || (global.fabric = {}), 20932 piBy2 = Math.PI * 2; 20933 20934 if (fabric.Ellipse) { 20935 fabric.warn("fabric.Ellipse is already defined."); 20936 return; 20937 } 20938 20939 /** 20940 * Ellipse class 20941 * @class fabric.Ellipse 20942 * @extends fabric.Object 20943 * @return {fabric.Ellipse} thisArg 20944 * @see {@link fabric.Ellipse#initialize} for constructor definition 20945 */ 20946 fabric.Ellipse = fabric.util.createClass( 20947 fabric.Object, 20948 /** @lends fabric.Ellipse.prototype */ { 20949 /** 20950 * Type of an object
20951 * @type String 20952 * @default 20953 */ 20954 type: "ellipse", 20955 20956 /** 20957 * Horizontal radius 20958 * @type Number 20959 * @default 20960 */ 20961 rx: 0, 20962 20963 /** 20964 * Vertical radius 20965 * @type Number 20966 * @default 20967 */ 20968 ry: 0, 20969 20970 cacheProperties: fabric.Object.prototype.cacheProperties.concat( 20971 "rx", 20972 "ry", 20973 ), 20974 20975 /** 20976 * Constructor 20977 * @param {Object} [options] Options object 20978 * @return {fabric.Ellipse} thisArg 20979 */ 20980 initialize: function (options) { 20981 this.callSuper("initialize", options); 20982 this.set("rx", (options && options.rx) || 0); 20983 this.set("ry", (options && options.ry) || 0); 20984 }, 20985 20986 /** 20987 * @private 20988 * @param {String} key 20989 * @param {*} value 20990 * @return {fabric.Ellipse} thisArg 20991 */ 20992 _set: function (key, value) { 20993 this.callSuper("_set", key, value); 20994 switch (key) { 20995 case "rx": 20996 this.rx = value; 20997 this.set("width", value * 2); 20998 break; 20999 21000 case "ry": 21001 this.ry = value; 21002 this.set("height", value * 2); 21003 break; 21004 } 21005 return this; 21006 }, 21007 21008 /** 21009 * Returns horizontal radius of an object (according to how an object is scaled) 21010 * @return {Number} 21011 */ 21012 getRx: function () { 21013 return this.get("rx") * this.get("scaleX"); 21014 }, 21015 21016 /** 21017 * Returns Vertical radius of an object (according to how an object is scaled) 21018 * @return {Number} 21019 */ 21020 getRy: function () { 21021 return this.get("ry") * this.get("scaleY"); 21022 }, 21023 21024 /** 21025 * Returns object representation of an instance 21026 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 21027 * @return {Object} object representation of an instance 21028 */ 21029 toObject: function (propertiesToInclude) { 21030 return this.callSuper( 21031 "toObject", 21032 ["rx", "ry"].concat(propertiesToInclude), 21033 ); 21034 }, 21035 21036 /* _TO_SVG_START_ */ 21037 /** 21038 * Returns svg representation of an instance 21039 * @return {Array} an array of strings with the specific svg representation 21040 * of the instance 21041 */ 21042 _toSVG: function () { 21043 return [ 21044 "<ellipse ", 21045 "COMMON_PARTS", 21046 'cx="0" cy="0" ', 21047 'rx="', 21048 this.rx, 21049 '" ry="', 21050 this.ry, 21051 '" />\n', 21052 ]; 21053 }, 21054 /* _TO_SVG_END_ */ 21055 21056 /** 21057 * @private 21058 * @param {CanvasRenderingContext2D} ctx context to render on 21059 */ 21060 _render: function (ctx) { 21061 ctx.beginPath(); 21062 ctx.save(); 21063 ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0); 21064 ctx.arc(0, 0, this.rx, 0, piBy2, false); 21065 ctx.restore(); 21066 this._renderPaintInOrder(ctx); 21067 }, 21068 }, 21069 ); 21070 21071 /* _FROM_SVG_START_ */ 21072 /** 21073 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) 21074 * @static 21075 * @memberOf fabric.Ellipse 21076 * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement 21077 */ 21078 fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( 21079 "cx cy rx ry".split(" "), 21080 ); 21081 21082 /** 21083 * Returns {@link fabric.Ellipse} instance from an SVG element 21084 * @static 21085 * @memberOf fabric.Ellipse 21086 * @param {SVGElement} element Element to parse 21087 * @param {Function} [callback] Options callback invoked after parsing is finished 21088 * @return {fabric.Ellipse} 21089 */ 21090 fabric.Ellipse.fromElement = function (element, callback) { 21091 var parsedAttributes = fabric.parseAttributes( 21092 element, 21093 fabric.Ellipse.ATTRIBUTE_NAMES, 21094 ); 21095 21096 parsedAttributes.left = (parsedAttributes.left || 0) - parsedAttributes.rx; 21097 parsedAttributes.top = (parsedAttributes.top || 0) - parsedAttributes.ry; 21098 callback(new fabric.Ellipse(parsedAttributes)); 21099 }; 21100 /* _FROM_SVG_END_ */ 21101 21102 /** 21103 * Returns {@link fabric.Ellipse} instance from an object representation 21104 * @static 21105 * @memberOf fabric.Ellipse 21106 * @param {Object} object Object to create an instance from 21107 * @param {function}
21107 [callback] invoked with new instance as first argument 21108 * @return {void} 21109 */ 21110 fabric.Ellipse.fromObject = function (object, callback) { 21111 fabric.Object._fromObject("Ellipse", object, callback); 21112 }; 21113})(typeof exports !== "undefined" ? exports : this); 21114 21115(function (global) { 21116 "use strict"; 21117 21118 var fabric = global.fabric || (global.fabric = {}), 21119 extend = fabric.util.object.extend; 21120 21121 if (fabric.Rect) { 21122 fabric.warn("fabric.Rect is already defined"); 21123 return; 21124 } 21125 21126 /** 21127 * Rectangle class 21128 * @class fabric.Rect 21129 * @extends fabric.Object 21130 * @return {fabric.Rect} thisArg 21131 * @see {@link fabric.Rect#initialize} for constructor definition 21132 */ 21133 fabric.Rect = fabric.util.createClass( 21134 fabric.Object, 21135 /** @lends fabric.Rect.prototype */ { 21136 /** 21137 * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) 21138 * as well as for history (undo/redo) purposes 21139 * @type Array 21140 */ 21141 stateProperties: fabric.Object.prototype.stateProperties.concat( 21142 "rx", 21143 "ry", 21144 ), 21145 21146 /** 21147 * Type of an object
21148 * @type String 21149 * @default 21150 */ 21151 type: "rect", 21152 21153 /** 21154 * Horizontal border radius 21155 * @type Number 21156 * @default 21157 */ 21158 rx: 0, 21159 21160 /** 21161 * Vertical border radius 21162 * @type Number 21163 * @default 21164 */ 21165 ry: 0, 21166 21167 cacheProperties: fabric.Object.prototype.cacheProperties.concat( 21168 "rx", 21169 "ry", 21170 ), 21171 21172 /** 21173 * Constructor 21174 * @param {Object} [options] Options object 21175 * @return {Object} thisArg 21176 */ 21177 initialize: function (options) { 21178 this.callSuper("initialize", options); 21179 this._initRxRy(); 21180 }, 21181 21182 /** 21183 * Initializes rx/ry attributes 21184 * @private 21185 */ 21186 _initRxRy: function () { 21187 if (this.rx && !this.ry) { 21188 this.ry = this.rx; 21189 } else if (this.ry && !this.rx) { 21190 this.rx = this.ry; 21191 } 21192 }, 21193 21194 /** 21195 * @private 21196 * @param {CanvasRenderingContext2D} ctx Context to render on 21197 */ 21198 _render: function (ctx) { 21199 // 1x1 case (used in spray brush) optimization was removed because 21200 // with caching and higher zoom level this makes more damage than help 21201 21202 var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, 21203 ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, 21204 w = this.width, 21205 h = this.height, 21206 x = -this.width / 2, 21207 y = -this.height / 2, 21208 isRounded = rx !== 0 || ry !== 0, 21209 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */ 21210 k = 1 - 0.5522847498; 21211 ctx.beginPath(); 21212 21213 ctx.moveTo(x + rx, y); 21214 21215 ctx.lineTo(x + w - rx, y); 21216 isRounded && 21217 ctx.bezierCurveTo( 21218 x + w - k * rx, 21219 y, 21220 x + w, 21221 y + k * ry, 21222 x + w, 21223 y + ry, 21224 ); 21225 21226 ctx.lineTo(x + w, y + h - ry); 21227 isRounded && 21228 ctx.bezierCurveTo( 21229 x + w, 21230 y + h - k * ry, 21231 x + w - k * rx, 21232 y + h, 21233 x + w - rx, 21234 y + h, 21235 ); 21236 21237 ctx.lineTo(x + rx, y + h); 21238 isRounded && 21239 ctx.bezierCurveTo( 21240 x + k * rx, 21241 y + h, 21242 x, 21243 y + h - k * ry, 21244 x, 21245 y + h - ry, 21246 ); 21247 21248 ctx.lineTo(x, y + ry); 21249 isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); 21250 21251 ctx.closePath(); 21252 21253 this._renderPaintInOrder(ctx); 21254 }, 21255 21256 /** 21257 * Returns object representation of an instance 21258 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 21259 * @return {Object} object representation of an instance 21260 */ 21261 toObject: function (propertiesToInclude) { 21262 return this.callSuper( 21263 "toObject", 21264 ["rx", "ry"].concat(propertiesToInclude), 21265 ); 21266 }, 21267 21268 /* _TO_SVG_START_ */ 21269 /** 21270 * Returns svg representation of an instance 21271 * @return {Array} an array of strings with the specific svg representation 21272 * of the instance 21273 */ 21274 _toSVG: function () { 21275 var x = -this.width / 2, 21276 y = -this.height / 2; 21277 return [ 21278 "<rect ", 21279 "COMMON_PARTS", 21280 'x="', 21281 x, 21282 '" y="', 21283 y, 21284 '" rx="', 21285 this.rx, 21286 '" ry="', 21287 this.ry, 21288 '" width="', 21289 this.width, 21290 '" height="', 21291 this.height, 21292 '" />\n', 21293 ]; 21294 }, 21295 /* _TO_SVG_END_ */ 21296 }, 21297 ); 21298 21299 /* _FROM_SVG_START_ */ 21300 /** 21301 * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) 21302 * @static 21303 * @memberOf fabric.Rect 21304 * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement 21305 */ 21306 fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( 21307 "x y rx ry width height".split(" "), 21308 ); 21309 21310 /** 21311 * Returns {@link fabric.Rect} instance from an SVG element 21312 * @static 21313 * @memberOf fabric.Rect 21314 * @param {SVGElement} element Element to parse 21315 * @param {Function} callback callback function invoked after parsing 21316 * @param {Object} [options] Options object 21317 */ 21318 fabric.Rect.fromElement = function (element, callback, options) { 21319 if (!element) { 21320 return callback(null); 21321 } 21322 options = options || {}; 21323 21324 var parsedAttributes = fabric.parseAttributes( 21325 element, 21326 fabric.Rect.ATTRIBUTE_NAMES, 21327 ); 21328 parsedAttributes.left = parsedAttributes.left || 0; 21329 parsedAttributes.top = parsedAttributes.top || 0; 21330 parsedAttributes.height = parsedAttributes.height || 0; 21331 parsedAttributes.width = parsedAttributes.width || 0; 21332 var rect = new fabric.Rect( 21333 extend( 21334 options ? fabric.util.object.clone(options) : {}, 21335 parsedAttributes, 21336 ), 21337 ); 21338 rect.visible = rect.visible && rect.width > 0 && rect.height > 0; 21339 callback(rect); 21340 }; 21341 /* _FROM_SVG_END_ */ 21342 21343 /** 21344 * Returns {@link fabric.Rect} instance from an object representation 21345 * @static 21346 * @memberOf fabric.Rect 21347 * @param {Object} object Object to create an instance from 21348 * @param {Function} [callback] Callback to invoke when an fabric.Rect instance is created 21349 */ 21350 fabric.Rect.fromObject = function (object, callback) { 21351 return fabric.Object._fromObject("Rect", object, callback); 21352 }; 21353})(typeof exports !== "undefined" ? exports : this); 21354 21355(function (global) { 21356 "use strict"; 21357 21358 var fabric = global.fabric || (global.fabric = {}), 21359 extend = fabric.util.object.extend, 21360 min = fabric.util.array.min, 21361 max = fabric.util.array.max, 21362 toFixed = fabric.util.toFixed, 21363 projectStrokeOnPoints = fabric.util.projectStrokeOnPoints; 21364 21365 if (fabric.Polyline) { 21366 fabric.warn("fabric.Polyline is already defined"); 21367 return; 21368 } 21369 21370 /** 21371 * Polyline class 21372 * @class fabric.Polyline 21373 * @extends fabric.Object 21374 * @see {@link fabric.Polyline#initialize}
21374 for constructor definition 21375 */ 21376 fabric.Polyline = fabric.util.createClass( 21377 fabric.Object, 21378 /** @lends fabric.Polyline.prototype */ { 21379 /** 21380 * Type of an object 21381 * @type String 21382 * @default 21383 */ 21384 type: "polyline", 21385 21386 /** 21387 * Points array 21388 * @type Array 21389 * @default 21390 */ 21391 points: null, 21392 21393 /** 21394 * WARNING: Feature in progress 21395 * Calculate the exact bounding box taking in account strokeWidth on acute angles 21396 * this will be turned to true by default on fabric 6.0 21397 * maybe will be left in as an optimization since calculations may be slow 21398 * @deprecated 21399 * @type Boolean 21400 * @default false 21401 */ 21402 exactBoundingBox: false, 21403 21404 cacheProperties: fabric.Object.prototype.cacheProperties.concat("points"), 21405 21406 /** 21407 * Constructor 21408 * @param {Array} points Array of points (where each point is an object with x and y) 21409 * @param {Object} [options] Options object 21410 * @return {fabric.Polyline} thisArg 21411 * @example 21412 * var poly = new fabric.Polyline([ 21413 * { x: 10, y: 10 }, 21414 * { x: 50, y: 30 }, 21415 * { x: 40, y: 70 }, 21416 * { x: 60, y: 50 }, 21417 * { x: 100, y: 150 }, 21418 * { x: 40, y: 100 } 21419 * ], { 21420 * stroke: 'red', 21421 * left: 100, 21422 * top: 100 21423 * }); 21424 */ 21425 initialize: function (points, options) { 21426 options = options || {}; 21427 this.points = points || []; 21428 this.callSuper("initialize", options); 21429 this._setPositionDimensions(options); 21430 }, 21431 21432 /** 21433 * @private 21434 */ 21435 _projectStrokeOnPoints: function () { 21436 return projectStrokeOnPoints(this.points, this, true); 21437 }, 21438 21439 _setPositionDimensions: function (options) { 21440 var calcDim = this._calcDimensions(options), 21441 correctLeftTop, 21442 correctSize = this.exactBoundingBox ? this.strokeWidth : 0; 21443 this.width = calcDim.width - correctSize; 21444 this.height = calcDim.height - correctSize; 21445 if (!options.fromSVG) { 21446 correctLeftTop = this.translateToGivenOrigin( 21447 { 21448 // this looks bad, but is one way to keep it optional for now. 21449 x: calcDim.left - this.strokeWidth / 2 + correctSize / 2, 21450 y: calcDim.top - this.strokeWidth / 2 + correctSize / 2, 21451 }, 21452 "left", 21453 "top", 21454 this.originX, 21455 this.originY, 21456 ); 21457 } 21458 if (typeof options.left === "undefined") { 21459 this.left = options.fromSVG ? calcDim.left : correctLeftTop.x; 21460 } 21461 if (typeof options.top === "undefined") { 21462 this.top = options.fromSVG ? calcDim.top : correctLeftTop.y; 21463 } 21464 this.pathOffset = { 21465 x: calcDim.left + this.width / 2 + correctSize / 2, 21466 y: calcDim.top + this.height / 2 + correctSize / 2, 21467 }; 21468 }, 21469 21470 /** 21471 * Calculate the polygon min and max point from points array, 21472 * returning an object with left, top, width, height to measure the 21473 * polygon size 21474 * @return {Object} object.left X coordinate of the polygon leftmost point 21475 * @return {Object} object.top Y coordinate of the polygon topmost point 21476 * @return {Object} object.width distance between X coordinates of the polygon leftmost and rightmost point 21477 * @return {Object} object.height distance between Y coordinates of the polygon topmost and bottommost point 21478 * @private 21479 */ 21480 _calcDimensions: function () { 21481 var points = this.exactBoundingBox 21482 ? this._projectStrokeOnPoints() 21483 : this.points, 21484 minX = min(points, "x") || 0, 21485 minY = min(points, "y") || 0, 21486 maxX = max(points, "x") || 0, 21487 maxY = max(points, "y") || 0, 21488 width = maxX - minX, 21489 height = maxY - minY; 21490 21491 return { 21492 left: minX, 21493 top: minY, 21494 width: width, 21495 height: height, 21496 }; 21497 }, 21498 21499 /** 21500 * Returns object representation of an instance 21501 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 21502 * @return {Object} Object representation of an instance 21503 */ 21504 toObject: function (propertiesToInclude) { 21505 return extend(this.callSuper("toObject", propertiesToInclude), { 21506 points: this.points.concat(), 21507 }); 21508 }, 21509 21510 /* _TO_SVG_START_ */ 21511 /** 21512 * Returns svg representation of an instance 21513 * @return {Array}
21513 an array of strings with the specific svg representation 21514 * of the instance 21515 */ 21516 _toSVG: function () { 21517 var points = [], 21518 diffX = this.pathOffset.x, 21519 diffY = this.pathOffset.y, 21520 NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; 21521 21522 for (var i = 0, len = this.points.length; i < len; i++) { 21523 points.push( 21524 toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS), 21525 ",", 21526 toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), 21527 " ", 21528 ); 21529 } 21530 return [ 21531 "<" + this.type + " ", 21532 "COMMON_PARTS", 21533 'points="', 21534 points.join(""), 21535 '" />\n', 21536 ]; 21537 }, 21538 /* _TO_SVG_END_ */ 21539 21540 /** 21541 * @private 21542 * @param {CanvasRenderingContext2D} ctx Context to render on 21543 */ 21544 commonRender: function (ctx) { 21545 var point, 21546 len = this.points.length, 21547 x = this.pathOffset.x, 21548 y = this.pathOffset.y; 21549 21550 if (!len || isNaN(this.points[len - 1].y)) { 21551 // do not draw if no points or odd points 21552 // NaN comes from parseFloat of a empty string in parser 21553 return false; 21554 } 21555 ctx.beginPath(); 21556 ctx.moveTo(this.points[0].x - x, this.points[0].y - y); 21557 for (var i = 0; i < len; i++) { 21558 point = this.points[i]; 21559 ctx.lineTo(point.x - x, point.y - y); 21560 } 21561 return true; 21562 }, 21563 21564 /** 21565 * @private 21566 * @param {CanvasRenderingContext2D} ctx Context to render on 21567 */ 21568 _render: function (ctx) { 21569 if (!this.commonRender(ctx)) { 21570 return; 21571 } 21572 this._renderPaintInOrder(ctx); 21573 }, 21574 21575 /** 21576 * Returns complexity of an instance 21577 * @return {Number} complexity of this instance 21578 */ 21579 complexity: function () { 21580 return this.get("points").length; 21581 }, 21582 }, 21583 ); 21584 21585 /* _FROM_SVG_START_ */ 21586 /** 21587 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) 21588 * @static 21589 * @memberOf fabric.Polyline 21590 * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement 21591 */ 21592 fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); 21593 21594 /** 21595 * Returns fabric.Polyline instance from an SVG element 21596 * @static 21597 * @memberOf fabric.Polyline 21598 * @param {SVGElement} element Element to parser 21599 * @param {Function} callback callback function invoked after parsing 21600 * @param {Object} [options] Options object 21601 */ 21602 fabric.Polyline.fromElementGenerator = function (_class) { 21603 return function (element, callback, options) { 21604 if (!element) { 21605 return callback(null); 21606 } 21607 options || (options = {}); 21608 21609 var points = fabric.parsePointsAttribute(element.getAttribute("points")), 21610 parsedAttributes = fabric.parseAttributes( 21611 element, 21612 fabric[_class].ATTRIBUTE_NAMES, 21613 ); 21614 parsedAttributes.fromSVG = true; 21615 callback(new fabric[_class](points, extend(parsedAttributes, options))); 21616 }; 21617 }; 21618 21619 fabric.Polyline.fromElement = 21620 fabric.Polyline.fromElementGenerator("Polyline"); 21621 21622 /* _FROM_SVG_END_ */ 21623 21624 /** 21625 * Returns fabric.Polyline instance from an object representation 21626 * @static 21627 * @memberOf fabric.Polyline 21628 * @param {Object} object Object to create an instance from 21629 * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created 21630 */ 21631 fabric.Polyline.fromObject = function (object, callback) { 21632 return fabric.Object._fromObject("Polyline", object, callback, "points"); 21633 }; 21634})(typeof exports !== "undefined" ? exports : this); 21635 21636(function (global) { 21637 "use strict"; 21638 21639 var fabric = global.fabric || (global.fabric = {}), 21640 projectStrokeOnPoints = fabric.util.projectStrokeOnPoints; 21641 21642 if (fabric.Polygon) { 21643 fabric.warn("fabric.Polygon is already defined"); 21644 return; 21645 } 21646 21647 /** 21648 * Polygon class 21649 * @class fabric.Polygon 21650 * @extends fabric.Polyline 21651 * @see {@link fabric.Polygon#initialize} for constructor definition 21652 */ 21653 fabric.Polygon = fabric.util.createClass( 21654 fabric.Polyline, 21655 /** @lends fabric.Polygon.prototype */ { 21656 /** 21657 * Type of an object
21658 * @type String 21659 * @default 21660 */ 21661 type: "polygon", 21662 21663 /** 21664 * @private 21665 */ 21666 _projectStrokeOnPoints: function () { 21667 return projectStrokeOnPoints(this.points, this); 21668 }, 21669 21670 /** 21671 * @private 21672 * @param {CanvasRenderingContext2D} ctx Context to render on 21673 */ 21674 _render: function (ctx) { 21675 if (!this.commonRender(ctx)) { 21676 return; 21677 } 21678 ctx.closePath(); 21679 this._renderPaintInOrder(ctx); 21680 }, 21681 }, 21682 ); 21683 21684 /* _FROM_SVG_START_ */ 21685 /** 21686 * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) 21687 * @static 21688 * @memberOf fabric.Polygon 21689 * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement 21690 */ 21691 fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); 21692 21693 /** 21694 * Returns {@link fabric.Polygon} instance from an SVG element 21695 * @static 21696 * @memberOf fabric.Polygon 21697 * @param {SVGElement} element Element to parse 21698 * @param {Function} callback callback function invoked after parsing 21699 * @param {Object} [options] Options object 21700 */ 21701 fabric.Polygon.fromElement = fabric.Polyline.fromElementGenerator("Polygon"); 21702 /* _FROM_SVG_END_ */ 21703 21704 /** 21705 * Returns fabric.Polygon instance from an object representation 21706 * @static 21707 * @memberOf fabric.Polygon 21708 * @param {Object} object Object to create an instance from 21709 * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created 21710 * @return {void} 21711 */ 21712 fabric.Polygon.fromObject = function (object, callback) { 21713 fabric.Object._fromObject("Polygon", object, callback, "points"); 21714 }; 21715})(typeof exports !== "undefined" ? exports : this); 21716 21717(function (global) { 21718 "use strict"; 21719 21720 var fabric = global.fabric || (global.fabric = {}), 21721 min = fabric.util.array.min, 21722 max = fabric.util.array.max, 21723 extend = fabric.util.object.extend, 21724 clone = fabric.util.object.clone, 21725 toFixed = fabric.util.toFixed; 21726 21727 if (fabric.Path) { 21728 fabric.warn("fabric.Path is already defined"); 21729 return; 21730 } 21731 21732 /** 21733 * Path class 21734 * @class fabric.Path 21735 * @extends fabric.Object 21736 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup} 21737 * @see {@link fabric.Path#initialize} for constructor definition 21738 */ 21739 fabric.Path = fabric.util.createClass( 21740 fabric.Object, 21741 /** @lends fabric.Path.prototype */ { 21742 /** 21743 * Type of an object
21744 * @type String 21745 * @default 21746 */ 21747 type: "path", 21748 21749 /** 21750 * Array of path points 21751 * @type Array 21752 * @default 21753 */ 21754 path: null, 21755 21756 cacheProperties: fabric.Object.prototype.cacheProperties.concat( 21757 "path", 21758 "fillRule", 21759 ), 21760 21761 stateProperties: fabric.Object.prototype.stateProperties.concat("path"), 21762 21763 /** 21764 * Constructor 21765 * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) 21766 * @param {Object} [options] Options object 21767 * @return {fabric.Path} thisArg 21768 */ 21769 initialize: function (path, options) { 21770 options = clone(options || {}); 21771 delete options.path; 21772 this.callSuper("initialize", options); 21773 this._setPath(path || [], options); 21774 }, 21775 21776 /** 21777 * @private 21778 * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) 21779 * @param {Object} [options] Options object 21780 */ 21781 _setPath: function (path, options) { 21782 this.path = fabric.util.makePathSimpler( 21783 Array.isArray(path) ? path : fabric.util.parsePath(path), 21784 ); 21785 21786 fabric.Polyline.prototype._setPositionDimensions.call( 21787 this, 21788 options || {}, 21789 ); 21790 }, 21791 21792 /** 21793 * @private 21794 * @param {CanvasRenderingContext2D} ctx context to render path on 21795 */ 21796 _renderPathCommands: function (ctx) { 21797 var current, // current instruction 21798 subpathStartX = 0, 21799 subpathStartY = 0, 21800 x = 0, // current x 21801 y = 0, // current y 21802 controlX = 0, // current control point x 21803 controlY = 0, // current control point y 21804 l = -this.pathOffset.x, 21805 t = -this.pathOffset.y; 21806 21807 ctx.beginPath(); 21808 21809 for (var i = 0, len = this.path.length; i < len; ++i) { 21810 current = this.path[i]; 21811 21812 switch ( 21813 current[0] // first letter 21814 ) { 21815 case "L": // lineto, absolute 21816 x = current[1]; 21817 y = current[2]; 21818 ctx.lineTo(x + l, y + t); 21819 break; 21820 21821 case "M": // moveTo, absolute 21822 x = current[1]; 21823 y = current[2]; 21824 subpathStartX = x; 21825 subpathStartY = y; 21826 ctx.moveTo(x + l, y + t); 21827 break; 21828 21829 case "C": // bezierCurveTo, absolute 21830 x = current[5]; 21831 y = current[6]; 21832 controlX = current[3]; 21833 controlY = current[4]; 21834 ctx.bezierCurveTo( 21835 current[1] + l, 21836 current[2] + t, 21837 controlX + l, 21838 controlY + t, 21839 x + l, 21840 y + t, 21841 ); 21842 break; 21843 21844 case "Q": // quadraticCurveTo, absolute 21845 ctx.quadraticCurveTo( 21846 current[1] + l, 21847 current[2] + t, 21848 current[3] + l, 21849 current[4] + t, 21850 ); 21851 x = current[3]; 21852 y = current[4]; 21853 controlX = current[1]; 21854 controlY = current[2]; 21855 break; 21856 21857 case "z": 21858 case "Z": 21859 x = subpathStartX; 21860 y = subpathStartY; 21861 ctx.closePath(); 21862 break; 21863 } 21864 } 21865 }, 21866 21867 /** 21868 * @private 21869 * @param {CanvasRenderingContext2D} ctx context to render path on 21870 */ 21871 _render: function (ctx) { 21872 this._renderPathCommands(ctx); 21873 this._renderPaintInOrder(ctx); 21874 }, 21875 21876 /** 21877 * Returns string representation of an instance 21878 * @return {String} string representation of an instance 21879 */ 21880 toString: function () { 21881 return ( 21882 "#<fabric.Path (" + 21883 this.complexity() + 21884 '): { "top": ' + 21885 this.top + 21886 ', "left": ' + 21887 this.left + 21888 " }>" 21889 ); 21890 }, 21891 21892 /** 21893 * Returns object representation of an instance 21894 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 21895 * @return {Object} object representation of an instance 21896 */ 21897 toObject: function (propertiesToInclude) { 21898 return extend(this.callSuper("toObject", propertiesToInclude), { 21899 path: this.path.map(function (item) { 21900 return item.slice(); 21901 }), 21902 }); 21903 }, 21904 21905 /** 21906 * Returns dataless object representation of an instance 21907 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 21908 * @return {Object} object representation of an instance 21909 */ 21910 toDatalessObject: function (propertiesToInclude) { 21911 var o = this.toObject(["sourcePath"].concat(propertiesToInclude)); 21912 if (o.sourcePath) { 21913 delete o.path; 21914 } 21915 return o; 21916 }, 21917 21918 /* _TO_SVG_START_ */ 21919 /** 21920 * Returns svg representation of an instance 21921 * @return {Array}
21921 an array of strings with the specific svg representation 21922 * of the instance 21923 */ 21924 _toSVG: function () { 21925 var path = fabric.util.joinPath(this.path); 21926 return [ 21927 "<path ", 21928 "COMMON_PARTS", 21929 'd="', 21930 path, 21931 '" stroke-linecap="round" ', 21932 "/>\n", 21933 ]; 21934 }, 21935 21936 _getOffsetTransform: function () { 21937 var digits = fabric.Object.NUM_FRACTION_DIGITS; 21938 return ( 21939 " translate(" + 21940 toFixed(-this.pathOffset.x, digits) + 21941 ", " + 21942 toFixed(-this.pathOffset.y, digits) + 21943 ")" 21944 ); 21945 }, 21946 21947 /** 21948 * Returns svg clipPath representation of an instance 21949 * @param {Function} [reviver] Method for further parsing of svg representation. 21950 * @return {String} svg representation of an instance 21951 */ 21952 toClipPathSVG: function (reviver) { 21953 var additionalTransform = this._getOffsetTransform(); 21954 return ( 21955 "\t" + 21956 this._createBaseClipPathSVGMarkup(this._toSVG(), { 21957 reviver: reviver, 21958 additionalTransform: additionalTransform, 21959 }) 21960 ); 21961 }, 21962 21963 /** 21964 * Returns svg representation of an instance 21965 * @param {Function} [reviver] Method for further parsing of svg representation. 21966 * @return {String} svg representation of an instance 21967 */ 21968 toSVG: function (reviver) { 21969 var additionalTransform = this._getOffsetTransform(); 21970 return this._createBaseSVGMarkup(this._toSVG(), { 21971 reviver: reviver, 21972 additionalTransform: additionalTransform, 21973 }); 21974 }, 21975 /* _TO_SVG_END_ */ 21976 21977 /** 21978 * Returns number representation of an instance complexity 21979 * @return {Number} complexity of this instance 21980 */ 21981 complexity: function () { 21982 return this.path.length; 21983 }, 21984 21985 /** 21986 * @private 21987 */ 21988 _calcDimensions: function () { 21989 var aX = [], 21990 aY = [], 21991 current, // current instruction 21992 subpathStartX = 0, 21993 subpathStartY = 0, 21994 x = 0, // current x 21995 y = 0, // current y 21996 bounds; 21997 21998 for (var i = 0, len = this.path.length; i < len; ++i) { 21999 current = this.path[i]; 22000 22001 switch ( 22002 current[0] // first letter 22003 ) { 22004 case "L": // lineto, absolute 22005 x = current[1]; 22006 y = current[2]; 22007 bounds = []; 22008 break; 22009 22010 case "M": // moveTo, absolute 22011 x = current[1]; 22012 y = current[2]; 22013 subpathStartX = x; 22014 subpathStartY = y; 22015 bounds = []; 22016 break; 22017 22018 case "C": // bezierCurveTo, absolute 22019 bounds = fabric.util.getBoundsOfCurve( 22020 x, 22021 y, 22022 current[1], 22023 current[2], 22024 current[3], 22025 current[4], 22026 current[5], 22027 current[6], 22028 ); 22029 x = current[5]; 22030 y = current[6]; 22031 break; 22032 22033 case "Q": // quadraticCurveTo, absolute 22034 bounds = fabric.util.getBoundsOfCurve( 22035 x, 22036 y, 22037 current[1], 22038 current[2], 22039 current[1], 22040 current[2], 22041 current[3], 22042 current[4], 22043 ); 22044 x = current[3]; 22045 y = current[4]; 22046 break; 22047 22048 case "z": 22049 case "Z": 22050 x = subpathStartX; 22051 y = subpathStartY; 22052 break; 22053 } 22054 bounds.forEach(function (point) { 22055 aX.push(point.x); 22056 aY.push(point.y); 22057 }); 22058 aX.push(x); 22059 aY.push(y); 22060 } 22061 22062 var minX = min(aX) || 0, 22063 minY = min(aY) || 0, 22064 maxX = max(aX) || 0, 22065 maxY = max(aY) || 0, 22066 deltaX = maxX - minX, 22067 deltaY = maxY - minY; 22068 22069 return { 22070 left: minX, 22071 top: minY, 22072 width: deltaX, 22073 height: deltaY, 22074 }; 22075 }, 22076 }, 22077 ); 22078 22079 /** 22080 * Creates an instance of fabric.Path from an object 22081 * @static 22082 * @memberOf fabric.Path 22083 * @param {Object} object 22084 * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created 22085 */ 22086 fabric.Path.fromObject = function (object, callback) { 22087 if (typeof object.sourcePath === "string") { 22088 var pathUrl = object.sourcePath; 22089 fabric.loadSVGFromURL(pathUrl, function (elements) { 22090 var path = elements[0]; 22091 path.setOptions(object); 22092 if (object.clipPath) { 22093 fabric.util.enlivenObjects( 22094 [object.clipPath], 22095 function (elivenedObjects) {
22096 path.clipPath = elivenedObjects[0]; 22097 callback && callback(path); 22098 }, 22099 ); 22100 } else { 22101 callback && callback(path); 22102 } 22103 }); 22104 } else { 22105 fabric.Object._fromObject("Path", object, callback, "path"); 22106 } 22107 }; 22108 22109 /* _FROM_SVG_START_ */ 22110 /** 22111 * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) 22112 * @static 22113 * @memberOf fabric.Path 22114 * @see http://www.w3.org/TR/SVG/paths.html#PathElement 22115 */ 22116 fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(["d"]); 22117 22118 /** 22119 * Creates an instance of fabric.Path from an SVG <path> element 22120 * @static 22121 * @memberOf fabric.Path 22122 * @param {SVGElement} element to parse 22123 * @param {Function} callback Callback to invoke when an fabric.Path instance is created 22124 * @param {Object} [options] Options object 22125 * @param {Function} [callback] Options callback invoked after parsing is finished 22126 */ 22127 fabric.Path.fromElement = function (element, callback, options) { 22128 var parsedAttributes = fabric.parseAttributes( 22129 element, 22130 fabric.Path.ATTRIBUTE_NAMES, 22131 ); 22132 parsedAttributes.fromSVG = true; 22133 callback( 22134 new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options)), 22135 ); 22136 }; 22137 /* _FROM_SVG_END_ */ 22138})(typeof exports !== "undefined" ? exports : this); 22139 22140(function (global) { 22141 "use strict"; 22142 22143 var fabric = global.fabric || (global.fabric = {}), 22144 min = fabric.util.array.min, 22145 max = fabric.util.array.max; 22146 22147 if (fabric.Group) { 22148 return; 22149 } 22150 22151 /** 22152 * Group class 22153 * @class fabric.Group 22154 * @extends fabric.Object 22155 * @mixes fabric.Collection 22156 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups} 22157 * @see {@link fabric.Group#initialize} for constructor definition 22158 */ 22159 fabric.Group = fabric.util.createClass( 22160 fabric.Object, 22161 fabric.Collection, 22162 /** @lends fabric.Group.prototype */ { 22163 /** 22164 * Type of an object
vendor: 16,447 bytes, lines 22165-22688
22165 * @type String 22166 * @default 22167 */ 22168 type: "group", 22169 22170 /** 22171 * Width of stroke 22172 * @type Number 22173 * @default 22174 */ 22175 strokeWidth: 0, 22176 22177 /** 22178 * Indicates if click, mouseover, mouseout events & hoverCursor should also check for subtargets 22179 * @type Boolean 22180 * @default 22181 */ 22182 subTargetCheck: false, 22183 22184 /** 22185 * Groups are container, do not render anything on theyr own, ence no cache properties 22186 * @type Array 22187 * @default 22188 */ 22189 cacheProperties: [], 22190 22191 /** 22192 * setOnGroup is a method used for TextBox that is no more used since 2.0.0 The behavior is still 22193 * available setting this boolean to true. 22194 * @type Boolean 22195 * @since 2.0.0 22196 * @default 22197 */ 22198 useSetOnGroup: false, 22199 22200 /** 22201 * Constructor 22202 * @param {Object} objects Group objects 22203 * @param {Object} [options] Options object 22204 * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already. 22205 * @return {Object} thisArg 22206 */ 22207 initialize: function (objects, options, isAlreadyGrouped) { 22208 options = options || {}; 22209 this._objects = []; 22210 // if objects enclosed in a group have been grouped already, 22211 // we cannot change properties of objects. 22212 // Thus we need to set options to group without objects, 22213 isAlreadyGrouped && this.callSuper("initialize", options); 22214 this._objects = objects || []; 22215 for (var i = this._objects.length; i--; ) { 22216 this._objects[i].group = this; 22217 } 22218 22219 if (!isAlreadyGrouped) { 22220 var center = options && options.centerPoint; 22221 // we want to set origins before calculating the bounding box. 22222 // so that the topleft can be set with that in mind. 22223 // if specific top and left are passed, are overwritten later 22224 // with the callSuper('initialize', options) 22225 if (options.originX !== undefined) { 22226 this.originX = options.originX; 22227 } 22228 if (options.originY !== undefined) { 22229 this.originY = options.originY; 22230 } 22231 // if coming from svg i do not want to calc bounds. 22232 // i assume width and height are passed along options 22233 center || this._calcBounds(); 22234 this._updateObjectsCoords(center); 22235 delete options.centerPoint; 22236 this.callSuper("initialize", options); 22237 } else { 22238 this._updateObjectsACoords(); 22239 } 22240 22241 this.setCoords(); 22242 }, 22243 22244 /** 22245 * @private 22246 */ 22247 _updateObjectsACoords: function () { 22248 var skipControls = true; 22249 for (var i = this._objects.length; i--; ) { 22250 this._objects[i].setCoords(skipControls); 22251 } 22252 }, 22253 22254 /** 22255 * @private 22256 * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change 22257 */ 22258 _updateObjectsCoords: function (center) { 22259 var center = center || this.getCenterPoint(); 22260 for (var i = this._objects.length; i--; ) { 22261 this._updateObjectCoords(this._objects[i], center); 22262 } 22263 }, 22264 22265 /** 22266 * @private 22267 * @param {Object} object 22268 * @param {fabric.Point} center, current center of group. 22269 */ 22270 _updateObjectCoords: function (object, center) { 22271 var objectLeft = object.left, 22272 objectTop = object.top, 22273 skipControls = true; 22274 22275 object.set({ 22276 left: objectLeft - center.x, 22277 top: objectTop - center.y, 22278 }); 22279 object.group = this; 22280 object.setCoords(skipControls); 22281 }, 22282 22283 /** 22284 * Returns string represenation of a group 22285 * @return {String} 22286 */ 22287 toString: function () { 22288 return "#<fabric.Group: (" + this.complexity() + ")>"; 22289 }, 22290 22291 /** 22292 * Adds an object to a group; Then recalculates group's dimension, position. 22293 * @param {Object} object 22294 * @return {fabric.Group} thisArg 22295 * @chainable 22296 */ 22297 addWithUpdate: function (object) { 22298 var nested = !!this.group; 22299 this._restoreObjectsState(); 22300 fabric.util.resetObjectTransform(this); 22301 if (object) { 22302 if (nested) { 22303 // if this group is inside another group, we need to pre transform the object 22304 fabric.util.removeTransformFromObject( 22305 object, 22306 this.group.calcTransformMatrix(), 22307 ); 22308 } 22309 this._objects.push(object); 22310 object.group = this; 22311 object._set("canvas", this.canvas); 22312 } 22313 this._calcBounds(); 22314 this._updateObjectsCoords(); 22315 this.dirty = true; 22316 if (nested) { 22317 this.group.addWithUpdate(); 22318 } else { 22319 this.setCoords(); 22320 } 22321 return this; 22322 }, 22323 22324 /** 22325 * Removes an object from a group; Then recalculates group's dimension, position. 22326 * @param {Object} object 22327 * @return {fabric.Group} thisArg 22328 * @chainable 22329 */ 22330 removeWithUpdate: function (object) { 22331 this._restoreObjectsState(); 22332 fabric.util.resetObjectTransform(this); 22333 22334 this.remove(object); 22335 this._calcBounds(); 22336 this._updateObjectsCoords(); 22337 this.setCoords(); 22338 this.dirty = true; 22339 return this; 22340 }, 22341 22342 /** 22343 * @private 22344 */ 22345 _onObjectAdded: function (object) { 22346 this.dirty = true; 22347 object.group = this; 22348 object._set("canvas", this.canvas); 22349 }, 22350 22351 /** 22352 * @private 22353 */ 22354 _onObjectRemoved: function (object) { 22355 this.dirty = true; 22356 delete object.group; 22357 }, 22358 22359 /** 22360 * @private 22361 */ 22362 _set: function (key, value) { 22363 var i = this._objects.length; 22364 if (this.useSetOnGroup) { 22365 while (i--) { 22366 this._objects[i].setOnGroup(key, value); 22367 } 22368 } 22369 if (key === "canvas") { 22370 while (i--) { 22371 this._objects[i]._set(key, value); 22372 } 22373 } 22374 fabric.Object.prototype._set.call(this, key, value); 22375 }, 22376 22377 /** 22378 * Returns object representation of an instance 22379 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 22380 * @return {Object} object representation of an instance 22381 */ 22382 toObject: function (propertiesToInclude) { 22383 var _includeDefaultValues = this.includeDefaultValues; 22384 var objsToObject = this._objects 22385 .filter(function (obj) { 22386 return !obj.excludeFromExport; 22387 }) 22388 .map(function (obj) { 22389 var originalDefaults = obj.includeDefaultValues; 22390 obj.includeDefaultValues = _includeDefaultValues; 22391 var _obj = obj.toObject(propertiesToInclude); 22392 obj.includeDefaultValues = originalDefaults; 22393 return _obj; 22394 }); 22395 var obj = fabric.Object.prototype.toObject.call( 22396 this, 22397 propertiesToInclude, 22398 ); 22399 obj.objects = objsToObject; 22400 return obj; 22401 }, 22402 22403 /** 22404 * Returns object representation of an instance, in dataless mode. 22405 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 22406 * @return {Object} object representation of an instance 22407 */ 22408 toDatalessObject: function (propertiesToInclude) { 22409 var objsToObject, 22410 sourcePath = this.sourcePath; 22411 if (sourcePath) { 22412 objsToObject = sourcePath; 22413 } else { 22414 var _includeDefaultValues = this.includeDefaultValues; 22415 objsToObject = this._objects.map(function (obj) { 22416 var originalDefaults = obj.includeDefaultValues; 22417 obj.includeDefaultValues = _includeDefaultValues; 22418 var _obj = obj.toDatalessObject(propertiesToInclude); 22419 obj.includeDefaultValues = originalDefaults; 22420 return _obj; 22421 }); 22422 } 22423 var obj = fabric.Object.prototype.toDatalessObject.call( 22424 this, 22425 propertiesToInclude, 22426 ); 22427 obj.objects = objsToObject; 22428 return obj; 22429 }, 22430 22431 /** 22432 * Renders instance on a given context 22433 * @param {CanvasRenderingContext2D} ctx context to render instance on 22434 */ 22435 render: function (ctx) { 22436 this._transformDone = true; 22437 this.callSuper("render", ctx); 22438 this._transformDone = false; 22439 }, 22440 22441 /** 22442 * Decide if the object should cache or not. Create its own cache level 22443 * needsItsOwnCache should be used when the object drawing method requires 22444 * a cache step. None of the fabric classes requires it. 22445 * Generally you do not cache objects in groups because the group is already cached. 22446 * @return {Boolean} 22447 */ 22448 shouldCache: function () { 22449 var ownCache = fabric.Object.prototype.shouldCache.call(this); 22450 if (ownCache) { 22451 for (var i = 0, len = this._objects.length; i < len; i++) { 22452 if (this._objects[i].willDrawShadow()) { 22453 this.ownCaching = false; 22454 return false; 22455 } 22456 } 22457 } 22458 return ownCache; 22459 }, 22460 22461 /** 22462 * Check if this object or a child object will cast a shadow 22463 * @return {Boolean} 22464 */ 22465 willDrawShadow: function () { 22466 if (fabric.Object.prototype.willDrawShadow.call(this)) { 22467 return true; 22468 } 22469 for (var i = 0, len = this._objects.length; i < len; i++) { 22470 if (this._objects[i].willDrawShadow()) { 22471 return true; 22472 } 22473 } 22474 return false; 22475 }, 22476 22477 /** 22478 * Check if this group or its parent group are caching, recursively up 22479 * @return {Boolean} 22480 */ 22481 isOnACache: function () { 22482 return this.ownCaching || (this.group && this.group.isOnACache()); 22483 }, 22484 22485 /** 22486 * Execute the drawing operation for an object on a specified context 22487 * @param {CanvasRenderingContext2D} ctx Context to render on 22488 */ 22489 drawObject: function (ctx) { 22490 for (var i = 0, len = this._objects.length; i < len; i++) { 22491 this._objects[i].render(ctx); 22492 } 22493 this._drawClipPath(ctx, this.clipPath); 22494 }, 22495 22496 /** 22497 * Check if cache is dirty 22498 */ 22499 isCacheDirty: function (skipCanvas) { 22500 if (this.callSuper("isCacheDirty", skipCanvas)) { 22501 return true; 22502 } 22503 if (!this.statefullCache) { 22504 return false; 22505 } 22506 for (var i = 0, len = this._objects.length; i < len; i++) { 22507 if (this._objects[i].isCacheDirty(true)) { 22508 if (this._cacheCanvas) { 22509 // if this group has not a cache canvas there is nothing to clean 22510 var x = this.cacheWidth / this.zoomX, 22511 y = this.cacheHeight / this.zoomY; 22512 this._cacheContext.clearRect(-x / 2, -y / 2, x, y); 22513 } 22514 return true; 22515 } 22516 } 22517 return false; 22518 }, 22519 22520 /** 22521 * Restores original state of each of group objects (original state is that which was before group was created). 22522 * if the nested boolean is true, the original state will be restored just for the 22523 * first group and not for all the group chain 22524 * @private 22525 * @param {Boolean} nested tell the function to restore object state up to the parent group and not more 22526 * @return {fabric.Group} thisArg 22527 * @chainable 22528 */ 22529 _restoreObjectsState: function () { 22530 var groupMatrix = this.calcOwnMatrix(); 22531 this._objects.forEach(function (object) { 22532 // instead of using _this = this; 22533 fabric.util.addTransformToObject(object, groupMatrix); 22534 delete object.group; 22535 object.setCoords(); 22536 }); 22537 return this; 22538 }, 22539 22540 /** 22541 * Destroys a group (restoring state of its objects) 22542 * @return {fabric.Group} thisArg 22543 * @chainable 22544 */ 22545 destroy: function () { 22546 // when group is destroyed objects needs to get a repaint to be eventually 22547 // displayed on canvas. 22548 this._objects.forEach(function (object) { 22549 object.set("dirty", true); 22550 }); 22551 return this._restoreObjectsState(); 22552 }, 22553 22554 dispose: function () { 22555 this.callSuper("dispose"); 22556 this.forEachObject(function (object) { 22557 object.dispose && object.dispose(); 22558 }); 22559 this._objects = []; 22560 }, 22561 22562 /** 22563 * make a group an active selection, remove the group from canvas 22564 * the group has to be on canvas for this to work. 22565 * @return {fabric.ActiveSelection} thisArg 22566 * @chainable 22567 */ 22568 toActiveSelection: function () { 22569 if (!this.canvas) { 22570 return; 22571 } 22572 var objects = this._objects, 22573 canvas = this.canvas; 22574 this._objects = []; 22575 var options = this.toObject(); 22576 delete options.objects; 22577 var activeSelection = new fabric.ActiveSelection([]); 22578 activeSelection.set(options); 22579 activeSelection.type = "activeSelection"; 22580 canvas.remove(this); 22581 objects.forEach(function (object) { 22582 object.group = activeSelection; 22583 object.dirty = true; 22584 canvas.add(object); 22585 }); 22586 activeSelection.canvas = canvas; 22587 activeSelection._objects = objects; 22588 canvas._activeObject = activeSelection; 22589 activeSelection.setCoords(); 22590 return activeSelection; 22591 }, 22592 22593 /** 22594 * Destroys a group (restoring state of its objects) 22595 * @return {fabric.Group} thisArg 22596 * @chainable 22597 */ 22598 ungroupOnCanvas: function () { 22599 return this._restoreObjectsState(); 22600 }, 22601 22602 /** 22603 * Sets coordinates of all objects inside group 22604 * @return {fabric.Group} thisArg 22605 * @chainable 22606 */ 22607 setObjectsCoords: function () { 22608 var skipControls = true; 22609 this.forEachObject(function (object) { 22610 object.setCoords(skipControls); 22611 }); 22612 return this; 22613 }, 22614 22615 /** 22616 * @private 22617 */ 22618 _calcBounds: function (onlyWidthHeight) { 22619 var aX = [], 22620 aY = [], 22621 o, 22622 prop, 22623 coords, 22624 props = ["tr", "br", "bl", "tl"], 22625 i = 0, 22626 iLen = this._objects.length, 22627 j, 22628 jLen = props.length; 22629 22630 for (; i < iLen; ++i) { 22631 o = this._objects[i]; 22632 coords = o.calcACoords(); 22633 for (j = 0; j < jLen; j++) { 22634 prop = props[j]; 22635 aX.push(coords[prop].x); 22636 aY.push(coords[prop].y); 22637 } 22638 o.aCoords = coords; 22639 } 22640 22641 this._getBounds(aX, aY, onlyWidthHeight); 22642 }, 22643 22644 /** 22645 * @private 22646 */ 22647 _getBounds: function (aX, aY, onlyWidthHeight) { 22648 var minXY = new fabric.Point(min(aX), min(aY)), 22649 maxXY = new fabric.Point(max(aX), max(aY)), 22650 top = minXY.y || 0, 22651 left = minXY.x || 0, 22652 width = maxXY.x - minXY.x || 0, 22653 height = maxXY.y - minXY.y || 0; 22654 this.width = width; 22655 this.height = height; 22656 if (!onlyWidthHeight) { 22657 // the bounding box always finds the topleft most corner. 22658 // whatever is the group origin, we set up here the left/top position. 22659 this.setPositionByOrigin({ x: left, y: top }, "left", "top"); 22660 } 22661 }, 22662 22663 /* _TO_SVG_START_ */ 22664 /** 22665 * Returns svg representation of an instance 22666 * @param {Function} [reviver] Method for further parsing of svg representation. 22667 * @return {String} svg representation of an instance 22668 */ 22669 _toSVG: function (reviver) { 22670 var svgString = ["<g ", "COMMON_PARTS", " >\n"]; 22671 22672 for (var i = 0, len = this._objects.length; i < len; i++) { 22673 svgString.push("\t\t", this._objects[i].toSVG(reviver)); 22674 } 22675 svgString.push("</g>\n"); 22676 return svgString; 22677 }, 22678 22679 /** 22680 * Returns styles-string for svg-export, specific version for group 22681 * @return {String} 22682 */ 22683 getSvgStyles: function () { 22684 var opacity = 22685 typeof this.opacity !== "undefined" && this.opacity !== 1 22686 ? "opacity: " + this.opacity + ";" 22687 : "", 22688 visibility = this.visible ? "" : " visibility: hidden;";
22689 return [opacity, this.getSvgFilter(), visibility].join(""); 22690 }, 22691 22692 /** 22693 * Returns svg clipPath representation of an instance 22694 * @param {Function} [reviver] Method for further parsing of svg representation. 22695 * @return {String} svg representation of an instance 22696 */ 22697 toClipPathSVG: function (reviver) { 22698 var svgString = []; 22699 22700 for (var i = 0, len = this._objects.length; i < len; i++) { 22701 svgString.push("\t", this._objects[i].toClipPathSVG(reviver)); 22702 } 22703 22704 return this._createBaseClipPathSVGMarkup(svgString, { 22705 reviver: reviver, 22706 }); 22707 }, 22708 /* _TO_SVG_END_ */ 22709 }, 22710 ); 22711 22712 /** 22713 * Returns {@link fabric.Group} instance from an object representation 22714 * @static 22715 * @memberOf fabric.Group 22716 * @param {Object} object Object to create a group from 22717 * @param {Function} [callback] Callback to invoke when an group instance is created 22718 */ 22719 fabric.Group.fromObject = function (object, callback) { 22720 var objects = object.objects, 22721 options = fabric.util.object.clone(object, true); 22722 delete options.objects; 22723 if (typeof objects === "string") { 22724 // it has to be an url or something went wrong. 22725 fabric.loadSVGFromURL(objects, function (elements) { 22726 var group = fabric.util.groupSVGElements(elements, object, objects); 22727 var clipPath = options.clipPath; 22728 delete options.clipPath; 22729 group.set(options); 22730 if (clipPath) { 22731 fabric.util.enlivenObjects([clipPath], function (elivenedObjects) { 22732 group.clipPath = elivenedObjects[0]; 22733 callback && callback(group); 22734 }); 22735 } else { 22736 callback && callback(group); 22737 } 22738 }); 22739 return; 22740 } 22741 fabric.util.enlivenObjects(objects, function (enlivenedObjects) { 22742 fabric.util.enlivenObjectEnlivables(object, options, function () { 22743 callback && callback(new fabric.Group(enlivenedObjects, options, true)); 22744 }); 22745 }); 22746 }; 22747})(typeof exports !== "undefined" ? exports : this); 22748 22749(function (global) { 22750 "use strict"; 22751 22752 var fabric = global.fabric || (global.fabric = {}); 22753 22754 if (fabric.ActiveSelection) { 22755 return; 22756 } 22757 22758 /** 22759 * Group class 22760 * @class fabric.ActiveSelection 22761 * @extends fabric.Group 22762 * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups} 22763 * @see {@link fabric.ActiveSelection#initialize} for constructor definition 22764 */ 22765 fabric.ActiveSelection = fabric.util.createClass( 22766 fabric.Group, 22767 /** @lends fabric.ActiveSelection.prototype */ { 22768 /** 22769 * Type of an object
vendor: 4,857 bytes, lines 22770-22925
22770 * @type String 22771 * @default 22772 */ 22773 type: "activeSelection", 22774 22775 /** 22776 * Constructor 22777 * @param {Object} objects ActiveSelection objects 22778 * @param {Object} [options] Options object 22779 * @return {Object} thisArg 22780 */ 22781 initialize: function (objects, options) { 22782 options = options || {}; 22783 this._objects = objects || []; 22784 for (var i = this._objects.length; i--; ) { 22785 this._objects[i].group = this; 22786 } 22787 22788 if (options.originX) { 22789 this.originX = options.originX; 22790 } 22791 if (options.originY) { 22792 this.originY = options.originY; 22793 } 22794 this._calcBounds(); 22795 this._updateObjectsCoords(); 22796 fabric.Object.prototype.initialize.call(this, options); 22797 this.setCoords(); 22798 }, 22799 22800 /** 22801 * Change te activeSelection to a normal group, 22802 * High level function that automatically adds it to canvas as 22803 * active object. no events fired. 22804 * @since 2.0.0 22805 * @return {fabric.Group} 22806 */ 22807 toGroup: function () { 22808 var objects = this._objects.concat(); 22809 this._objects = []; 22810 var options = fabric.Object.prototype.toObject.call(this); 22811 var newGroup = new fabric.Group([]); 22812 delete options.type; 22813 newGroup.set(options); 22814 objects.forEach(function (object) { 22815 object.canvas.remove(object); 22816 object.group = newGroup; 22817 }); 22818 newGroup._objects = objects; 22819 if (!this.canvas) { 22820 return newGroup; 22821 } 22822 var canvas = this.canvas; 22823 canvas.add(newGroup); 22824 canvas._activeObject = newGroup; 22825 newGroup.setCoords(); 22826 return newGroup; 22827 }, 22828 22829 /** 22830 * If returns true, deselection is cancelled. 22831 * @since 2.0.0 22832 * @return {Boolean} [cancel] 22833 */ 22834 onDeselect: function () { 22835 this.destroy(); 22836 return false; 22837 }, 22838 22839 /** 22840 * Returns string representation of a group 22841 * @return {String} 22842 */ 22843 toString: function () { 22844 return "#<fabric.ActiveSelection: (" + this.complexity() + ")>"; 22845 }, 22846 22847 /** 22848 * Decide if the object should cache or not. Create its own cache level 22849 * objectCaching is a global flag, wins over everything 22850 * needsItsOwnCache should be used when the object drawing method requires 22851 * a cache step. None of the fabric classes requires it. 22852 * Generally you do not cache objects in groups because the group outside is cached. 22853 * @return {Boolean} 22854 */ 22855 shouldCache: function () { 22856 return false; 22857 }, 22858 22859 /** 22860 * Check if this group or its parent group are caching, recursively up 22861 * @return {Boolean} 22862 */ 22863 isOnACache: function () { 22864 return false; 22865 }, 22866 22867 /** 22868 * Renders controls and borders for the object 22869 * @param {CanvasRenderingContext2D} ctx Context to render on 22870 * @param {Object} [styleOverride] properties to override the object style 22871 * @param {Object} [childrenOverride] properties to override the children overrides 22872 */ 22873 _renderControls: function (ctx, styleOverride, childrenOverride) { 22874 ctx.save(); 22875 ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; 22876 this.callSuper("_renderControls", ctx, styleOverride); 22877 childrenOverride = childrenOverride || {}; 22878 if (typeof childrenOverride.hasControls === "undefined") { 22879 childrenOverride.hasControls = false; 22880 } 22881 childrenOverride.forActiveSelection = true; 22882 for (var i = 0, len = this._objects.length; i < len; i++) { 22883 this._objects[i]._renderControls(ctx, childrenOverride); 22884 } 22885 ctx.restore(); 22886 }, 22887 }, 22888 ); 22889 22890 /** 22891 * Returns {@link fabric.ActiveSelection} instance from an object representation 22892 * @static 22893 * @memberOf fabric.ActiveSelection 22894 * @param {Object} object Object to create a group from 22895 * @param {Function} [callback] Callback to invoke when an ActiveSelection instance is created 22896 */ 22897 fabric.ActiveSelection.fromObject = function (object, callback) { 22898 fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) { 22899 delete object.objects; 22900 callback && 22901 callback(new fabric.ActiveSelection(enlivenedObjects, object, true)); 22902 }); 22903 }; 22904})(typeof exports !== "undefined" ? exports : this); 22905 22906(function (global) { 22907 "use strict"; 22908 22909 var extend = fabric.util.object.extend; 22910 22911 if (!global.fabric) { 22912 global.fabric = {}; 22913 } 22914 22915 if (global.fabric.Image) { 22916 fabric.warn("fabric.Image is already defined."); 22917 return; 22918 } 22919 22920 /** 22921 * Image class 22922 * @class fabric.Image 22923 * @extends fabric.Object 22924 * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#images} 22925 * @see {@link fabric.Image#initialize}
22925 for constructor definition 22926 */ 22927 fabric.Image = fabric.util.createClass( 22928 fabric.Object, 22929 /** @lends fabric.Image.prototype */ { 22930 /** 22931 * Type of an object 22932 * @type String 22933 * @default 22934 */ 22935 type: "image", 22936 22937 /** 22938 * Width of a stroke. 22939 * For image quality a stroke multiple of 2 gives better results. 22940 * @type Number 22941 * @default 22942 */ 22943 strokeWidth: 0, 22944 22945 /** 22946 * When calling {@link fabric.Image.getSrc}, return value from element src with `element.getAttribute('src')`. 22947 * This allows for relative urls as image src. 22948 * @since 2.7.0 22949 * @type Boolean 22950 * @default 22951 */ 22952 srcFromAttribute: false, 22953 22954 /** 22955 * private 22956 * contains last value of scaleX to detect 22957 * if the Image got resized after the last Render 22958 * @type Number 22959 */ 22960 _lastScaleX: 1, 22961 22962 /** 22963 * private 22964 * contains last value of scaleY to detect 22965 * if the Image got resized after the last Render 22966 * @type Number 22967 */ 22968 _lastScaleY: 1, 22969 22970 /** 22971 * private 22972 * contains last value of scaling applied by the apply filter chain 22973 * @type Number 22974 */ 22975 _filterScalingX: 1, 22976 22977 /** 22978 * private 22979 * contains last value of scaling applied by the apply filter chain 22980 * @type Number 22981 */ 22982 _filterScalingY: 1, 22983 22984 /** 22985 * minimum scale factor under which any resizeFilter is triggered to resize the image 22986 * 0 will disable the automatic resize. 1 will trigger automatically always. 22987 * number bigger than 1 are not implemented yet. 22988 * @type Number 22989 */ 22990 minimumScaleTrigger: 0.5, 22991 22992 /** 22993 * List of properties to consider when checking if 22994 * state of an object is changed ({@link fabric.Object#hasStateChanged}) 22995 * as well as for history (undo/redo) purposes 22996 * @type Array 22997 */ 22998 stateProperties: fabric.Object.prototype.stateProperties.concat( 22999 "cropX", 23000 "cropY", 23001 ), 23002 23003 /** 23004 * List of properties to consider when checking if cache needs refresh 23005 * Those properties are checked by statefullCache ON ( or lazy mode if we want ) or from single 23006 * calls to Object.set(key, value). If the key is in this list, the object is marked as dirty 23007 * and refreshed at the next render 23008 * @type Array 23009 */ 23010 cacheProperties: fabric.Object.prototype.cacheProperties.concat( 23011 "cropX", 23012 "cropY", 23013 ), 23014 23015 /** 23016 * key used to retrieve the texture representing this image 23017 * @since 2.0.0
23018 * @type String 23019 * @default 23020 */ 23021 cacheKey: "", 23022 23023 /** 23024 * Image crop in pixels from original image size. 23025 * @since 2.0.0 23026 * @type Number 23027 * @default 23028 */ 23029 cropX: 0, 23030 23031 /** 23032 * Image crop in pixels from original image size. 23033 * @since 2.0.0 23034 * @type Number 23035 * @default 23036 */ 23037 cropY: 0, 23038 23039 /** 23040 * Indicates whether this canvas will use image smoothing when painting this image. 23041 * Also influence if the cacheCanvas for this image uses imageSmoothing 23042 * @since 4.0.0-beta.11 23043 * @type Boolean 23044 * @default 23045 */ 23046 imageSmoothing: true, 23047 23048 /** 23049 * Constructor 23050 * Image can be initialized with any canvas drawable or a string. 23051 * The string should be a url and will be loaded as an image. 23052 * Canvas and Image element work out of the box, while videos require extra code to work. 23053 * Please check video element events for seeking. 23054 * @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | String} element Image element 23055 * @param {Object} [options] Options object 23056 * @param {function} [callback] callback function to call after eventual filters applied. 23057 * @return {fabric.Image} thisArg 23058 */ 23059 initialize: function (element, options) { 23060 options || (options = {}); 23061 this.filters = []; 23062 this.cacheKey = "texture" + fabric.Object.__uid++; 23063 this.callSuper("initialize", options); 23064 this._initElement(element, options); 23065 }, 23066 23067 /** 23068 * Returns image element which this instance if based on 23069 * @return {HTMLImageElement} Image element 23070 */ 23071 getElement: function () { 23072 return this._element || {}; 23073 }, 23074 23075 /** 23076 * Sets image element for this instance to a specified one. 23077 * If filters defined they are applied to new image. 23078 * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. 23079 * @param {HTMLImageElement} element 23080 * @param {Object} [options] Options object 23081 * @return {fabric.Image} thisArg 23082 * @chainable 23083 */ 23084 setElement: function (element, options) { 23085 this.removeTexture(this.cacheKey); 23086 this.removeTexture(this.cacheKey + "_filtered"); 23087 this._element = element; 23088 this._originalElement = element; 23089 this._initConfig(options); 23090 if (this.filters.length !== 0) { 23091 this.applyFilters(); 23092 } 23093 // resizeFilters work on the already filtered copy. 23094 // we need to apply resizeFilters AFTER normal filters. 23095 // applyResizeFilters is run more often than normal filters 23096 // and is triggered by user interactions rather than dev code 23097 if (this.resizeFilter) { 23098 this.applyResizeFilters(); 23099 } 23100 return this; 23101 }, 23102 23103 /** 23104 * Delete a single texture if in webgl mode 23105 */ 23106 removeTexture: function (key) { 23107 var backend = fabric.filterBackend; 23108 if (backend && backend.evictCachesForKey) { 23109 backend.evictCachesForKey(key); 23110 } 23111 }, 23112 23113 /** 23114 * Delete textures, reference to elements and eventually JSDOM cleanup 23115 */ 23116 dispose: function () { 23117 this.callSuper("dispose"); 23118 this.removeTexture(this.cacheKey); 23119 this.removeTexture(this.cacheKey + "_filtered"); 23120 this._cacheContext = undefined; 23121 ["_originalElement", "_element", "_filteredEl", "_cacheCanvas"].forEach( 23122 function (element) { 23123 fabric.util.cleanUpJsdomNode(this[element]); 23124 this[element] = undefined; 23125 }.bind(this), 23126 ); 23127 }, 23128 23129 /** 23130 * Get the crossOrigin value (of the corresponding image element) 23131 */ 23132 getCrossOrigin: function () { 23133 return ( 23134 this._originalElement && (this._originalElement.crossOrigin || null) 23135 ); 23136 }, 23137 23138 /** 23139 * Returns original size of an image 23140 * @return {Object} Object with "width" and "height" properties 23141 */ 23142 getOriginalSize: function () { 23143 var element = this.getElement(); 23144 return {
23145 width: element.naturalWidth || element.width, 23146 height: element.naturalHeight || element.height, 23147 }; 23148 }, 23149 23150 /** 23151 * @private 23152 * @param {CanvasRenderingContext2D} ctx Context to render on 23153 */ 23154 _stroke: function (ctx) { 23155 if (!this.stroke || this.strokeWidth === 0) { 23156 return; 23157 } 23158 var w = this.width / 2, 23159 h = this.height / 2; 23160 ctx.beginPath(); 23161 ctx.moveTo(-w, -h); 23162 ctx.lineTo(w, -h); 23163 ctx.lineTo(w, h); 23164 ctx.lineTo(-w, h); 23165 ctx.lineTo(-w, -h); 23166 ctx.closePath(); 23167 }, 23168 23169 /** 23170 * Returns object representation of an instance 23171 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 23172 * @return {Object} Object representation of an instance 23173 */ 23174 toObject: function (propertiesToInclude) { 23175 var filters = []; 23176 23177 this.filters.forEach(function (filterObj) { 23178 if (filterObj) { 23179 filters.push(filterObj.toObject()); 23180 } 23181 }); 23182 var object = extend( 23183 this.callSuper( 23184 "toObject", 23185 ["cropX", "cropY"].concat(propertiesToInclude), 23186 ), 23187 { 23188 src: this.getSrc(), 23189 crossOrigin: this.getCrossOrigin(), 23190 filters: filters, 23191 }, 23192 ); 23193 if (this.resizeFilter) { 23194 object.resizeFilter = this.resizeFilter.toObject(); 23195 } 23196 return object; 23197 }, 23198 23199 /** 23200 * Returns true if an image has crop applied, inspecting values of cropX,cropY,width,height. 23201 * @return {Boolean} 23202 */ 23203 hasCrop: function () { 23204 return ( 23205 this.cropX || 23206 this.cropY || 23207 this.width < this._element.width || 23208 this.height < this._element.height 23209 ); 23210 }, 23211 23212 /* _TO_SVG_START_ */ 23213 /** 23214 * Returns svg representation of an instance 23215 * @return {Array} an array of strings with the specific svg representation 23216 * of the instance 23217 */ 23218 _toSVG: function () { 23219 var svgString = [], 23220 imageMarkup = [], 23221 strokeSvg, 23222 element = this._element, 23223 x = -this.width / 2, 23224 y = -this.height / 2, 23225 clipPath = "", 23226 imageRendering = ""; 23227 if (!element) { 23228 return []; 23229 } 23230 if (this.hasCrop()) { 23231 var clipPathId = fabric.Object.__uid++; 23232 svgString.push( 23233 '<clipPath id="imageCrop_' + clipPathId + '">\n', 23234 '\t<rect x="' + 23235 x + 23236 '" y="' + 23237 y + 23238 '" width="' + 23239 this.width + 23240 '" height="' + 23241 this.height + 23242 '" />\n', 23243 "</clipPath>\n", 23244 ); 23245 clipPath = ' clip-path="url(#imageCrop_' + clipPathId + ')" '; 23246 } 23247 if (!this.imageSmoothing) { 23248 imageRendering = '" image-rendering="optimizeSpeed'; 23249 } 23250 imageMarkup.push( 23251 "\t<image ", 23252 "COMMON_PARTS", 23253 'xlink:href="',
23254 this.getSvgSrc(true), 23255 '" x="', 23256 x - this.cropX, 23257 '" y="', 23258 y - this.cropY, 23259 // we're essentially moving origin of transformation from top/left corner to the center of the shape 23260 // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left 23261 // so that object's center aligns with container's left/top 23262 '" width="', 23263 element.width || element.naturalWidth, 23264 '" height="', 23265 element.height || element.height, 23266 imageRendering, 23267 '"', 23268 clipPath, 23269 "></image>\n", 23270 ); 23271 23272 if (this.stroke || this.strokeDashArray) { 23273 var origFill = this.fill; 23274 this.fill = null; 23275 strokeSvg = [ 23276 "\t<rect ", 23277 'x="', 23278 x, 23279 '" y="', 23280 y, 23281 '" width="', 23282 this.width, 23283 '" height="', 23284 this.height, 23285 '" style="', 23286 this.get
23286SvgStyles(), 23287 '"/>\n', 23288 ]; 23289 this.fill = origFill; 23290 } 23291 if (this.paintFirst !== "fill") { 23292 svgString = svgString.concat(strokeSvg, imageMarkup); 23293 } else { 23294 svgString = svgString.concat(imageMarkup, strokeSvg); 23295 } 23296 return svgString; 23297 }, 23298 /* _TO_SVG_END_ */ 23299 23300 /** 23301 * Returns source of an image 23302 * @param {Boolean} filtered indicates if the src is needed for svg 23303 * @return {String} Source of an image 23304 */ 23305 getSrc: function (filtered) { 23306 var element = filtered ? this._element : this._originalElement; 23307 if (element) { 23308 if (element.toDataURL) { 23309 return element.toDataURL(); 23310 } 23311 23312 if (this.srcFromAttribute) { 23313 return element.getAttribute("src"); 23314 } else { 23315 return element.src; 23316 } 23317 } else { 23318 return this.src || ""; 23319 } 23320 }, 23321 23322 /** 23323 * Sets source of an image 23324 * @param {String} src Source string (URL) 23325 * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied) 23326 * @param {Object} [options] Options object 23327 * @param {String} [options.crossOrigin] crossOrigin value (one of "", "anonymous", "use-credentials") 23328 * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes 23329 * @return {fabric.Image} thisArg 23330 * @chainable 23331 */ 23332 setSrc: function (src, callback, options) { 23333 fabric.util.loadImage( 23334 src, 23335 function (img, isError) { 23336 this.setElement(img, options); 23337 this._setWidthHeight(); 23338 callback && callback(this, isError); 23339 }, 23340 this, 23341 options && options.crossOrigin, 23342 ); 23343 return this; 23344 }, 23345 23346 /** 23347 * Returns string representation of an instance 23348 * @return {String} String representation of an instance 23349 */ 23350 toString: function () { 23351 return '#<fabric.Image: { src: "' + this.getSrc() + '" }>'; 23352 }, 23353 23354 applyResizeFilters: function () { 23355 var filter = this.resizeFilter, 23356 minimumScale = this.minimumScaleTrigger, 23357 objectScale = this.getTotalObjectScaling(), 23358 scaleX = objectScale.scaleX, 23359 scaleY = objectScale.scaleY, 23360 elementToFilter = this._filteredEl || this._originalElement; 23361 if (this.group) { 23362 this.set("dirty", true); 23363 } 23364 if (!filter || (scaleX > minimumScale && scaleY > minimumScale)) { 23365 this._element = elementToFilter; 23366 this._filterScalingX = 1; 23367 this._filterScalingY = 1; 23368 this._lastScaleX = scaleX; 23369 this._lastScaleY = scaleY; 23370 return; 23371 } 23372 if (!fabric.filterBackend) { 23373 fabric.filterBackend = fabric.initFilterBackend(); 23374 } 23375 var canvasEl = fabric.util.createCanvasElement(), 23376 cacheKey = this._filteredEl 23377 ? this.cacheKey + "_filtered" 23378 : this.cacheKey, 23379 sourceWidth = elementToFilter.width, 23380 sourceHeight = elementToFilter.height; 23381 canvasEl.width = sourceWidth; 23382 canvasEl.height = sourceHeight; 23383 this._element = canvasEl; 23384 this._lastScaleX = filter.scaleX = scaleX; 23385 this._lastScaleY = filter.scaleY = scaleY; 23386 fabric.filterBackend.applyFilters( 23387 [filter], 23388 elementToFilter, 23389 sourceWidth, 23390 sourceHeight, 23391 this._element, 23392 cacheKey, 23393 ); 23394 this._filterScalingX = canvasEl.width / this._originalElement.width; 23395 this._filterScalingY = canvasEl.height / this._originalElement.height; 23396 }, 23397 23398 /** 23399 * Applies filters assigned to this image (from "filters" array) or from filter param 23400 * @method applyFilters 23401 * @param {Array} filters to be applied 23402 * @param {Boolean} forResizing specify if the filter operation is a resize operation 23403 * @return {thisArg} return the fabric.Image object 23404 * @chainable 23405 */ 23406 applyFilters: function (filters) { 23407 filters = filters || this.filters || []; 23408 filters = filters.filter(function (filter) { 23409 return filter && !filter.isNeutralState(); 23410 }); 23411 this.set("dirty", true); 23412 23413 // needs to clear out or WEBGL will not resize correctly 23414 this.removeTexture(this.cacheKey + "_filtered"); 23415 23416 if (filters.length === 0) { 23417 this._element = this._originalElement; 23418 this._filteredEl = null; 23419 this._filterScalingX = 1; 23420 this._filterScalingY = 1; 23421 return this; 23422 } 23423 23424 var imgElement = this._originalElement,
vendor: 5,539 bytes, lines 23425-23585
23425 sourceWidth = imgElement.naturalWidth || imgElement.width, 23426 sourceHeight = imgElement.naturalHeight || imgElement.height; 23427 23428 if (this._element === this._originalElement) { 23429 // if the element is the same we need to create a new element 23430 var canvasEl = fabric.util.createCanvasElement(); 23431 canvasEl.width = sourceWidth; 23432 canvasEl.height = sourceHeight; 23433 this._element = canvasEl; 23434 this._filteredEl = canvasEl; 23435 } else { 23436 // clear the existing element to get new filter data 23437 // also dereference the eventual resized _element 23438 this._element = this._filteredEl; 23439 this._filteredEl 23440 .getContext("2d") 23441 .clearRect(0, 0, sourceWidth, sourceHeight); 23442 // we also need to resize again at next renderAll, so remove saved _lastScaleX/Y 23443 this._lastScaleX = 1; 23444 this._lastScaleY = 1; 23445 } 23446 if (!fabric.filterBackend) { 23447 fabric.filterBackend = fabric.initFilterBackend(); 23448 } 23449 fabric.filterBackend.applyFilters( 23450 filters, 23451 this._originalElement, 23452 sourceWidth, 23453 sourceHeight, 23454 this._element, 23455 this.cacheKey, 23456 ); 23457 if ( 23458 this._originalElement.width !== this._element.width || 23459 this._originalElement.height !== this._element.height 23460 ) { 23461 this._filterScalingX = 23462 this._element.width / this._originalElement.width; 23463 this._filterScalingY = 23464 this._element.height / this._originalElement.height; 23465 } 23466 return this; 23467 }, 23468 23469 /** 23470 * @private 23471 * @param {CanvasRenderingContext2D} ctx Context to render on 23472 */ 23473 _render: function (ctx) { 23474 fabric.util.setImageSmoothing(ctx, this.imageSmoothing); 23475 if ( 23476 this.isMoving !== true && 23477 this.resizeFilter && 23478 this._needsResize() 23479 ) { 23480 this.applyResizeFilters(); 23481 } 23482 this._stroke(ctx); 23483 this._renderPaintInOrder(ctx); 23484 }, 23485 23486 /** 23487 * Paint the cached copy of the object on the target context. 23488 * it will set the imageSmoothing for the draw operation 23489 * @param {CanvasRenderingContext2D} ctx Context to render on 23490 */ 23491 drawCacheOnCanvas: function (ctx) { 23492 fabric.util.setImageSmoothing(ctx, this.imageSmoothing); 23493 fabric.Object.prototype.drawCacheOnCanvas.call(this, ctx); 23494 }, 23495 23496 /** 23497 * Decide if the object should cache or not. Create its own cache level 23498 * needsItsOwnCache should be used when the object drawing method requires 23499 * a cache step. None of the fabric classes requires it. 23500 * Generally you do not cache objects in groups because the group outside is cached. 23501 * This is the special image version where we would like to avoid caching where possible. 23502 * Essentially images do not benefit from caching. They may require caching, and in that 23503 * case we do it. Also caching an image usually ends in a loss of details. 23504 * A full performance audit should be done. 23505 * @return {Boolean} 23506 */ 23507 shouldCache: function () { 23508 return this.needsItsOwnCache(); 23509 }, 23510 23511 _renderFill: function (ctx) { 23512 var elementToDraw = this._element; 23513 if (!elementToDraw) { 23514 return; 23515 } 23516 var scaleX = this._filterScalingX, 23517 scaleY = this._filterScalingY, 23518 w = this.width, 23519 h = this.height, 23520 min = Math.min, 23521 max = Math.max, 23522 // crop values cannot be lesser than 0. 23523 cropX = max(this.cropX, 0), 23524 cropY = max(this.cropY, 0), 23525 elWidth = elementToDraw.naturalWidth || elementToDraw.width, 23526 elHeight = elementToDraw.naturalHeight || elementToDraw.height, 23527 sX = cropX * scaleX, 23528 sY = cropY * scaleY, 23529 // the width height cannot exceed element width/height, starting from the crop offset. 23530 sW = min(w * scaleX, elWidth - sX), 23531 sH = min(h * scaleY, elHeight - sY), 23532 x = -w / 2, 23533 y = -h / 2, 23534 maxDestW = min(w, elWidth / scaleX - cropX), 23535 maxDestH = min(h, elHeight / scaleY - cropY); 23536 23537 elementToDraw && 23538 ctx.drawImage( 23539 elementToDraw, 23540 sX, 23541 sY, 23542 sW, 23543 sH, 23544 x, 23545 y, 23546 maxDestW, 23547 maxDestH, 23548 ); 23549 }, 23550 23551 /** 23552 * needed to check if image needs resize 23553 * @private 23554 */ 23555 _needsResize: function () { 23556 var scale = this.getTotalObjectScaling(); 23557 return ( 23558 scale.scaleX !== this._lastScaleX || scale.scaleY !== this._lastScaleY 23559 ); 23560 }, 23561 23562 /** 23563 * @private 23564 */ 23565 _resetWidthHeight: function () { 23566 this.set(this.getOriginalSize()); 23567 }, 23568 23569 /** 23570 * The Image class's initialization method. This method is automatically 23571 * called by the constructor. 23572 * @private 23573 * @param {HTMLImageElement|String} element The element representing the image 23574 * @param {Object} [options] Options object 23575 */ 23576 _initElement: function (element, options) { 23577 this.setElement(fabric.util.getById(element), options); 23578 fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); 23579 }, 23580 23581 /** 23582 * @private 23583 * @param {Object} [options] Options object 23584 */ 23585 _initConfig: function (options) {
23586 options || (options = {}); 23587 this.setOptions(options); 23588 this._setWidthHeight(options); 23589 }, 23590 23591 /** 23592 * @private 23593 * @param {Array} filters to be initialized 23594 * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created 23595 */ 23596 _initFilters: function (filters, callback) { 23597 if (filters && filters.length) { 23598 fabric.util.enlivenObjects( 23599 filters, 23600 function (enlivenedObjects) { 23601 callback && callback(enlivenedObjects); 23602 }, 23603 "fabric.Image.filters", 23604 ); 23605 } else { 23606 callback && callback(); 23607 } 23608 }, 23609 23610 /** 23611 * @private 23612 * Set the width and the height of the image object, using the element or the 23613 * options. 23614 * @param {Object} [options] Object with width/height properties 23615 */ 23616 _setWidthHeight: function (options) { 23617 options || (options = {}); 23618 var el = this.getElement(); 23619 this.width = options.width || el.naturalWidth || el.width || 0; 23620 this.height = options.height || el.naturalHeight || el.height || 0; 23621 }, 23622 23623 /** 23624 * Calculate offset for center and scale factor for the image in order to respect 23625 * the preserveAspectRatio attribute 23626 * @private 23627 * @return {Object} 23628 */ 23629 parsePreserveAspectRatioAttribute: function () { 23630 var pAR = fabric.util.parsePreserveAspectRatioAttribute( 23631 this.preserveAspectRatio || "", 23632 ), 23633 rWidth = this._element.width, 23634 rHeight = this._element.height, 23635 scaleX = 1, 23636 scaleY = 1, 23637 offsetLeft = 0, 23638 offsetTop = 0, 23639 cropX = 0, 23640 cropY = 0, 23641 offset, 23642 pWidth = this.width, 23643 pHeight = this.height, 23644 parsedAttributes = { width: pWidth, height: pHeight }; 23645 if (pAR && (pAR.alignX !== "none" || pAR.alignY !== "none")) { 23646 if (pAR.meetOrSlice === "meet") { 23647 scaleX = scaleY = fabric.util.findScaleToFit( 23648 this._element, 23649 parsedAttributes, 23650 ); 23651 offset = (pWidth - rWidth * scaleX) / 2; 23652 if (pAR.alignX === "Min") { 23653 offsetLeft = -offset; 23654 } 23655 if (pAR.alignX === "Max") { 23656 offsetLeft = offset; 23657 } 23658 offset = (pHeight - rHeight * scaleY) / 2; 23659 if (pAR.alignY === "Min") { 23660 offsetTop = -offset; 23661 } 23662 if (pAR.alignY === "Max") { 23663 offsetTop = offset; 23664 } 23665 } 23666 if (pAR.meetOrSlice === "slice") { 23667 scaleX = scaleY = fabric.util.findScaleToCover( 23668 this._element, 23669 parsedAttributes, 23670 ); 23671 offset = rWidth - pWidth / scaleX; 23672 if (pAR.alignX === "Mid") { 23673 cropX = offset / 2; 23674 } 23675 if (pAR.alignX === "Max") { 23676 cropX = offset; 23677 } 23678 offset = rHeight - pHeight / scaleY; 23679 if (pAR.alignY === "Mid") { 23680 cropY = offset / 2; 23681 } 23682 if (pAR.alignY === "Max") { 23683 cropY = offset; 23684 } 23685 rWidth = pWidth / scaleX; 23686 rHeight = pHeight / scaleY; 23687 } 23688 } else { 23689 scaleX = pWidth / rWidth; 23690 scaleY = pHeight / rHeight; 23691 } 23692 return { 23693 width: rWidth, 23694 height: rHeight, 23695 scaleX: scaleX, 23696 scaleY: scaleY, 23697 offsetLeft: offsetLeft, 23698 offsetTop: offsetTop, 23699 cropX: cropX, 23700 cropY: cropY, 23701 }; 23702 }, 23703 }, 23704 ); 23705 23706 /** 23707 * Default CSS class name for canvas 23708 * @static
23709 * @type String 23710 * @default 23711 */ 23712 fabric.Image.CSS_CANVAS = "canvas-img"; 23713 23714 /** 23715 * Alias for getSrc 23716 * @static 23717 */ 23718 fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; 23719 23720 /** 23721 * Creates an instance of fabric.Image from its object representation 23722 * @static 23723 * @param {Object} object Object to create an instance from 23724 * @param {Function} callback Callback to invoke when an image instance is created 23725 */ 23726 fabric.Image.fromObject = function (_object, callback) { 23727 var object = fabric.util.object.clone(_object); 23728 fabric.util.loadImage( 23729 object.src, 23730 function (img, isError) { 23731 if (isError) { 23732 callback && callback(null, true); 23733 return; 23734 } 23735 fabric.Image.prototype._initFilters.call( 23736 object, 23737 object.filters, 23738 function (filters) { 23739 object.filters = filters || []; 23740 fabric.Image.prototype._initFilters.call( 23741 object, 23742 [object.resizeFilter], 23743 function (resizeFilters) { 23744 object.resizeFilter = resizeFilters[0]; 23745 fabric.util.enlivenObjectEnlivables( 23746 object, 23747 object, 23748 function () { 23749 var image = new fabric.Image(img, object); 23750 callback(image, false); 23751 }, 23752 ); 23753 }, 23754 ); 23755 }, 23756 ); 23757 }, 23758 null, 23759 object.crossOrigin, 23760 ); 23761 }; 23762 23763 /** 23764 * Creates an instance of fabric.Image from an URL string 23765 * @static 23766 * @param {String} url URL to create an image from 23767 * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument). Second argument is a boolean indicating if an error occurred or not. 23768 * @param {Object} [imgOptions] Options object 23769 */ 23770 fabric.Image.fromURL = function (url, callback, imgOptions) { 23771 url = replaceCdnUrl(url); 23772 fabric.util.loadImage( 23773 url, 23774 function (img, isError) { 23775 callback && callback(new fabric.Image(img, imgOptions), isError); 23776 }, 23777 null, 23778 imgOptions && imgOptions.crossOrigin, 23779 ); 23780 }; 23781 23782 /* _FROM_SVG_START_ */ 23783 /** 23784 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) 23785 * @static 23786 * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} 23787 */ 23788 fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( 23789 "x y width height preserveAspectRatio xlink:href crossOrigin image-rendering".split( 23790 " ", 23791 ), 23792 ); 23793 23794 /** 23795 * Returns {@link fabric.Image} instance from an SVG element 23796 * @static 23797 * @param {SVGElement} element Element to parse 23798 * @param {Object} [options] Options object 23799 * @param {Function} callback Callback to execute when fabric.Image object is created 23800 * @return {fabric.Image} Instance of fabric.Image 23801 */ 23802 fabric.Image.fromElement = function (element, callback, options) { 23803 var parsedAttributes = fabric.parseAttributes( 23804 element, 23805 fabric.Image.ATTRIBUTE_NAMES, 23806 ); 23807 fabric.Image.fromURL( 23808 parsedAttributes["xlink:href"], 23809 callback, 23810 extend( 23811 options ? fabric.util.object.clone(options) : {}, 23812 parsedAttributes, 23813 ), 23814 ); 23815 }; 23816 /* _FROM_SVG_END_ */ 23817})(typeof exports !== "undefined" ? exports : this); 23818 23819fabric.util.object.extend( 23820 fabric.Object.prototype, 23821 /** @lends fabric.Object.prototype */ { 23822 /** 23823 * @private 23824 * @return {Number} angle value 23825 */ 23826 _getAngleValueForStraighten: function () { 23827 var angle = this.angle % 360; 23828 if (angle > 0) { 23829 return Math.round((angle - 1) / 90) * 90; 23830 } 23831 return Math.round(angle / 90) * 90; 23832 }, 23833 23834 /** 23835 * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer) 23836 * @return {fabric.Object} thisArg 23837 * @chainable 23838 */ 23839 straighten: function () { 23840 return this.rotate(this._getAngleValueForStraighten()); 23841 }, 23842 23843 /** 23844 * Same as {@link fabric.Object.prototype.straighten} but with animation 23845 * @param {Object} callbacks Object with callback functions 23846 * @param {Function} [callbacks.onComplete] Invoked on completion 23847 * @param {Function} [callbacks.onChange] Invoked on every step of animation 23848 * @return {fabric.Object} thisArg 23849 */ 23850 fxStraighten: function (callbacks) { 23851 callbacks = callbacks || {}; 23852 23853 var empty = function () {}, 23854 onComplete = callbacks.onComplete || empty, 23855 onChange = callbacks.onChange || empty, 23856 _this = this; 23857 23858 return fabric.util.animate({ 23859 target: this, 23860 startValue: this.get("angle"), 23861 endValue: this._getAngleValueForStraighten(), 23862 duration: this.FX_DURATION, 23863 onChange: function (value) { 23864 _this.rotate(value); 23865 onChange(); 23866 }, 23867 onComplete: function () { 23868 _this.setCoords(); 23869 onComplete(); 23870 }, 23871 }); 23872 }, 23873 }, 23874); 23875 23876fabric.util.object.extend( 23877 fabric.StaticCanvas.prototype, 23878 /** @lends fabric.StaticCanvas.prototype */ { 23879 /** 23880 * Straightens object, then rerenders canvas 23881 * @param {fabric.Object} object Object to straighten 23882 * @return {fabric.Canvas} thisArg 23883 * @chainable 23884 */ 23885 straightenObject: function (object) { 23886 object.straighten(); 23887 this.requestRenderAll(); 23888 return this; 23889 }, 23890 23891 /** 23892 * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated 23893 * @param {fabric.Object} object Object to straighten 23894 * @return {fabric.Canvas} thisArg 23895 */ 23896 fxStraightenObject: function (object) { 23897 return object.fxStraighten({ 23898 onChange: this.requestRenderAllBound, 23899 }); 23900 }, 23901 }, 23902); 23903 23904(function () { 23905 "use strict"; 23906 23907 /** 23908 * Tests if webgl supports certain precision 23909 * @param {WebGL} Canvas WebGL context to test on 23910 * @param {String} Precision to test can be any of following: 'lowp', 'mediump', 'highp' 23911 * @returns {Boolean} Whether the user's browser WebGL supports given precision. 23912 */ 23913 function testPrecision(gl, precision) { 23914 var fragmentSource = "precision " + precision + " float;\nvoid main(){}"; 23915 var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 23916 gl.shaderSource(fragmentShader, fragmentSource); 23917 gl.compileShader(fragmentShader); 23918 if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { 23919 return false;
23920 } 23921 return true; 23922 } 23923 23924 /** 23925 * Indicate whether this filtering backend is supported by the user's browser. 23926 * @param {Number} tileSize check if the tileSize is supported 23927 * @returns {Boolean} Whether the user's browser supports WebGL. 23928 */ 23929 fabric.isWebglSupported = function (tileSize) { 23930 if (fabric.isLikelyNode) { 23931 return false; 23932 } 23933 tileSize = tileSize || fabric.WebglFilterBackend.prototype.tileSize; 23934 var canvas = document.createElement("canvas"); 23935 var gl = 23936 canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); 23937 var isSupported = false; 23938 // eslint-disable-next-line 23939 if (gl) { 23940 fabric.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); 23941 isSupported = fabric.maxTextureSize >= tileSize; 23942 var precisions = ["highp", "mediump", "lowp"]; 23943 for (var i = 0; i < 3; i++) { 23944 if (testPrecision(gl, precisions[i])) { 23945 fabric.webGlPrecision = precisions[i]; 23946 break; 23947 } 23948 } 23949 } 23950 this.isSupported = isSupported; 23951 return isSupported; 23952 }; 23953 23954 fabric.WebglFilterBackend = WebglFilterBackend; 23955 23956 /** 23957 * WebGL filter backend. 23958 */ 23959 function WebglFilterBackend(options) { 23960 if (options && options.tileSize) { 23961 this.tileSize = options.tileSize; 23962 } 23963 this.setupGLContext(this.tileSize, this.tileSize); 23964 this.captureGPUInfo(); 23965 } 23966 23967 WebglFilterBackend.prototype = 23968 /** @lends fabric.WebglFilterBackend.prototype */ { 23969 tileSize: 2048, 23970 23971 /** 23972 * Experimental. This object is a sort of repository of help layers used to avoid 23973 * of recreating them during frequent filtering. If you are previewing a filter with 23974 * a slider you probably do not want to create help layers every filter step.
23975 * in this object there will be appended some canvases, created once, resized sometimes 23976 * cleared never. Clearing is left to the developer. 23977 **/ 23978 resources: {}, 23979 23980 /** 23981 * Setup a WebGL context suitable for filtering, and bind any needed event handlers. 23982 */ 23983 setupGLContext: function (width, height) { 23984 this.dispose(); 23985 this.createWebGLCanvas(width, height); 23986 // eslint-disable-next-line 23987 this.aPosition = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]); 23988 this.chooseFastestCopyGLTo2DMethod(width, height); 23989 }, 23990 23991 /** 23992 * Pick a method to copy data from GL context to 2d canvas. In some browsers using 23993 * putImageData is faster than drawImage for that specific operation. 23994 */ 23995 chooseFastestCopyGLTo2DMethod: function (width, height) { 23996 var canMeasurePerf = typeof window.performance !== "undefined", 23997 canUseImageData; 23998 try { 23999 new ImageData(1, 1); 24000 canUseImageData = true; 24001 } catch (e) { 24002 canUseImageData = false; 24003 } 24004 // eslint-disable-next-line no-undef 24005 var canUseArrayBuffer = typeof ArrayBuffer !== "undefined"; 24006 // eslint-disable-next-line no-undef 24007 var canUseUint8Clamped = typeof Uint8ClampedArray !== "undefined"; 24008 24009 if ( 24010 !( 24011 canMeasurePerf && 24012 canUseImageData && 24013 canUseArrayBuffer && 24014 canUseUint8Clamped 24015 ) 24016 ) { 24017 return; 24018 } 24019 24020 var targetCanvas = fabric.util.createCanvasElement(); 24021 // eslint-disable-next-line no-undef 24022 var imageBuffer = new ArrayBuffer(width * height * 4); 24023 if (fabric.forceGLPutImageData) { 24024 this.imageBuffer = imageBuffer; 24025 this.copyGLTo2D = copyGLTo2DPutImageData; 24026 return; 24027 } 24028 var testContext = { 24029 imageBuffer: imageBuffer, 24030 destinationWidth: width, 24031 destinationHeight: height, 24032 targetCanvas: targetCanvas, 24033 }; 24034 var startTime, drawImageTime, putImageDataTime; 24035 targetCanvas.width = width; 24036 targetCanvas.height = height; 24037 24038 startTime = window.performance.now(); 24039 copyGLTo2DDrawImage.call(testContext, this.gl, testContext); 24040 drawImageTime = window.performance.now() - startTime; 24041 24042 startTime = window.performance.now(); 24043 copyGLTo2DPutImageData.call(testContext, this.gl, testContext); 24044 putImageDataTime = window.performance.now() - startTime; 24045 24046 if (drawImageTime > putImageDataTime) { 24047 this.imageBuffer = imageBuffer; 24048 this.copyGLTo2D = copyGLTo2DPutImageData; 24049 } else { 24050 this.copyGLTo2D = copyGLTo2DDrawImage; 24051 } 24052 }, 24053 24054 /** 24055 * Create a canvas element and associated WebGL context and attaches them as 24056 * class properties to the GLFilterBackend class. 24057 */ 24058 createWebGLCanvas: function (width, height) { 24059 var canvas = fabric.util.createCanvasElement(); 24060 canvas.width = width; 24061 canvas.height = height; 24062 var glOptions = { 24063 alpha: true, 24064 premultipliedAlpha: false, 24065 depth: false, 24066 stencil: false, 24067 antialias: false, 24068 }, 24069 gl = canvas.getContext("webgl", glOptions); 24070 if (!gl) { 24071 gl = canvas.getContext("experimental-webgl", glOptions); 24072 } 24073 if (!gl) { 24074 return; 24075 } 24076 gl.clearColor(0, 0, 0, 0); 24077 // this canvas can fire webglcontextlost and webglcontextrestored 24078 this.canvas = canvas; 24079 this.gl = gl; 24080 }, 24081 24082 /** 24083 * Attempts to apply the requested filters to the source provided, drawing the filtered output 24084 * to the provided target canvas. 24085 * 24086 * @param {Array} filters The filters to apply. 24087 * @param {HTMLImageElement|HTMLCanvasElement} source The source to be filtered. 24088 * @param {Number} width The width of the source input. 24089 * @param {Number} height The height of the source input. 24090 * @param {HTMLCanvasElement} targetCanvas The destination for filtered output to be drawn. 24091 * @param {String|undefined} cacheKey A key used to cache resources related to the source. If 24092 * omitted, caching will be skipped. 24093 */ 24094 applyFilters: function ( 24095 filters, 24096 source, 24097 width, 24098 height, 24099 targetCanvas, 24100 cacheKey, 24101 ) { 24102 var gl = this.gl; 24103 var cachedTexture; 24104 if (cacheKey) { 24105 cachedTexture = this.getCachedTexture(cacheKey, source); 24106 } 24107 var pipelineState = { 24108 originalWidth: source.width || source.originalWidth, 24109 originalHeight: source.height || source.originalHeight, 24110 sourceWidth: width, 24111 sourceHeight: height, 24112 destinationWidth: width, 24113 destinationHeight: height, 24114 context: gl, 24115 sourceTexture: this.createTexture( 24116 gl, 24117 width, 24118 height, 24119 !cachedTexture && source, 24120 ), 24121 targetTexture: this.createTexture(gl, width, height), 24122 originalTexture: 24123 cachedTexture || 24124 this.createTexture(gl, width, height, !cachedTexture && source), 24125 passes: filters.length, 24126 webgl: true, 24127 aPosition: this.aPosition, 24128 programCache: this.programCache, 24129 pass: 0, 24130 filterBackend: this, 24131 targetCanvas: targetCanvas, 24132 }; 24133 var tempFbo = gl.createFramebuffer(); 24134 gl.bindFramebuffer(gl.FRAMEBUFFER, tempFbo);
24135 filters.forEach(function (filter) { 24136 filter && filter.applyTo(pipelineState); 24137 }); 24138 resizeCanvasIfNeeded(pipelineState); 24139 this.copyGLTo2D(gl, pipelineState); 24140 gl.bindTexture(gl.TEXTURE_2D, null); 24141 gl.deleteTexture(pipelineState.sourceTexture); 24142 gl.deleteTexture(pipelineState.targetTexture); 24143 gl.deleteFramebuffer(tempFbo); 24144 targetCanvas.getContext("2d").setTransform(1, 0, 0, 1, 0, 0); 24145 return pipelineState; 24146 }, 24147 24148 /** 24149 * Detach event listeners, remove references, and clean up caches. 24150 */ 24151 dispose: function () { 24152 if (this.canvas) { 24153 this.canvas = null; 24154 this.gl = null; 24155 } 24156 this.clearWebGLCaches(); 24157 }, 24158 24159 /** 24160 * Wipe out WebGL-related caches. 24161 */ 24162 clearWebGLCaches: function () { 24163 this.programCache = {}; 24164 this.textureCache = {}; 24165 }, 24166 24167 /** 24168 * Create a WebGL texture object. 24169 * 24170 * Accepts specific dimensions to initialize the texture to or a source image. 24171 * 24172 * @param {WebGLRenderingContext} gl The GL context to use for creating the texture. 24173 * @param {Number} width The width to initialize the texture at. 24174 * @param {Number} height The height to initialize the texture. 24175 * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source for the texture data. 24176 * @param {Number} filterType gl.NEAREST or gl.LINEAR usually, webgl numeri constants 24177 * @returns {WebGLTexture} 24178 */ 24179 createTexture: function ( 24180 gl, 24181 width, 24182 height, 24183 textureImageSource, 24184 filterType, 24185 ) { 24186 var texture = gl.createTexture(); 24187 gl.bindTexture(gl.TEXTURE_2D, texture); 24188 gl.texParameteri( 24189 gl.TEXTURE_2D, 24190 gl.TEXTURE_MAG_FILTER, 24191 filterType || gl.NEAREST, 24192 ); 24193 gl.texParameteri( 24194 gl.TEXTURE_2D, 24195 gl.TEXTURE_MIN_FILTER, 24196 filterType || gl.NEAREST, 24197 ); 24198 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 24199 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 24200 if (textureImageSource) { 24201 gl.texImage2D( 24202 gl.TEXTURE_2D, 24203 0, 24204 gl.RGBA, 24205 gl.RGBA, 24206 gl.UNSIGNED_BYTE, 24207 textureImageSource, 24208 ); 24209 } else { 24210 gl.texImage2D( 24211 gl.TEXTURE_2D, 24212 0, 24213 gl.RGBA, 24214 width, 24215 height, 24216 0, 24217 gl.RGBA, 24218 gl.UNSIGNED_BYTE, 24219 null, 24220 ); 24221 } 24222 return texture; 24223 }, 24224 24225 /** 24226 * Can be optionally used to get a texture from the cache array 24227 * 24228 * If an existing texture is not found, a new texture is created and cached. 24229 * 24230 * @param {String} uniqueId A cache key to use to find an existing texture. 24231 * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source to use to create the 24232 * texture cache entry if one does not already exist. 24233 */ 24234 getCachedTexture: function (uniqueId, textureImageSource) { 24235 if (this.textureCache[uniqueId]) { 24236 return this.textureCache[uniqueId]; 24237 } else { 24238 var texture = this.createTexture( 24239 this.gl, 24240 textureImageSource.width, 24241 textureImageSource.height, 24242 textureImageSource, 24243 ); 24244 this.textureCache[uniqueId] = texture; 24245 return texture; 24246 } 24247 }, 24248 24249 /** 24250 * Clear out cached resources related to a source image that has been 24251 * filtered previously. 24252 * 24253 * @param {String} cacheKey The cache key provided when the source image was filtered. 24254 */ 24255 evictCachesForKey: function (cacheKey) { 24256 if (this.textureCache[cacheKey]) { 24257 this.gl.deleteTexture(this.textureCache[cacheKey]); 24258 delete this.textureCache[cacheKey]; 24259 } 24260 }, 24261 24262 copyGLTo2D: copyGLTo2DDrawImage, 24263 24264 /** 24265 * Attempt to extract GPU information strings from a WebGL context. 24266 * 24267 * Useful information when debugging or blacklisting specific GPUs. 24268 * 24269 * @returns {Object} A GPU info object with renderer and vendor strings. 24270 */ 24271 captureGPUInfo: function () { 24272 if (this.gpuInfo) { 24273 return this.gpuInfo; 24274 } 24275 var gl = this.gl, 24276 gpuInfo = { renderer: "", vendor: "" }; 24277 if (!gl) { 24278 return gpuInfo; 24279 } 24280 var ext = gl.getExtension("WEBGL_debug_renderer_info"); 24281 if (ext) { 24282 var renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); 24283 var vendor = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL); 24284 if (renderer) { 24285 gpuInfo.renderer = renderer.toLowerCase(); 24286 } 24287 if (vendor) { 24288 gpuInfo.vendor = vendor.toLowerCase(); 24289 } 24290 } 24291 this.gpuInfo = gpuInfo; 24292 return gpuInfo; 24293 }, 24294 }; 24295})(); 24296 24297function resizeCanvasIfNeeded(pipelineState) { 24298 var targetCanvas = pipelineState.targetCanvas, 24299 width = targetCanvas.width, 24300 height = targetCanvas.height, 24301 dWidth = pipelineState.destinationWidth, 24302 dHeight = pipelineState.destinationHeight; 24303 24304 if (width !== dWidth || height !== dHeight) { 24305 targetCanvas.width = dWidth; 24306 targetCanvas.height = dHeight; 24307 } 24308} 24309 24310/** 24311 * Copy an input WebGL canvas on to an output 2D canvas. 24312 * 24313 * The WebGL canvas is assumed to be upside down, with the top-left pixel of the 24314 * desired output image appearing in the bottom-left corner of the WebGL canvas. 24315 * 24316 * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from. 24317 * @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to. 24318 * @param {Object} pipelineState The 2D target canvas to copy on to. 24319 */ 24320function copyGLTo2DDrawImage(gl, pipelineState) { 24321 var glCanvas = gl.canvas, 24322 targetCanvas = pipelineState.targetCanvas, 24323 ctx = targetCanvas.getContext("2d"); 24324 ctx.translate(0, targetCanvas.height); // move it down again 24325 ctx.scale(1, -1); // vertical flip 24326 // where is my image on the big glcanvas? 24327 var sourceY = glCanvas.height - targetCanvas.height; 24328 ctx.drawImage( 24329 glCanvas, 24330 0, 24331 sourceY, 24332 targetCanvas.width, 24333 targetCanvas.height, 24334 0, 24335 0, 24336 targetCanvas.width, 24337 targetCanvas.height, 24338 ); 24339} 24340 24341/** 24342 * Copy an input WebGL canvas on to an output 2D canvas using 2d canvas' putImageData 24343 * API. Measurably faster than using ctx.drawImage in Firefox (version 54 on OSX Sierra). 24344 * 24345 * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from. 24346 * @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to. 24347 * @param {Object} pipelineState The 2D target canvas to copy on to. 24348 */ 24349function copyGLTo2DPutImageData(gl, pipelineState) { 24350 var targetCanvas = pipelineState.targetCanvas, 24351 ctx = targetCanvas.getContext("2d"), 24352 dWidth = pipelineState.destinationWidth, 24353 dHeight = pipelineState.destinationHeight, 24354 numBytes = dWidth * dHeight * 4; 24355 24356 // eslint-disable-next-line no-undef 24357 var u8 = new Uint8Array(this.imageBuffer, 0, numBytes); 24358 // eslint-disable-next-line no-undef 24359 var u8Clamped = new Uint8ClampedArray(this.imageBuffer, 0, numBytes); 24360 24361 gl.readPixels(0, 0, dWidth, dHeight, gl.RGBA, gl.UNSIGNED_BYTE, u8); 24362 var imgData = new ImageData(u8Clamped, dWidth, dHeight); 24363 ctx.putImageData(imgData, 0, 0); 24364} 24365 24366(function () { 24367 "use strict"; 24368 24369 var noop = function () {}; 24370 24371 fabric.Canvas2dFilterBackend = Canvas2dFilterBackend; 24372 24373 /** 24374 * Canvas 2D filter backend. 24375 */ 24376 function Canvas2dFilterBackend() {} 24377 24378 Canvas2dFilterBackend.prototype = 24379 /** @lends fabric.Canvas2dFilterBackend.prototype */ { 24380 evictCachesForKey: noop, 24381 dispose: noop, 24382 clearWebGLCaches: noop, 24383 24384 /** 24385 * Experimental. This object is a sort of repository of help layers used to avoid 24386 * of recreating them during frequent filtering. If you are previewing a filter with 24387 * a slider you probably do not want to create help layers every filter step.
24388 * in this object there will be appended some canvases, created once, resized sometimes 24389 * cleared never. Clearing is left to the developer. 24390 **/ 24391 resources: {}, 24392 24393 /** 24394 * Apply a set of filters against a source image and draw the filtered output 24395 * to the provided destination canvas. 24396 * 24397 * @param {EnhancedFilter} filters The filter to apply. 24398 * @param {HTMLImageElement|HTMLCanvasElement} sourceElement The source to be filtered. 24399 * @param {Number} sourceWidth The width of the source input. 24400 * @param {Number} sourceHeight The height of the source input. 24401 * @param {HTMLCanvasElement} targetCanvas The destination for filtered output to be drawn. 24402 */ 24403 applyFilters: function ( 24404 filters, 24405 sourceElement, 24406 sourceWidth, 24407 sourceHeight, 24408 targetCanvas, 24409 ) { 24410 var ctx = targetCanvas.getContext("2d"); 24411 ctx.drawImage(sourceElement, 0, 0, sourceWidth, sourceHeight); 24412 var imageData = ctx.getImageData(0, 0, sourceWidth, sourceHeight); 24413 var originalImageData = ctx.getImageData( 24414 0, 24415 0, 24416 sourceWidth, 24417 sourceHeight, 24418 ); 24419 var pipelineState = { 24420 sourceWidth: sourceWidth, 24421 sourceHeight: sourceHeight, 24422 imageData: imageData, 24423 originalEl: sourceElement, 24424 originalImageData: originalImageData, 24425 canvasEl: targetCanvas, 24426 ctx: ctx, 24427 filterBackend: this, 24428 }; 24429 filters.forEach(function (filter) { 24430 filter.applyTo(pipelineState); 24431 }); 24432 if ( 24433 pipelineState.imageData.width !== sourceWidth || 24434 pipelineState.imageData.height !== sourceHeight 24435 ) { 24436 targetCanvas.width = pipelineState.imageData.width; 24437 targetCanvas.height = pipelineState.imageData.height; 24438 } 24439 ctx.putImageData(pipelineState.imageData, 0, 0); 24440 return pipelineState; 24441 }, 24442 }; 24443})(); 24444 24445/** 24446 * @namespace fabric.Image.filters 24447 * @memberOf fabric.Image 24448 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#image_filters} 24449 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 24450 */ 24451fabric.Image = fabric.Image || {}; 24452fabric.Image.filters = fabric.Image.filters || {}; 24453 24454/** 24455 * Root filter class from which all filter classes inherit from 24456 * @class fabric.Image.filters.BaseFilter 24457 * @memberOf fabric.Image.filters 24458 */ 24459fabric.Image.filters.BaseFilter = fabric.util.createClass( 24460 /** @lends fabric.Image.filters.BaseFilter.prototype */ { 24461 /** 24462 * Filter type 24463 * @param {String} type 24464 * @default 24465 */ 24466 type: "BaseFilter", 24467 24468 /** 24469 * Array of attributes to send with buffers. do not modify 24470 * @private 24471 */ 24472 24473 vertexSource: 24474 "attribute vec2 aPosition;\n" + 24475 "varying vec2 vTexCoord;\n" + 24476 "void main() {\n" + 24477 "vTexCoord = aPosition;\n" + 24478 "gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n" + 24479 "}", 24480 24481 fragmentSource: 24482 "precision highp float;\n" + 24483 "varying vec2 vTexCoord;\n" + 24484 "uniform sampler2D uTexture;\n" + 24485 "void main() {\n" + 24486 "gl_FragColor = texture2D(uTexture, vTexCoord);\n" + 24487 "}", 24488 24489 /** 24490 * Constructor 24491 * @param {Object} [options] Options object 24492 */ 24493 initialize: function (options) { 24494 if (options) { 24495 this.setOptions(options); 24496 } 24497 }, 24498 24499 /** 24500 * Sets filter's properties from options 24501 * @param {Object} [options] Options object 24502 */ 24503 setOptions: function (options) { 24504 for (var prop in options) { 24505 this[prop] = options[prop]; 24506 } 24507 }, 24508 24509 /** 24510 * Compile this filter's shader program. 24511 * 24512 * @param {WebGLRenderingContext} gl The GL canvas context to use for shader compilation. 24513 * @param {String} fragmentSource fragmentShader source for compilation 24514 * @param {String} vertexSource vertexShader source for compilation 24515 */ 24516 createProgram: function (gl, fragmentSource, vertexSource) { 24517 fragmentSource = fragmentSource || this.fragmentSource; 24518 vertexSource = vertexSource || this.vertexSource; 24519 if (fabric.webGlPrecision !== "highp") { 24520 fragmentSource = fragmentSource.replace( 24521 /precision highp float/g, 24522 "precision " + fabric.webGlPrecision + " float", 24523 ); 24524 } 24525 var vertexShader = gl.createShader(gl.VERTEX_SHADER); 24526 gl.shaderSource(vertexShader, vertexSource); 24527 gl.compileShader(vertexShader); 24528 if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { 24529 throw new Error( 24530 // eslint-disable-next-line prefer-template 24531 "Vertex shader compile error for " + 24532 this.type + 24533 ": " + 24534 gl.getShaderInfoLog(vertexShader), 24535 ); 24536 } 24537 24538 var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 24539 gl.shaderSource(fragmentShader, fragmentSource); 24540 gl.compileShader(fragmentShader); 24541 if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { 24542 throw new Error( 24543 // eslint-disable-next-line prefer-template 24544 "Fragment shader compile error for " + 24545 this.type + 24546 ": " + 24547 gl.getShaderInfoLog(fragmentShader), 24548 ); 24549 } 24550 24551 var program = gl.createProgram(); 24552 gl.attachShader(program, vertexShader); 24553 gl.attachShader(program, fragmentShader); 24554 gl.linkProgram(program); 24555 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 24556 throw new Error( 24557 // eslint-disable-next-line prefer-template 24558 'Shader link error for "${this.type}" ' + 24559 gl.getProgramInfoLog(program), 24560 ); 24561 } 24562 24563 var attributeLocations = this.getAttributeLocations(gl, program); 24564 var uniformLocations = this.getUniformLocations(gl, program) || {}; 24565 uniformLocations.uStepW = gl.getUniformLocation(program, "uStepW"); 24566 uniformLocations.uStepH = gl.getUniformLocation(program, "uStepH"); 24567 return { 24568 program: program, 24569 attributeLocations: attributeLocations, 24570 uniformLocations: uniformLocations, 24571 }; 24572 }, 24573 24574 /** 24575 * Return a map of attribute names to WebGLAttributeLocation objects. 24576 * 24577 * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. 24578 * @param {WebGLShaderProgram} program The shader program from which to take attribute locations. 24579 * @returns {Object} A map of attribute names to attribute locations. 24580 */ 24581 getAttributeLocations: function (gl, program) { 24582 return { 24583 aPosition: gl.getAttribLocation(program, "aPosition"), 24584 }; 24585 }, 24586 24587 /** 24588 * Return a map of uniform names to WebGLUniformLocation objects. 24589 * 24590 * Intended to be overridden by subclasses. 24591 * 24592 * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. 24593 * @param {WebGLShaderProgram} program The shader program from which to take uniform locations. 24594 * @returns {Object} A map of uniform names to uniform locations. 24595 */ 24596 getUniformLocations: function (/* gl, program */) { 24597 // in case i do not need any special uniform i need to return an empty object 24598 return {}; 24599 }, 24600 24601 /** 24602 * Send attribute data from this filter to its shader program on the GPU. 24603 * 24604 * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. 24605 * @param {Object} attributeLocations A map of shader attribute names to their locations. 24606 */ 24607 sendAttributeData: function (gl, attributeLocations, aPositionData) { 24608 var attributeLocation = attributeLocations.aPosition; 24609 var buffer = gl.createBuffer(); 24610 gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 24611 gl.enableVertexAttribArray(attributeLocation); 24612 gl.vertexAttribPointer(attributeLocation, 2, gl.FLOAT, false, 0, 0); 24613 gl.bufferData(gl.ARRAY_BUFFER, aPositionData, gl.STATIC_DRAW); 24614 }, 24615 24616 _setupFrameBuffer: function (options) { 24617 var gl = options.context, 24618 width, 24619 height; 24620 if (options.passes > 1) { 24621 width = options.destinationWidth; 24622 height = options.destinationHeight; 24623 if (options.sourceWidth !== width || options.sourceHeight !== height) { 24624 gl.deleteTexture(options.targetTexture); 24625 options.targetTexture = options.filterBackend.createTexture( 24626 gl, 24627 width, 24628 height, 24629 ); 24630 } 24631 gl.framebufferTexture2D( 24632 gl.FRAMEBUFFER, 24633 gl.COLOR_ATTACHMENT0, 24634 gl.TEXTURE_2D, 24635 options.targetTexture, 24636 0, 24637 ); 24638 } else { 24639 // draw last filter on canvas and not to framebuffer. 24640 gl.bindFramebuffer(gl.FRAMEBUFFER, null); 24641 gl.finish(); 24642 } 24643 }, 24644 24645 _swapTextures: function (options) { 24646 options.passes--; 24647 options.pass++; 24648 var temp = options.targetTexture; 24649 options.targetTexture = options.sourceTexture; 24650 options.sourceTexture = temp; 24651 }, 24652 24653 /** 24654 * Generic isNeutral implementation for one parameter based filters. 24655 * Used only in image applyFilters to discard filters that will not have an effect 24656 * on the image 24657 * Other filters may need their own version ( ColorMatrix, HueRotation, gamma,
vendor: 6,264 bytes, lines 24657-24834
24657ComposedFilter ) 24658 * @param {Object} options 24659 **/ 24660 isNeutralState: function (/* options */) { 24661 var main = this.mainParameter, 24662 _class = fabric.Image.filters[this.type].prototype; 24663 if (main) { 24664 if (Array.isArray(_class[main])) { 24665 for (var i = _class[main].length; i--; ) { 24666 if (this[main][i] !== _class[main][i]) { 24667 return false; 24668 } 24669 } 24670 return true; 24671 } else { 24672 return _class[main] === this[main]; 24673 } 24674 } else { 24675 return false; 24676 } 24677 }, 24678 24679 /** 24680 * Apply this filter to the input image data provided. 24681 * 24682 * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. 24683 * 24684 * @param {Object} options 24685 * @param {Number} options.passes The number of filters remaining to be executed 24686 * @param {Boolean} options.webgl Whether to use webgl to render the filter. 24687 * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. 24688 * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. 24689 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 24690 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 24691 */ 24692 applyTo: function (options) { 24693 if (options.webgl) { 24694 this._setupFrameBuffer(options); 24695 this.applyToWebGL(options); 24696 this._swapTextures(options); 24697 } else { 24698 this.applyTo2d(options); 24699 } 24700 }, 24701 24702 /** 24703 * Retrieves the cached shader. 24704 * @param {Object} options 24705 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 24706 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 24707 */ 24708 retrieveShader: function (options) { 24709 if (!options.programCache.hasOwnProperty(this.type)) { 24710 options.programCache[this.type] = this.createProgram(options.context); 24711 } 24712 return options.programCache[this.type]; 24713 }, 24714 24715 /** 24716 * Apply this filter using webgl. 24717 * 24718 * @param {Object} options 24719 * @param {Number} options.passes The number of filters remaining to be executed 24720 * @param {Boolean} options.webgl Whether to use webgl to render the filter. 24721 * @param {WebGLTexture} options.originalTexture The texture of the original input image. 24722 * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. 24723 * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. 24724 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 24725 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 24726 */ 24727 applyToWebGL: function (options) { 24728 var gl = options.context; 24729 var shader = this.retrieveShader(options); 24730 if (options.pass === 0 && options.originalTexture) { 24731 gl.bindTexture(gl.TEXTURE_2D, options.originalTexture); 24732 } else { 24733 gl.bindTexture(gl.TEXTURE_2D, options.sourceTexture); 24734 } 24735 gl.useProgram(shader.program); 24736 this.sendAttributeData(gl, shader.attributeLocations, options.aPosition); 24737 24738 gl.uniform1f(shader.uniformLocations.uStepW, 1 / options.sourceWidth); 24739 gl.uniform1f(shader.uniformLocations.uStepH, 1 / options.sourceHeight); 24740 24741 this.sendUniformData(gl, shader.uniformLocations); 24742 gl.viewport(0, 0, options.destinationWidth, options.destinationHeight); 24743 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 24744 }, 24745 24746 bindAdditionalTexture: function (gl, texture, textureUnit) { 24747 gl.activeTexture(textureUnit); 24748 gl.bindTexture(gl.TEXTURE_2D, texture); 24749 // reset active texture to 0 as usual 24750 gl.activeTexture(gl.TEXTURE0); 24751 }, 24752 24753 unbindAdditionalTexture: function (gl, textureUnit) { 24754 gl.activeTexture(textureUnit); 24755 gl.bindTexture(gl.TEXTURE_2D, null); 24756 gl.activeTexture(gl.TEXTURE0); 24757 }, 24758 24759 getMainParameter: function () { 24760 return this[this.mainParameter]; 24761 }, 24762 24763 setMainParameter: function (value) { 24764 this[this.mainParameter] = value; 24765 }, 24766 24767 /** 24768 * Send uniform data from this filter to its shader program on the GPU. 24769 * 24770 * Intended to be overridden by subclasses. 24771 * 24772 * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. 24773 * @param {Object} uniformLocations A map of shader uniform names to their locations. 24774 */ 24775 sendUniformData: function (/* gl, uniformLocations */) { 24776 // Intentionally left blank. Override me in subclasses. 24777 }, 24778 24779 /** 24780 * If needed by a 2d filter, this functions can create an helper canvas to be used 24781 * remember that options.targetCanvas is available for use till end of chain. 24782 */ 24783 createHelpLayer: function (options) { 24784 if (!options.helpLayer) { 24785 var helpLayer = document.createElement("canvas"); 24786 helpLayer.width = options.sourceWidth; 24787 helpLayer.height = options.sourceHeight; 24788 options.helpLayer = helpLayer; 24789 } 24790 }, 24791 24792 /** 24793 * Returns object representation of an instance 24794 * @return {Object} Object representation of an instance 24795 */ 24796 toObject: function () { 24797 var object = { type: this.type }, 24798 mainP = this.mainParameter; 24799 if (mainP) { 24800 object[mainP] = this[mainP]; 24801 } 24802 return object; 24803 }, 24804 24805 /** 24806 * Returns a JSON representation of an instance 24807 * @return {Object} JSON 24808 */ 24809 toJSON: function () { 24810 // delegate, not alias 24811 return this.toObject(); 24812 }, 24813 }, 24814); 24815 24816fabric.Image.filters.BaseFilter.fromObject = function (object, callback) { 24817 var filter = new fabric.Image.filters[object.type](object); 24818 callback && callback(filter); 24819 return filter; 24820}; 24821 24822(function (global) { 24823 "use strict"; 24824 24825 var fabric = global.fabric || (global.fabric = {}), 24826 filters = fabric.Image.filters, 24827 createClass = fabric.util.createClass; 24828 24829 /** 24830 * Color Matrix filter class 24831 * @class fabric.Image.filters.ColorMatrix 24832 * @memberOf fabric.Image.filters 24833 * @extends fabric.Image.filters.BaseFilter 24834 * @see {@link fabric.Image.filters.ColorMatrix#initialize}
24834 for constructor definition 24835 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 24836 * @see {@Link http://www.webwasp.co.uk/tutorials/219/Color_Matrix_Filter.php} 24837 * @see {@Link http://phoboslab.org/log/2013/11/fast-image-filters-with-webgl} 24838 * @example <caption>Kodachrome filter</caption> 24839 * var filter = new fabric.Image.filters.ColorMatrix({ 24840 * matrix: [ 24841 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, 24842 -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, 24843 -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, 24844 0, 0, 0, 1, 0 24845 ] 24846 * }); 24847 * object.filters.push(filter); 24848 * object.applyFilters(); 24849 */ 24850 filters.ColorMatrix = createClass( 24851 filters.BaseFilter, 24852 /** @lends fabric.Image.filters.ColorMatrix.prototype */ { 24853 /** 24854 * Filter type 24855 * @param {String} type 24856 * @default 24857 */ 24858 type: "ColorMatrix", 24859 24860 fragmentSource: 24861 "precision highp float;\n" + 24862 "uniform sampler2D uTexture;\n" + 24863 "varying vec2 vTexCoord;\n" + 24864 "uniform mat4 uColorMatrix;\n" + 24865 "uniform vec4 uConstants;\n" + 24866 "void main() {\n" + 24867 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 24868 "color *= uColorMatrix;\n" + 24869 "color += uConstants;\n" + 24870 "gl_FragColor = color;\n" + 24871 "}", 24872 24873 /** 24874 * Colormatrix for pixels. 24875 * array of 20 floats. Numbers in positions 4, 9, 14, 19 loose meaning 24876 * outside the -1, 1 range. 24877 * 0.0039215686 is the part of 1 that get translated to 1 in 2d 24878 * @param {Array} matrix array of 20 numbers. 24879 * @default 24880 */ 24881 matrix: [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0], 24882 24883 mainParameter: "matrix", 24884 24885 /** 24886 * Lock the colormatrix on the color part, skipping alpha, mainly for non webgl scenario 24887 * to save some calculation 24888 * @type Boolean 24889 * @default true 24890 */ 24891 colorsOnly: true, 24892 24893 /** 24894 * Constructor 24895 * @param {Object} [options] Options object 24896 */ 24897 initialize: function (options) { 24898 this.callSuper("initialize", options); 24899 // create a new array instead mutating the prototype with push 24900 this.matrix = this.matrix.slice(0); 24901 }, 24902 24903 /** 24904 * Apply the ColorMatrix operation to a Uint8Array representing the pixels of an image. 24905 * 24906 * @param {Object} options 24907 * @param {ImageData} options.imageData The Uint8Array to be filtered. 24908 */ 24909 applyTo2d: function (options) { 24910 var imageData = options.imageData, 24911 data = imageData.data, 24912 iLen = data.length, 24913 m = this.matrix, 24914 r, 24915 g, 24916 b, 24917 a, 24918 i, 24919 colorsOnly = this.colorsOnly; 24920 24921 for (i = 0; i < iLen; i += 4) { 24922 r = data[i]; 24923 g = data[i + 1]; 24924 b = data[i + 2]; 24925 if (colorsOnly) { 24926 data[i] = r * m[0] + g * m[1] + b * m[2] + m[4] * 255; 24927 data[i + 1] = r * m[5] + g * m[6] + b * m[7] + m[9] * 255; 24928 data[i + 2] = r * m[10] + g * m[11] + b * m[12] + m[14] * 255; 24929 } else { 24930 a = data[i + 3]; 24931 data[i] = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4] * 255; 24932 data[i + 1] = 24933 r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9] * 255; 24934 data[i + 2] = 24935 r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14] * 255; 24936 data[i + 3] = 24937 r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19] * 255; 24938 } 24939 } 24940 }, 24941 24942 /** 24943 * Return WebGL uniform locations for this filter's shader. 24944 * 24945 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 24946 * @param {WebGLShaderProgram} program This filter's compiled shader program. 24947 */ 24948 getUniformLocations: function (gl, program) { 24949 return { 24950 uColorMatrix: gl.getUniformLocation(program, "uColorMatrix"), 24951 uConstants: gl.getUniformLocation(program, "uConstants"), 24952 }; 24953 }, 24954 24955 /** 24956 * Send data from this filter to its shader program's uniforms. 24957 * 24958 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 24959 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 24960 */ 24961 sendUniformData: function (gl, uniformLocations) { 24962 var m = this.matrix, 24963 matrix = [ 24964 m[0], 24965 m[1], 24966 m[2], 24967 m[3], 24968 m[5], 24969 m[6], 24970 m[7], 24971 m[8],
24972 m[10], 24973 m[11], 24974 m[12], 24975 m[13], 24976 m[15], 24977 m[16], 24978 m[17], 24979 m[18], 24980 ], 24981 constants = [m[4], m[9], m[14], m[19]]; 24982 gl.uniformMatrix4fv(uniformLocations.uColorMatrix, false, matrix); 24983 gl.uniform4fv(uniformLocations.uConstants, constants); 24984 }, 24985 }, 24986 ); 24987 24988 /** 24989 * Returns filter instance from an object representation 24990 * @static 24991 * @param {Object} object Object to create an instance from 24992 * @param {function} [callback] function to invoke after filter creation 24993 * @return {fabric.Image.filters.ColorMatrix} Instance of fabric.Image.filters.ColorMatrix 24994 */ 24995 fabric.Image.filters.ColorMatrix.fromObject = 24996 fabric.Image.filters.BaseFilter.fromObject; 24997})(typeof exports !== "undefined" ? exports : this); 24998 24999(function (global) { 25000 "use strict"; 25001 25002 var fabric = global.fabric || (global.fabric = {}), 25003 filters = fabric.Image.filters, 25004 createClass = fabric.util.createClass; 25005 25006 /** 25007 * Brightness filter class 25008 * @class fabric.Image.filters.Brightness 25009 * @memberOf fabric.Image.filters 25010 * @extends fabric.Image.filters.BaseFilter 25011 * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition 25012 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 25013 * @example 25014 * var filter = new fabric.Image.filters.Brightness({ 25015 * brightness: 0.05 25016 * }); 25017 * object.filters.push(filter); 25018 * object.applyFilters(); 25019 */ 25020 filters.Brightness = createClass( 25021 filters.BaseFilter, 25022 /** @lends fabric.Image.filters.Brightness.prototype */ { 25023 /** 25024 * Filter type 25025 * @param {String} type 25026 * @default 25027 */ 25028 type: "Brightness", 25029 25030 /** 25031 * Fragment source for the brightness program 25032 */ 25033 fragmentSource: 25034 "precision highp float;\n" + 25035 "uniform sampler2D uTexture;\n" + 25036 "uniform float uBrightness;\n" + 25037 "varying vec2 vTexCoord;\n" + 25038 "void main() {\n" + 25039 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 25040 "color.rgb += uBrightness;\n" + 25041 "gl_FragColor = color;\n" + 25042 "}", 25043 25044 /** 25045 * Brightness value, from -1 to 1. 25046 * translated to -255 to 255 for 2d 25047 * 0.0039215686 is the part of 1 that get translated to 1 in 2d 25048 * @param {Number} brightness 25049 * @default 25050 */ 25051 brightness: 0, 25052 25053 /** 25054 * Describe the property that is the filter parameter 25055 * @param {String} m 25056 * @default 25057 */ 25058 mainParameter: "brightness", 25059 25060 /** 25061 * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. 25062 * 25063 * @param {Object} options 25064 * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. 25065 */ 25066 applyTo2d: function (options) { 25067 if (this.brightness === 0) { 25068 return; 25069 } 25070 var imageData = options.imageData, 25071 data = imageData.data, 25072 i, 25073 len = data.length, 25074 brightness = Math.round(this.brightness * 255); 25075 for (i = 0; i < len; i += 4) { 25076 data[i] = data[i] + brightness; 25077 data[i + 1] = data[i + 1] + brightness; 25078 data[i + 2] = data[i + 2] + brightness; 25079 } 25080 }, 25081 25082 /** 25083 * Return WebGL uniform locations for this filter's shader. 25084 * 25085 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 25086 * @param {WebGLShaderProgram} program This filter's compiled shader program. 25087 */ 25088 getUniformLocations: function (gl, program) { 25089 return { 25090 uBrightness: gl.getUniformLocation(program, "uBrightness"), 25091 }; 25092 }, 25093 25094 /** 25095 * Send data from this filter to its shader program's uniforms. 25096 * 25097 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 25098 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 25099 */ 25100 sendUniformData: function (gl, uniformLocations) { 25101 gl.uniform1f(uniformLocations.uBrightness, this.brightness); 25102 }, 25103 }, 25104 ); 25105 25106 /** 25107 * Returns filter instance from an object representation 25108 * @static 25109 * @param {Object} object Object to create an instance from 25110 * @param {function} [callback] to be invoked after filter creation 25111 * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness 25112 */ 25113 fabric.Image.filters.Brightness.fromObject = 25114 fabric.Image.filters.BaseFilter.fromObject; 25115})(typeof exports !== "undefined" ? exports : this); 25116 25117(function (global) { 25118 "use strict"; 25119 25120 var fabric = global.fabric || (global.fabric = {}), 25121 extend = fabric.util.object.extend, 25122 filters = fabric.Image.filters, 25123 createClass = fabric.util.createClass; 25124 25125 /** 25126 * Adapted from <a href="http://www.html5rocks.com/en/tutorials/canvas/imagefilters/">html5rocks article</a> 25127 * @class fabric.Image.filters.Convolute 25128 * @memberOf fabric.Image.filters 25129 * @extends fabric.Image.filters.BaseFilter 25130 * @see {@link fabric.Image.filters.Convolute#initialize}
25130 for constructor definition 25131 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 25132 * @example <caption>Sharpen filter</caption> 25133 * var filter = new fabric.Image.filters.Convolute({ 25134 * matrix: [ 0, -1, 0, 25135 * -1, 5, -1, 25136 * 0, -1, 0 ] 25137 * }); 25138 * object.filters.push(filter); 25139 * object.applyFilters(); 25140 * canvas.renderAll(); 25141 * @example <caption>Blur filter</caption> 25142 * var filter = new fabric.Image.filters.Convolute({ 25143 * matrix: [ 1/9, 1/9, 1/9, 25144 * 1/9, 1/9, 1/9, 25145 * 1/9, 1/9, 1/9 ] 25146 * }); 25147 * object.filters.push(filter); 25148 * object.applyFilters(); 25149 * canvas.renderAll(); 25150 * @example <caption>Emboss filter</caption> 25151 * var filter = new fabric.Image.filters.Convolute({ 25152 * matrix: [ 1, 1, 1, 25153 * 1, 0.7, -1, 25154 * -1, -1, -1 ] 25155 * }); 25156 * object.filters.push(filter); 25157 * object.applyFilters(); 25158 * canvas.renderAll(); 25159 * @example <caption>Emboss filter with opaqueness</caption> 25160 * var filter = new fabric.Image.filters.Convolute({ 25161 * opaque: true, 25162 * matrix: [ 1, 1, 1, 25163 * 1, 0.7, -1, 25164 * -1, -1, -1 ] 25165 * }); 25166 * object.filters.push(filter); 25167 * object.applyFilters(); 25168 * canvas.renderAll(); 25169 */ 25170 filters.Convolute = createClass( 25171 filters.BaseFilter, 25172 /** @lends fabric.Image.filters.Convolute.prototype */ { 25173 /** 25174 * Filter type 25175 * @param {String} type 25176 * @default 25177 */ 25178 type: "Convolute", 25179 25180 /* 25181 * Opaque value (true/false) 25182 */ 25183 opaque: false, 25184 25185 /* 25186 * matrix for the filter, max 9x9 25187 */ 25188 matrix: [0, 0, 0, 0, 1, 0, 0, 0, 0], 25189 25190 /** 25191 * Fragment source for the brightness program 25192 */ 25193 fragmentSource: { 25194 Convolute_3_1: 25195 "precision highp float;\n" + 25196 "uniform sampler2D uTexture;\n" + 25197 "uniform float uMatrix[9];\n" + 25198 "uniform float uStepW;\n" + 25199 "uniform float uStepH;\n" + 25200 "varying vec2 vTexCoord;\n" + 25201 "void main() {\n" + 25202 "vec4 color = vec4(0, 0, 0, 0);\n" + 25203 "for (float h = 0.0; h < 3.0; h+=1.0) {\n" + 25204 "for (float w = 0.0; w < 3.0; w+=1.0) {\n" + 25205 "vec2 matrixPos = vec2(uStepW * (w - 1), uStepH * (h - 1));\n" + 25206 "color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 3.0 + w)];\n" + 25207 "}\n" + 25208 "}\n" + 25209 "gl_FragColor = color;\n" + 25210 "}", 25211 Convolute_3_0: 25212 "precision highp float;\n" + 25213 "uniform sampler2D uTexture;\n" + 25214 "uniform float uMatrix[9];\n" + 25215 "uniform float uStepW;\n" + 25216 "uniform float uStepH;\n" + 25217 "varying vec2 vTexCoord;\n" + 25218 "void main() {\n" + 25219 "vec4 color = vec4(0, 0, 0, 1);\n" + 25220 "for (float h = 0.0; h < 3.0; h+=1.0) {\n" + 25221 "for (float w = 0.0; w < 3.0; w+=1.0) {\n" + 25222 "vec2 matrixPos = vec2(uStepW * (w - 1.0), uStepH * (h - 1.0));\n" + 25223 "color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 3.0 + w)];\n" + 25224 "}\n" + 25225 "}\n" + 25226 "float alpha = texture2D(uTexture, vTexCoord).a;\n" + 25227 "gl_FragColor = color;\n" + 25228 "gl_FragColor.a = alpha;\n" + 25229 "}", 25230 Convolute_5_1: 25231 "precision highp float;\n" + 25232 "uniform sampler2D uTexture;\n" + 25233 "uniform float uMatrix[25];\n" + 25234 "uniform float uStepW;\n" + 25235 "uniform float uStepH;\n" + 25236 "varying vec2 vTexCoord;\n" + 25237 "void main() {\n" + 25238 "vec4 color = vec4(0, 0, 0, 0);\n" + 25239 "for (float h = 0.0; h < 5.0; h+=1.0) {\n" + 25240 "for (float w = 0.0; w < 5.0; w+=1.0) {\n" + 25241 "vec2 matrixPos = vec2(uStepW * (w - 2.0), uStepH * (h - 2.0));\n" + 25242 "color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 5.0 + w)];\n" + 25243 "}\n" + 25244 "}\n" + 25245 "gl_FragColor = color;\n" + 25246 "}", 25247 Convolute_5_0: 25248 "precision highp float;\n" + 25249 "uniform sampler2D uTexture;\n" + 25250 "uniform float uMatrix[25];\n" + 25251 "uniform float uStepW;\n" + 25252 "uniform float uStepH;\n" + 25253 "varying vec2 vTexCoord;\n" + 25254 "void main() {\n" + 25255 "vec4 color = vec4(0, 0, 0, 1);\n" + 25256 "for (float h = 0.0; h < 5.0; h+=1.0) {\n" + 25257 "for (float w = 0.0; w < 5.0; w+=1.0) {\n" + 25258 "vec2 matrixPos = vec2(uStepW * (w - 2.0), uStepH * (h - 2.0));\n" + 25259 "color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 5.0 + w)];\n" + 25260 "}\n" + 25261 "}\n" + 25262 "float alpha = texture2D(uTexture, vTexCoord).a;\n" + 25263 "gl_FragColor = color;\n" + 25264 "gl_FragColor.a = alpha;\n" + 25265 "}", 25266 Convolute_7_1: 25267 "precision highp float;\n" + 25268 "uniform sampler2D uTexture;\n" + 25269 "uniform float uMatrix[49];\n" + 25270 "uniform float uStepW;\n" + 25271 "uniform float uStepH;\n" + 25272 "varying vec2 vTexCoord;\n" + 25273 "void main() {\n" + 25274 "vec4 color = vec4(0, 0, 0, 0);\n" + 25275 "for (float h = 0.0; h < 7.0; h+=1.0) {\n" + 25276 "for (float w = 0.0; w < 7.0; w+=1.0) {\n" + 25277 "vec2 matrixPos = vec2(uStepW * (w - 3.0), uStepH * (h - 3.0));\n" + 25278 "color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 7.0 + w)];\n" + 25279 "}\n" + 25280 "}\n" + 25281 "gl_FragColor = color;\n" + 25282 "}", 25283 Convolute_7_0: 25284 "precision highp float;\n" + 25285 "uniform sampler2D uTexture;\n" + 25286 "uniform float uMatrix[49];\n" + 25287 "uniform float uStepW;\n" + 25288 "uniform float uStepH;\n" + 25289 "varying vec2 vTexCoord;\n" + 25290 "void main() {\n" + 25291 "vec4 color = vec4(0, 0, 0, 1);\n" + 25292 "for (float h = 0.0; h < 7.0; h+=1.0) {\n" + 25293 "for (float w = 0.0; w < 7.0; w+=1.0) {\n" + 25294 "vec2 matrixPos = vec2(uStepW * (w - 3.0), uStepH * (h - 3.0));\n" + 25295 "color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 7.0 + w)];\n" + 25296 "}\n" + 25297 "}\n" + 25298 "float alpha = texture2D(uTexture, vTexCoord).a;\n" + 25299 "gl_FragColor = color;\n" + 25300 "gl_FragColor.a = alpha;\n" + 25301 "}", 25302 Convolute_9_1: 25303 "precision highp float;\n" + 25304 "uniform sampler2D uTexture;\n" + 25305 "uniform float uMatrix[81];\n" + 25306 "uniform float uStepW;\n" + 25307 "uniform float uStepH;\n" + 25308 "varying vec2 vTexCoord;\n" + 25309 "void main() {\n" + 25310 "vec4 color = vec4(0, 0, 0, 0);\n" + 25311 "for (float h = 0.0; h < 9.0; h+=1.0) {\n" + 25312 "for (float w = 0.0; w < 9.0; w+=1.0) {\n" + 25313 "vec2 matrixPos = vec2(uStepW * (w - 4.0), uStepH * (h - 4.0));\n" + 25314 "color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 9.0 + w)];\n" + 25315 "}\n" + 25316 "}\n" + 25317 "gl_FragColor = color;\n" + 25318 "}", 25319 Convolute_9_0: 25320 "precision highp float;\n" + 25321 "uniform sampler2D uTexture;\n" + 25322 "uniform float uMatrix[81];\n" + 25323 "uniform float uStepW;\n" + 25324 "uniform float uStepH;\n" + 25325 "varying vec2 vTexCoord;\n" + 25326 "void main() {\n" + 25327 "vec4 color = vec4(0, 0, 0, 1);\n" + 25328 "for (float h = 0.0; h < 9.0; h+=1.0) {\n" + 25329 "for (float w = 0.0; w < 9.0; w+=1.0) {\n" + 25330 "vec2 matrixPos = vec2(uStepW * (w - 4.0), uStepH * (h - 4.0));\n" + 25331 "color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 9.0 + w)];\n" + 25332 "}\n" + 25333 "}\n" + 25334 "float alpha = texture2D(uTexture, vTexCoord).a;\n" + 25335 "gl_FragColor = color;\n" + 25336 "gl_FragColor.a = alpha;\n" + 25337 "}", 25338 }, 25339 25340 /** 25341 * Constructor 25342 * @memberOf fabric.Image.filters.Convolute.prototype 25343 * @param {Object} [options] Options object 25344 * @param {Boolean} [options.opaque=false] Opaque value (true/false) 25345 * @param {Array} [options.matrix] Filter matrix 25346 */ 25347 25348 /** 25349 * Retrieves the cached shader. 25350 * @param {Object} options 25351 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 25352 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 25353 */ 25354 retrieveShader: function (options) { 25355 var size = Math.sqrt(this.matrix.length); 25356 var cacheKey = this.type + "_" + size + "_" + (this.opaque ? 1 : 0); 25357 var shaderSource = this.fragmentSource[cacheKey]; 25358 if (!options.programCache.hasOwnProperty(cacheKey)) { 25359 options.programCache[cacheKey] = this.createProgram( 25360 options.context, 25361 shaderSource, 25362 ); 25363 } 25364 return options.programCache[cacheKey]; 25365 }, 25366 25367 /** 25368 * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. 25369 * 25370 * @param {Object} options 25371 * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. 25372 */ 25373 applyTo2d: function (options) { 25374 var imageData = options.imageData, 25375 data = imageData.data, 25376 weights = this.matrix, 25377 side = Math.round(Math.sqrt(weights.length)), 25378 halfSide = Math.floor(side / 2), 25379 sw = imageData.width, 25380 sh = imageData.height, 25381 output = options.ctx.createImageData(sw, sh), 25382 dst = output.data, 25383 // go through the destination image pixels 25384 alphaFac = this.opaque ? 1 : 0, 25385 r, 25386 g, 25387 b, 25388 a, 25389 dstOff, 25390 scx, 25391 scy, 25392 srcOff, 25393 wt, 25394 x, 25395 y, 25396 cx, 25397 cy; 25398 25399 for (y = 0; y < sh; y++) { 25400 for (x = 0; x < sw; x++) { 25401 dstOff = (y * sw + x) * 4; 25402 // calculate the weighed sum of the source image pixels that 25403 // fall under the convolution matrix 25404 r = 0; 25405 g = 0; 25406 b = 0; 25407 a = 0; 25408 25409 for (cy = 0; cy < side; cy++) { 25410 for (cx = 0; cx < side; cx++) { 25411 scy = y + cy - halfSide; 25412 scx = x + cx - halfSide; 25413 25414 // eslint-disable-next-line max-depth 25415 if (scy < 0 || scy >= sh || scx < 0 || scx >= sw) { 25416 continue; 25417 } 25418 25419 srcOff = (scy * sw + scx) * 4; 25420 wt = weights[cy * side + cx]; 25421 25422 r += data[srcOff] * wt; 25423 g += data[srcOff + 1] * wt; 25424 b += data[srcOff + 2] * wt; 25425 // eslint-disable-next-line max-depth 25426 if (!alphaFac) { 25427 a += data[srcOff + 3] * wt; 25428 } 25429 } 25430 } 25431 dst[dstOff] = r; 25432 dst[dstOff + 1] = g; 25433 dst[dstOff + 2] = b; 25434 if (!alphaFac) { 25435 dst[dstOff + 3] = a; 25436 } else { 25437 dst[dstOff + 3] = data[dstOff + 3]; 25438 } 25439 } 25440 } 25441 options.imageData = output; 25442 }, 25443 25444 /** 25445 * Return WebGL uniform locations for this filter's shader. 25446 * 25447 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 25448 * @param {WebGLShaderProgram} program This filter's compiled shader program. 25449 */ 25450 getUniformLocations: function (gl, program) { 25451 return { 25452 uMatrix: gl.getUniformLocation(program, "uMatrix"), 25453 uOpaque: gl.getUniformLocation(program, "uOpaque"), 25454 uHalfSize: gl.getUniformLocation(program, "uHalfSize"), 25455 uSize: gl.getUniformLocation(program, "uSize"), 25456 }; 25457 }, 25458 25459 /** 25460 * Send data from this filter to its shader program's uniforms. 25461 * 25462 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 25463 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 25464 */ 25465 sendUniformData: function (gl, uniformLocations) {
vendor: 5,942 bytes, lines 25466-25640
25466 gl.uniform1fv(uniformLocations.uMatrix, this.matrix); 25467 }, 25468 25469 /** 25470 * Returns object representation of an instance 25471 * @return {Object} Object representation of an instance 25472 */ 25473 toObject: function () { 25474 return extend(this.callSuper("toObject"), { 25475 opaque: this.opaque, 25476 matrix: this.matrix, 25477 }); 25478 }, 25479 }, 25480 ); 25481 25482 /** 25483 * Returns filter instance from an object representation 25484 * @static 25485 * @param {Object} object Object to create an instance from 25486 * @param {function} [callback] to be invoked after filter creation 25487 * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute 25488 */ 25489 fabric.Image.filters.Convolute.fromObject = 25490 fabric.Image.filters.BaseFilter.fromObject; 25491})(typeof exports !== "undefined" ? exports : this); 25492 25493(function (global) { 25494 "use strict"; 25495 25496 var fabric = global.fabric || (global.fabric = {}), 25497 filters = fabric.Image.filters, 25498 createClass = fabric.util.createClass; 25499 25500 /** 25501 * Grayscale image filter class 25502 * @class fabric.Image.filters.Grayscale 25503 * @memberOf fabric.Image.filters 25504 * @extends fabric.Image.filters.BaseFilter 25505 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 25506 * @example 25507 * var filter = new fabric.Image.filters.Grayscale(); 25508 * object.filters.push(filter); 25509 * object.applyFilters(); 25510 */ 25511 filters.Grayscale = createClass( 25512 filters.BaseFilter, 25513 /** @lends fabric.Image.filters.Grayscale.prototype */ { 25514 /** 25515 * Filter type 25516 * @param {String} type 25517 * @default 25518 */ 25519 type: "Grayscale", 25520 25521 fragmentSource: { 25522 average: 25523 "precision highp float;\n" + 25524 "uniform sampler2D uTexture;\n" + 25525 "varying vec2 vTexCoord;\n" + 25526 "void main() {\n" + 25527 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 25528 "float average = (color.r + color.b + color.g) / 3.0;\n" + 25529 "gl_FragColor = vec4(average, average, average, color.a);\n" + 25530 "}", 25531 lightness: 25532 "precision highp float;\n" + 25533 "uniform sampler2D uTexture;\n" + 25534 "uniform int uMode;\n" + 25535 "varying vec2 vTexCoord;\n" + 25536 "void main() {\n" + 25537 "vec4 col = texture2D(uTexture, vTexCoord);\n" + 25538 "float average = (max(max(col.r, col.g),col.b) + min(min(col.r, col.g),col.b)) / 2.0;\n" + 25539 "gl_FragColor = vec4(average, average, average, col.a);\n" + 25540 "}", 25541 luminosity: 25542 "precision highp float;\n" + 25543 "uniform sampler2D uTexture;\n" + 25544 "uniform int uMode;\n" + 25545 "varying vec2 vTexCoord;\n" + 25546 "void main() {\n" + 25547 "vec4 col = texture2D(uTexture, vTexCoord);\n" + 25548 "float average = 0.21 * col.r + 0.72 * col.g + 0.07 * col.b;\n" + 25549 "gl_FragColor = vec4(average, average, average, col.a);\n" + 25550 "}", 25551 }, 25552 25553 /** 25554 * Grayscale mode, between 'average', 'lightness', 'luminosity' 25555 * @param {String} type 25556 * @default 25557 */ 25558 mode: "average", 25559 25560 mainParameter: "mode", 25561 25562 /** 25563 * Apply the Grayscale operation to a Uint8Array representing the pixels of an image. 25564 * 25565 * @param {Object} options 25566 * @param {ImageData} options.imageData The Uint8Array to be filtered. 25567 */ 25568 applyTo2d: function (options) { 25569 var imageData = options.imageData, 25570 data = imageData.data, 25571 i, 25572 len = data.length, 25573 value, 25574 mode = this.mode; 25575 for (i = 0; i < len; i += 4) { 25576 if (mode === "average") { 25577 value = (data[i] + data[i + 1] + data[i + 2]) / 3; 25578 } else if (mode === "lightness") { 25579 value = 25580 (Math.min(data[i], data[i + 1], data[i + 2]) + 25581 Math.max(data[i], data[i + 1], data[i + 2])) / 25582 2; 25583 } else if (mode === "luminosity") { 25584 value = 0.21 * data[i] + 0.72 * data[i + 1] + 0.07 * data[i + 2]; 25585 } 25586 data[i] = value; 25587 data[i + 1] = value; 25588 data[i + 2] = value; 25589 } 25590 }, 25591 25592 /** 25593 * Retrieves the cached shader. 25594 * @param {Object} options 25595 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 25596 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 25597 */ 25598 retrieveShader: function (options) { 25599 var cacheKey = this.type + "_" + this.mode; 25600 if (!options.programCache.hasOwnProperty(cacheKey)) { 25601 var shaderSource = this.fragmentSource[this.mode]; 25602 options.programCache[cacheKey] = this.createProgram( 25603 options.context, 25604 shaderSource, 25605 ); 25606 } 25607 return options.programCache[cacheKey]; 25608 }, 25609 25610 /** 25611 * Return WebGL uniform locations for this filter's shader. 25612 * 25613 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 25614 * @param {WebGLShaderProgram} program This filter's compiled shader program. 25615 */ 25616 getUniformLocations: function (gl, program) { 25617 return { 25618 uMode: gl.getUniformLocation(program, "uMode"), 25619 }; 25620 }, 25621 25622 /** 25623 * Send data from this filter to its shader program's uniforms. 25624 * 25625 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 25626 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 25627 */ 25628 sendUniformData: function (gl, uniformLocations) { 25629 // default average mode. 25630 var mode = 1; 25631 gl.uniform1i(uniformLocations.uMode, mode); 25632 }, 25633 25634 /** 25635 * Grayscale filter isNeutralState implementation 25636 * The filter is never neutral 25637 * on the image 25638 **/ 25639 isNeutralState: function () { 25640 return false;
25641 }, 25642 }, 25643 ); 25644 25645 /** 25646 * Returns filter instance from an object representation 25647 * @static 25648 * @param {Object} object Object to create an instance from 25649 * @param {function} [callback] to be invoked after filter creation 25650 * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale 25651 */ 25652 fabric.Image.filters.Grayscale.fromObject = 25653 fabric.Image.filters.BaseFilter.fromObject; 25654})(typeof exports !== "undefined" ? exports : this); 25655 25656(function (global) { 25657 "use strict"; 25658 25659 var fabric = global.fabric || (global.fabric = {}), 25660 filters = fabric.Image.filters, 25661 createClass = fabric.util.createClass; 25662 25663 /** 25664 * Invert filter class 25665 * @class fabric.Image.filters.Invert 25666 * @memberOf fabric.Image.filters 25667 * @extends fabric.Image.filters.BaseFilter 25668 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 25669 * @example 25670 * var filter = new fabric.Image.filters.Invert(); 25671 * object.filters.push(filter); 25672 * object.applyFilters(canvas.renderAll.bind(canvas)); 25673 */ 25674 filters.Invert = createClass( 25675 filters.BaseFilter, 25676 /** @lends fabric.Image.filters.Invert.prototype */ { 25677 /** 25678 * Filter type 25679 * @param {String} type 25680 * @default 25681 */ 25682 type: "Invert", 25683 25684 fragmentSource: 25685 "precision highp float;\n" + 25686 "uniform sampler2D uTexture;\n" + 25687 "uniform int uInvert;\n" + 25688 "varying vec2 vTexCoord;\n" + 25689 "void main() {\n" + 25690 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 25691 "if (uInvert == 1) {\n" + 25692 "gl_FragColor = vec4(1.0 - color.r,1.0 -color.g,1.0 -color.b,color.a);\n" + 25693 "} else {\n" + 25694 "gl_FragColor = color;\n" + 25695 "}\n" + 25696 "}", 25697 25698 /** 25699 * Filter invert. if false, does nothing 25700 * @param {Boolean} invert 25701 * @default 25702 */ 25703 invert: true, 25704 25705 mainParameter: "invert", 25706 25707 /** 25708 * Apply the Invert operation to a Uint8Array representing the pixels of an image. 25709 * 25710 * @param {Object} options 25711 * @param {ImageData} options.imageData The Uint8Array to be filtered. 25712 */ 25713 applyTo2d: function (options) { 25714 var imageData = options.imageData, 25715 data = imageData.data, 25716 i, 25717 len = data.length; 25718 for (i = 0; i < len; i += 4) { 25719 data[i] = 255 - data[i]; 25720 data[i + 1] = 255 - data[i + 1]; 25721 data[i + 2] = 255 - data[i + 2]; 25722 } 25723 }, 25724 25725 /** 25726 * Invert filter isNeutralState implementation 25727 * Used only in image applyFilters to discard filters that will not have an effect 25728 * on the image 25729 * @param {Object} options 25730 **/ 25731 isNeutralState: function () { 25732 return !this.invert; 25733 }, 25734 25735 /** 25736 * Return WebGL uniform locations for this filter's shader. 25737 * 25738 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 25739 * @param {WebGLShaderProgram} program This filter's compiled shader program. 25740 */ 25741 getUniformLocations: function (gl, program) { 25742 return { 25743 uInvert: gl.getUniformLocation(program, "uInvert"), 25744 }; 25745 }, 25746 25747 /** 25748 * Send data from this filter to its shader program's uniforms. 25749 * 25750 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 25751 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 25752 */ 25753 sendUniformData: function (gl, uniformLocations) { 25754 gl.uniform1i(uniformLocations.uInvert, this.invert); 25755 }, 25756 }, 25757 ); 25758 25759 /** 25760 * Returns filter instance from an object representation 25761 * @static 25762 * @param {Object} object Object to create an instance from 25763 * @param {function} [callback] to be invoked after filter creation 25764 * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert 25765 */ 25766 fabric.Image.filters.Invert.fromObject = 25767 fabric.Image.filters.BaseFilter.fromObject; 25768})(typeof exports !== "undefined" ? exports : this); 25769 25770(function (global) { 25771 "use strict"; 25772 25773 var fabric = global.fabric || (global.fabric = {}), 25774 extend = fabric.util.object.extend, 25775 filters = fabric.Image.filters, 25776 createClass = fabric.util.createClass; 25777 25778 /** 25779 * Noise filter class 25780 * @class fabric.Image.filters.Noise 25781 * @memberOf fabric.Image.filters 25782 * @extends fabric.Image.filters.BaseFilter 25783 * @see {@link fabric.Image.filters.Noise#initialize}
25783 for constructor definition 25784 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 25785 * @example 25786 * var filter = new fabric.Image.filters.Noise({ 25787 * noise: 700 25788 * }); 25789 * object.filters.push(filter); 25790 * object.applyFilters(); 25791 * canvas.renderAll(); 25792 */ 25793 filters.Noise = createClass( 25794 filters.BaseFilter, 25795 /** @lends fabric.Image.filters.Noise.prototype */ { 25796 /** 25797 * Filter type 25798 * @param {String} type 25799 * @default 25800 */ 25801 type: "Noise", 25802 25803 /** 25804 * Fragment source for the noise program 25805 */ 25806 fragmentSource: 25807 "precision highp float;\n" + 25808 "uniform sampler2D uTexture;\n" + 25809 "uniform float uStepH;\n" + 25810 "uniform float uNoise;\n" + 25811 "uniform float uSeed;\n" + 25812 "varying vec2 vTexCoord;\n" + 25813 "float rand(vec2 co, float seed, float vScale) {\n" + 25814 "return fract(sin(dot(co.xy * vScale ,vec2(12.9898 , 78.233))) * 43758.5453 * (seed + 0.01) / 2.0);\n" + 25815 "}\n" + 25816 "void main() {\n" + 25817 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 25818 "color.rgb += (0.5 - rand(vTexCoord, uSeed, 0.1 / uStepH)) * uNoise;\n" + 25819 "gl_FragColor = color;\n" + 25820 "}", 25821 25822 /** 25823 * Describe the property that is the filter parameter 25824 * @param {String} m 25825 * @default 25826 */ 25827 mainParameter: "noise", 25828 25829 /** 25830 * Noise value, from 25831 * @param {Number} noise 25832 * @default 25833 */ 25834 noise: 0, 25835 25836 /** 25837 * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. 25838 * 25839 * @param {Object} options 25840 * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. 25841 */ 25842 applyTo2d: function (options) { 25843 if (this.noise === 0) { 25844 return; 25845 } 25846 var imageData = options.imageData, 25847 data = imageData.data, 25848 i, 25849 len = data.length, 25850 noise = this.noise, 25851 rand; 25852 25853 for (i = 0, len = data.length; i < len; i += 4) { 25854 rand = (0.5 - Math.random()) * noise; 25855 25856 data[i] += rand; 25857 data[i + 1] += rand; 25858 data[i + 2] += rand; 25859 } 25860 }, 25861 25862 /** 25863 * Return WebGL uniform locations for this filter's shader. 25864 * 25865 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 25866 * @param {WebGLShaderProgram} program This filter's compiled shader program. 25867 */ 25868 getUniformLocations: function (gl, program) { 25869 return { 25870 uNoise: gl.getUniformLocation(program, "uNoise"), 25871 uSeed: gl.getUniformLocation(program, "uSeed"), 25872 }; 25873 }, 25874 25875 /** 25876 * Send data from this filter to its shader program's uniforms. 25877 * 25878 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 25879 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 25880 */ 25881 sendUniformData: function (gl, uniformLocations) { 25882 gl.uniform1f(uniformLocations.uNoise, this.noise / 255); 25883 gl.uniform1f(uniformLocations.uSeed, Math.random()); 25884 }, 25885 25886 /** 25887 * Returns object representation of an instance 25888 * @return {Object} Object representation of an instance 25889 */ 25890 toObject: function () { 25891 return extend(this.callSuper("toObject"), { 25892 noise: this.noise, 25893 }); 25894 }, 25895 }, 25896 ); 25897 25898 /** 25899 * Returns filter instance from an object representation 25900 * @static 25901 * @param {Object} object Object to create an instance from 25902 * @param {Function} [callback] to be invoked after filter creation 25903 * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise 25904 */ 25905 fabric.Image.filters.Noise.fromObject = 25906 fabric.Image.filters.BaseFilter.fromObject; 25907})(typeof exports !== "undefined" ? exports : this); 25908 25909(function (global) { 25910 "use strict"; 25911 25912 var fabric = global.fabric || (global.fabric = {}), 25913 filters = fabric.Image.filters, 25914 createClass = fabric.util.createClass; 25915 25916 /** 25917 * Pixelate filter class 25918 * @class fabric.Image.filters.Pixelate 25919 * @memberOf fabric.Image.filters 25920 * @extends fabric.Image.filters.BaseFilter 25921 * @see {@link fabric.Image.filters.Pixelate#initialize}
25921 for constructor definition 25922 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 25923 * @example 25924 * var filter = new fabric.Image.filters.Pixelate({ 25925 * blocksize: 8 25926 * }); 25927 * object.filters.push(filter); 25928 * object.applyFilters(); 25929 */ 25930 filters.Pixelate = createClass( 25931 filters.BaseFilter, 25932 /** @lends fabric.Image.filters.Pixelate.prototype */ { 25933 /** 25934 * Filter type 25935 * @param {String} type 25936 * @default 25937 */ 25938 type: "Pixelate", 25939 25940 blocksize: 4, 25941 25942 mainParameter: "blocksize", 25943 25944 /** 25945 * Fragment source for the Pixelate program 25946 */ 25947 fragmentSource: 25948 "precision highp float;\n" + 25949 "uniform sampler2D uTexture;\n" + 25950 "uniform float uBlocksize;\n" + 25951 "uniform float uStepW;\n" + 25952 "uniform float uStepH;\n" + 25953 "varying vec2 vTexCoord;\n" + 25954 "void main() {\n" + 25955 "float blockW = uBlocksize * uStepW;\n" + 25956 "float blockH = uBlocksize * uStepW;\n" + 25957 "int posX = int(vTexCoord.x / blockW);\n" + 25958 "int posY = int(vTexCoord.y / blockH);\n" + 25959 "float fposX = float(posX);\n" + 25960 "float fposY = float(posY);\n" + 25961 "vec2 squareCoords = vec2(fposX * blockW, fposY * blockH);\n" + 25962 "vec4 color = texture2D(uTexture, squareCoords);\n" + 25963 "gl_FragColor = color;\n" + 25964 "}", 25965 25966 /** 25967 * Apply the Pixelate operation to a Uint8ClampedArray representing the pixels of an image. 25968 * 25969 * @param {Object} options 25970 * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. 25971 */ 25972 applyTo2d: function (options) { 25973 var imageData = options.imageData, 25974 data = imageData.data, 25975 iLen = imageData.height, 25976 jLen = imageData.width, 25977 index, 25978 i, 25979 j, 25980 r, 25981 g, 25982 b, 25983 a, 25984 _i, 25985 _j, 25986 _iLen, 25987 _jLen; 25988 25989 for (i = 0; i < iLen; i += this.blocksize) { 25990 for (j = 0; j < jLen; j += this.blocksize) { 25991 index = i * 4 * jLen + j * 4; 25992 25993 r = data[index]; 25994 g = data[index + 1]; 25995 b = data[index + 2]; 25996 a = data[index + 3]; 25997 25998 _iLen = Math.min(i + this.blocksize, iLen); 25999 _jLen = Math.min(j + this.blocksize, jLen); 26000 for (_i = i; _i < _iLen; _i++) { 26001 for (_j = j; _j < _jLen; _j++) { 26002 index = _i * 4 * jLen + _j * 4; 26003 data[index] = r; 26004 data[index + 1] = g; 26005 data[index + 2] = b; 26006 data[index + 3] = a; 26007 } 26008 } 26009 } 26010 } 26011 }, 26012 26013 /** 26014 * Indicate when the filter is not gonna apply changes to the image 26015 **/ 26016 isNeutralState: function () { 26017 return this.blocksize === 1; 26018 }, 26019 26020 /** 26021 * Return WebGL uniform locations for this filter's shader. 26022 * 26023 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 26024 * @param {WebGLShaderProgram} program This filter's compiled shader program. 26025 */ 26026 getUniformLocations: function (gl, program) { 26027 return { 26028 uBlocksize: gl.getUniformLocation(program, "uBlocksize"), 26029 uStepW: gl.getUniformLocation(program, "uStepW"), 26030 uStepH: gl.getUniformLocation(program, "uStepH"), 26031 }; 26032 }, 26033 26034 /** 26035 * Send data from this filter to its shader program's uniforms. 26036 * 26037 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 26038 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 26039 */ 26040 sendUniformData: function (gl, uniformLocations) { 26041 gl.uniform1f(uniformLocations.uBlocksize, this.blocksize); 26042 }, 26043 }, 26044 ); 26045 26046 /** 26047 * Returns filter instance from an object representation 26048 * @static 26049 * @param {Object} object Object to create an instance from 26050 * @param {Function} [callback] to be invoked after filter creation 26051 * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate 26052 */ 26053 fabric.Image.filters.Pixelate.fromObject = 26054 fabric.Image.filters.BaseFilter.fromObject; 26055})(typeof exports !== "undefined" ? exports : this); 26056 26057(function (global) { 26058 "use strict"; 26059 26060 var fabric = global.fabric || (global.fabric = {}), 26061 extend = fabric.util.object.extend, 26062 filters = fabric.Image.filters, 26063 createClass = fabric.util.createClass; 26064 26065 /** 26066 * Remove white filter class 26067 * @class fabric.Image.filters.RemoveColor 26068 * @memberOf fabric.Image.filters 26069 * @extends fabric.Image.filters.BaseFilter 26070 * @see {@link fabric.Image.filters.RemoveColor#initialize}
26070 for constructor definition 26071 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 26072 * @example 26073 * var filter = new fabric.Image.filters.RemoveColor({ 26074 * threshold: 0.2, 26075 * }); 26076 * object.filters.push(filter); 26077 * object.applyFilters(); 26078 * canvas.renderAll(); 26079 */ 26080 filters.RemoveColor = createClass( 26081 filters.BaseFilter, 26082 /** @lends fabric.Image.filters.RemoveColor.prototype */ { 26083 /** 26084 * Filter type 26085 * @param {String} type 26086 * @default 26087 */ 26088 type: "RemoveColor", 26089 26090 /** 26091 * Color to remove, in any format understood by fabric.Color. 26092 * @param {String} type 26093 * @default 26094 */ 26095 color: "#FFFFFF", 26096 26097 /** 26098 * Fragment source for the brightness program 26099 */ 26100 fragmentSource: 26101 "precision highp float;\n" + 26102 "uniform sampler2D uTexture;\n" + 26103 "uniform vec4 uLow;\n" + 26104 "uniform vec4 uHigh;\n" + 26105 "varying vec2 vTexCoord;\n" + 26106 "void main() {\n" + 26107 "gl_FragColor = texture2D(uTexture, vTexCoord);\n" + 26108 "if(all(greaterThan(gl_FragColor.rgb,uLow.rgb)) && all(greaterThan(uHigh.rgb,gl_FragColor.rgb))) {\n" + 26109 "gl_FragColor.a = 0.0;\n" + 26110 "}\n" + 26111 "}", 26112 26113 /** 26114 * distance to actual color, as value up or down from each r,g,b 26115 * between 0 and 1 26116 **/ 26117 distance: 0.02, 26118 26119 /** 26120 * For color to remove inside distance, use alpha channel for a smoother deletion 26121 * NOT IMPLEMENTED YET 26122 **/ 26123 useAlpha: false, 26124 26125 /** 26126 * Constructor 26127 * @memberOf fabric.Image.filters.RemoveWhite.prototype 26128 * @param {Object} [options] Options object 26129 * @param {Number} [options.color=#RRGGBB] Threshold value 26130 * @param {Number} [options.distance=10] Distance value 26131 */ 26132 26133 /** 26134 * Applies filter to canvas element 26135 * @param {Object} canvasEl Canvas element to apply filter to 26136 */ 26137 applyTo2d: function (options) { 26138 var imageData = options.imageData, 26139 data = imageData.data, 26140 i, 26141 distance = this.distance * 255, 26142 r, 26143 g, 26144 b, 26145 source = new fabric.Color(this.color).getSource(), 26146 lowC = [ 26147 source[0] - distance, 26148 source[1] - distance, 26149 source[2] - distance, 26150 ], 26151 highC = [ 26152 source[0] + distance, 26153 source[1] + distance, 26154 source[2] + distance, 26155 ]; 26156 26157 for (i = 0; i < data.length; i += 4) { 26158 r = data[i]; 26159 g = data[i + 1]; 26160 b = data[i + 2]; 26161 26162 if ( 26163 r > lowC[0] && 26164 g > lowC[1] && 26165 b > lowC[2] && 26166 r < highC[0] && 26167 g < highC[1] && 26168 b < highC[2] 26169 ) { 26170 data[i + 3] = 0; 26171 } 26172 } 26173 }, 26174 26175 /** 26176 * Return WebGL uniform locations for this filter's shader. 26177 * 26178 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 26179 * @param {WebGLShaderProgram} program This filter's compiled shader program. 26180 */ 26181 getUniformLocations: function (gl, program) { 26182 return { 26183 uLow: gl.getUniformLocation(program, "uLow"), 26184 uHigh: gl.getUniformLocation(program, "uHigh"), 26185 }; 26186 }, 26187 26188 /** 26189 * Send data from this filter to its shader program's uniforms. 26190 * 26191 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 26192 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 26193 */ 26194 sendUniformData: function (gl, uniformLocations) { 26195 var source = new fabric.Color(this.color).getSource(), 26196 distance = parseFloat(this.distance), 26197 lowC = [ 26198 0 + source[0] / 255 - distance, 26199 0 + source[1] / 255 - distance, 26200 0 + source[2] / 255 - distance, 26201 1, 26202 ], 26203 highC = [ 26204 source[0] / 255 + distance, 26205 source[1] / 255 + distance, 26206 source[2] / 255 + distance, 26207 1, 26208 ]; 26209 gl.uniform4fv(uniformLocations.uLow, lowC); 26210 gl.uniform4fv(uniformLocations.uHigh, highC); 26211 }, 26212 26213 /** 26214 * Returns object representation of an instance 26215 * @return {Object} Object representation of an instance 26216 */ 26217 toObject: function () { 26218 return extend(this.callSuper("toObject"), { 26219 color: this.color, 26220 distance: this.distance, 26221 }); 26222 }, 26223 }, 26224 ); 26225 26226 /** 26227 * Returns filter instance from an object representation 26228 * @static 26229 * @param {Object} object Object to create an instance from 26230 * @param {Function} [callback] to be invoked after filter creation 26231 * @return {fabric.Image.filters.RemoveColor} Instance of fabric.Image.filters.RemoveWhite 26232 */ 26233 fabric.Image.filters.RemoveColor.fromObject = 26234 fabric.Image.filters.BaseFilter.fromObject; 26235})(typeof exports !== "undefined" ? exports : this); 26236 26237(function (global) { 26238 "use strict"; 26239 26240 var fabric = global.fabric || (global.fabric = {}), 26241 filters = fabric.Image.filters, 26242 createClass = fabric.util.createClass; 26243 26244 var matrices = { 26245 Brownie: [ 26246 0.5997, 0.34553, -0.27082, 0, 0.186, -0.0377, 0.86095, 0.15059, 0,
26247 -0.1449, 0.24113, -0.07441, 0.44972, 0, -0.02965, 0, 0, 0, 1, 0, 26248 ], 26249 Vintage: [ 26250 0.62793, 0.32021, -0.03965, 0, 0.03784, 0.02578, 0.64411, 0.03259, 0, 26251 0.02926, 0.0466, -0.08512, 0.52416, 0, 0.02023, 0, 0, 0, 1, 0, 26252 ], 26253 Kodachrome: [ 26254 1.12855, -0.39673, -0.03992, 0, 0.24991, -0.16404, 1.08352, -0.05498, 0, 26255 0.09698, -0.16786, -0.56034, 1.60148, 0, 0.13972, 0, 0, 0, 1, 0, 26256 ], 26257 Technicolor: [ 26258 1.91252, -0.85453, -0.09155, 0, 0.04624, -0.30878, 1.76589, -0.10601, 0, 26259 -0.27589, -0.2311, -0.75018, 1.84759, 0, 0.12137, 0, 0, 0, 1, 0, 26260 ], 26261 Polaroid: [ 26262 1.438, -0.062, -0.062, 0, 0, -0.122, 1.378, -0.122, 0, 0, -0.016, -0.016, 26263 1.483, 0, 0, 0, 0, 0, 1, 0, 26264 ], 26265 Sepia: [ 26266 0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 26267 0, 0, 0, 0, 0, 1, 0, 26268 ], 26269 BlackWhite: [ 26270 1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 0, 0, 0, 26271 1, 0, 26272 ], 26273 }; 26274 26275 for (var key in matrices) { 26276 filters[key] = createClass( 26277 filters.ColorMatrix, 26278 /** @lends fabric.Image.filters.Sepia.prototype */ { 26279 /** 26280 * Filter type 26281 * @param {String} type 26282 * @default 26283 */ 26284 type: key, 26285 26286 /** 26287 * Colormatrix for the effect 26288 * array of 20 floats. Numbers in positions 4, 9, 14, 19 loose meaning 26289 * outside the -1, 1 range. 26290 * @param {Array} matrix array of 20 numbers. 26291 * @default 26292 */ 26293 matrix: matrices[key], 26294 26295 /** 26296 * Lock the matrix export for this kind of static, parameter less filters. 26297 */ 26298 mainParameter: false, 26299 /** 26300 * Lock the colormatrix on the color part, skipping alpha 26301 */ 26302 colorsOnly: true, 26303 }, 26304 ); 26305 fabric.Image.filters[key].fromObject = 26306 fabric.Image.filters.BaseFilter.fromObject; 26307 } 26308})(typeof exports !== "undefined" ? exports : this); 26309 26310(function (global) { 26311 "use strict"; 26312 26313 var fabric = global.fabric, 26314 filters = fabric.Image.filters, 26315 createClass = fabric.util.createClass; 26316 26317 /** 26318 * Color Blend filter class 26319 * @class fabric.Image.filter.BlendColor 26320 * @memberOf fabric.Image.filters 26321 * @extends fabric.Image.filters.BaseFilter 26322 * @example 26323 * var filter = new fabric.Image.filters.BlendColor({ 26324 * color: '#000', 26325 * mode: 'multiply' 26326 * }); 26327 * 26328 * var filter = new fabric.Image.filters.BlendImage({ 26329 * image: fabricImageObject, 26330 * mode: 'multiply', 26331 * alpha: 0.5 26332 * }); 26333 * object.filters.push(filter); 26334 * object.applyFilters(); 26335 * canvas.renderAll(); 26336 */ 26337 26338 filters.BlendColor = createClass( 26339 filters.BaseFilter, 26340 /** @lends fabric.Image.filters.Blend.prototype */ { 26341 type: "BlendColor", 26342 26343 /** 26344 * Color to make the blend operation with. default to a reddish color since black or white 26345 * gives always strong result.
26346 * @type String 26347 * @default 26348 **/ 26349 color: "#F95C63", 26350 26351 /** 26352 * Blend mode for the filter: one of multiply, add, diff, screen, subtract, 26353 * darken, lighten, overlay, exclusion, tint. 26354 * @type String 26355 * @default 26356 **/ 26357 mode: "multiply", 26358 26359 /** 26360 * alpha value. represent the strength of the blend color operation. 26361 * @type Number 26362 * @default 26363 **/ 26364 alpha: 1, 26365 26366 /** 26367 * Fragment source for the Multiply program 26368 */ 26369 fragmentSource: { 26370 multiply: "gl_FragColor.rgb *= uColor.rgb;\n", 26371 screen: 26372 "gl_FragColor.rgb = 1.0 - (1.0 - gl_FragColor.rgb) * (1.0 - uColor.rgb);\n", 26373 add: "gl_FragColor.rgb += uColor.rgb;\n", 26374 diff: "gl_FragColor.rgb = abs(gl_FragColor.rgb - uColor.rgb);\n", 26375 subtract: "gl_FragColor.rgb -= uColor.rgb;\n", 26376 lighten: "gl_FragColor.rgb = max(gl_FragColor.rgb, uColor.rgb);\n", 26377 darken: "gl_FragColor.rgb = min(gl_FragColor.rgb, uColor.rgb);\n", 26378 exclusion: 26379 "gl_FragColor.rgb += uColor.rgb - 2.0 * (uColor.rgb * gl_FragColor.rgb);\n", 26380 overlay: 26381 "if (uColor.r < 0.5) {\n" + 26382 "gl_FragColor.r *= 2.0 * uColor.r;\n" + 26383 "} else {\n" + 26384 "gl_FragColor.r = 1.0 - 2.0 * (1.0 - gl_FragColor.r) * (1.0 - uColor.r);\n" + 26385 "}\n" + 26386 "if (uColor.g < 0.5) {\n" + 26387 "gl_FragColor.g *= 2.0 * uColor.g;\n" + 26388 "} else {\n" + 26389 "gl_FragColor.g = 1.0 - 2.0 * (1.0 - gl_FragColor.g) * (1.0 - uColor.g);\n" + 26390 "}\n" + 26391 "if (uColor.b < 0.5) {\n" + 26392 "gl_FragColor.b *= 2.0 * uColor.b;\n" + 26393 "} else {\n" + 26394 "gl_FragColor.b = 1.0 - 2.0 * (1.0 - gl_FragColor.b) * (1.0 - uColor.b);\n" + 26395 "}\n", 26396 tint: 26397 "gl_FragColor.rgb *= (1.0 - uColor.a);\n" + 26398 "gl_FragColor.rgb += uColor.rgb;\n", 26399 }, 26400 26401 /** 26402 * build the fragment source for the filters, joining the common part with 26403 * the specific one. 26404 * @param {String} mode the mode of the filter, a key of this.fragmentSource 26405 * @return {String} the source to be compiled 26406 * @private 26407 */ 26408 buildSource: function (mode) { 26409 return ( 26410 "precision highp float;\n" + 26411 "uniform sampler2D uTexture;\n" + 26412 "uniform vec4 uColor;\n" + 26413 "varying vec2 vTexCoord;\n" + 26414 "void main() {\n" + 26415 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 26416 "gl_FragColor = color;\n" + 26417 "if (color.a > 0.0) {\n" + 26418 this.fragmentSource[mode] + 26419 "}\n" + 26420 "}" 26421 ); 26422 }, 26423 26424 /** 26425 * Retrieves the cached shader. 26426 * @param {Object} options 26427 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 26428 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 26429 */ 26430 retrieveShader: function (options) { 26431 var cacheKey = this.type + "_" + this.mode, 26432 shaderSource; 26433 if (!options.programCache.hasOwnProperty(cacheKey)) { 26434 shaderSource = this.buildSource(this.mode); 26435 options.programCache[cacheKey] = this.createProgram( 26436 options.context, 26437 shaderSource, 26438 ); 26439 } 26440 return options.programCache[cacheKey]; 26441 }, 26442 26443 /** 26444 * Apply the Blend operation to a Uint8ClampedArray representing the pixels of an image. 26445 * 26446 * @param {Object} options 26447 * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. 26448 */ 26449 applyTo2d: function (options) { 26450 var imageData = options.imageData, 26451 data = imageData.data, 26452 iLen = data.length, 26453 tr, 26454 tg, 26455 tb, 26456 r, 26457 g, 26458 b, 26459 source, 26460 alpha1 = 1 - this.alpha; 26461 26462 source = new fabric.Color(this.color).getSource(); 26463 tr = source[0] * this.alpha; 26464 tg = source[1] * this.alpha; 26465 tb = source[2] * this.alpha; 26466 26467 for (var i = 0; i < iLen; i += 4) { 26468 r = data[i]; 26469 g = data[i + 1]; 26470 b = data[i + 2]; 26471 26472 switch (this.mode) { 26473 case "multiply": 26474 data[i] = (r * tr) / 255; 26475 data[i + 1] = (g * tg) / 255; 26476 data[i + 2] = (b * tb) / 255; 26477 break; 26478 case "screen":
26479 data[i] = 255 - ((255 - r) * (255 - tr)) / 255; 26480 data[i + 1] = 255 - ((255 - g) * (255 - tg)) / 255; 26481 data[i + 2] = 255 - ((255 - b) * (255 - tb)) / 255; 26482 break; 26483 case "add": 26484 data[i] = r + tr; 26485 data[i + 1] = g + tg; 26486 data[i + 2] = b + tb; 26487 break; 26488 case "diff": 26489 case "difference": 26490 data[i] = Math.abs(r - tr); 26491 data[i + 1] = Math.abs(g - tg); 26492 data[i + 2] = Math.abs(b - tb); 26493 break; 26494 case "subtract": 26495 data[i] = r - tr; 26496 data[i + 1] = g - tg; 26497 data[i + 2] = b - tb; 26498 break; 26499 case "darken": 26500 data[i] = Math.min(r, tr); 26501 data[i + 1] = Math.min(g, tg); 26502 data[i + 2] = Math.min(b, tb); 26503 break; 26504 case "lighten": 26505 data[i] = Math.max(r, tr); 26506 data[i + 1] = Math.max(g, tg); 26507 data[i + 2] = Math.max(b, tb); 26508 break; 26509 case "overlay": 26510 data[i] = 26511 tr < 128 26512 ? (2 * r * tr) / 255 26513 : 255 - (2 * (255 - r) * (255 - tr)) / 255; 26514 data[i + 1] = 26515 tg < 128 26516 ? (2 * g * tg) / 255
26517 : 255 - (2 * (255 - g) * (255 - tg)) / 255; 26518 data[i + 2] = 26519 tb < 128 26520 ? (2 * b * tb) / 255 26521 : 255 - (2 * (255 - b) * (255 - tb)) / 255; 26522 break; 26523 case "exclusion": 26524 data[i] = tr + r - (2 * tr * r) / 255; 26525 data[i + 1] = tg + g - (2 * tg * g) / 255; 26526 data[i + 2] = tb + b - (2 * tb * b) / 255; 26527 break; 26528 case "tint": 26529 data[i] = tr + r * alpha1; 26530 data[i + 1] = tg + g * alpha1; 26531 data[i + 2] = tb + b * alpha1; 26532 } 26533 } 26534 }, 26535 26536 /** 26537 * Return WebGL uniform locations for this filter's shader. 26538 * 26539 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 26540 * @param {WebGLShaderProgram} program This filter's compiled shader program. 26541 */ 26542 getUniformLocations: function (gl, program) { 26543 return { 26544 uColor: gl.getUniformLocation(program, "uColor"), 26545 }; 26546 }, 26547 26548 /** 26549 * Send data from this filter to its shader program's uniforms. 26550 * 26551 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 26552 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 26553 */ 26554 sendUniformData: function (gl, uniformLocations) { 26555 var source = new fabric.Color(this.color).getSource(); 26556 source[0] = (this.alpha * source[0]) / 255; 26557 source[1] = (this.alpha * source[1]) / 255; 26558 source[2] = (this.alpha * source[2]) / 255; 26559 source[3] = this.alpha; 26560 gl.uniform4fv(uniformLocations.uColor, source); 26561 }, 26562 26563 /** 26564 * Returns object representation of an instance 26565 * @return {Object} Object representation of an instance 26566 */ 26567 toObject: function () { 26568 return { 26569 type: this.type, 26570 color: this.color, 26571 mode: this.mode, 26572 alpha: this.alpha, 26573 }; 26574 }, 26575 }, 26576 ); 26577 26578 /** 26579 * Returns filter instance from an object representation 26580 * @static 26581 * @param {Object} object Object to create an instance from 26582 * @param {function} [callback] to be invoked after filter creation 26583 * @return {fabric.Image.filters.BlendColor} Instance of fabric.Image.filters.BlendColor 26584 */ 26585 fabric.Image.filters.BlendColor.fromObject = 26586 fabric.Image.filters.BaseFilter.fromObject; 26587})(typeof exports !== "undefined" ? exports : this); 26588 26589(function (global) { 26590 "use strict"; 26591 26592 var fabric = global.fabric, 26593 filters = fabric.Image.filters, 26594 createClass = fabric.util.createClass; 26595 26596 /** 26597 * Image Blend filter class 26598 * @class fabric.Image.filter.BlendImage 26599 * @memberOf fabric.Image.filters 26600 * @extends fabric.Image.filters.BaseFilter 26601 * @example 26602 * var filter = new fabric.Image.filters.BlendColor({ 26603 * color: '#000', 26604 * mode: 'multiply' 26605 * }); 26606 * 26607 * var filter = new fabric.Image.filters.BlendImage({ 26608 * image: fabricImageObject, 26609 * mode: 'multiply', 26610 * alpha: 0.5 26611 * }); 26612 * object.filters.push(filter); 26613 * object.applyFilters(); 26614 * canvas.renderAll(); 26615 */ 26616 26617 filters.BlendImage = createClass( 26618 filters.BaseFilter, 26619 /** @lends fabric.Image.filters.BlendImage.prototype */ { 26620 type: "BlendImage", 26621 26622 /** 26623 * Color to make the blend operation with. default to a reddish color since black or white 26624 * gives always strong result. 26625 **/ 26626 image: null, 26627 26628 /** 26629 * Blend mode for the filter (one of "multiply", "mask")
26630 * @type String 26631 * @default 26632 **/ 26633 mode: "multiply", 26634 26635 /** 26636 * alpha value. represent the strength of the blend image operation. 26637 * not implemented. 26638 **/ 26639 alpha: 1, 26640 26641 vertexSource: 26642 "attribute vec2 aPosition;\n" + 26643 "varying vec2 vTexCoord;\n" + 26644 "varying vec2 vTexCoord2;\n" + 26645 "uniform mat3 uTransformMatrix;\n" + 26646 "void main() {\n" + 26647 "vTexCoord = aPosition;\n" + 26648 "vTexCoord2 = (uTransformMatrix * vec3(aPosition, 1.0)).xy;\n" + 26649 "gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n" + 26650 "}", 26651 26652 /** 26653 * Fragment source for the Multiply program 26654 */ 26655 fragmentSource: { 26656 multiply: 26657 "precision highp float;\n" + 26658 "uniform sampler2D uTexture;\n" + 26659 "uniform sampler2D uImage;\n" + 26660 "uniform vec4 uColor;\n" + 26661 "varying vec2 vTexCoord;\n" + 26662 "varying vec2 vTexCoord2;\n" + 26663 "void main() {\n" + 26664 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 26665 "vec4 color2 = texture2D(uImage, vTexCoord2);\n" + 26666 "color.rgba *= color2.rgba;\n" + 26667 "gl_FragColor = color;\n" + 26668 "}", 26669 mask: 26670 "precision highp float;\n" + 26671 "uniform sampler2D uTexture;\n" + 26672 "uniform sampler2D uImage;\n" + 26673 "uniform vec4 uColor;\n" + 26674 "varying vec2 vTexCoord;\n" + 26675 "varying vec2 vTexCoord2;\n" + 26676 "void main() {\n" + 26677 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 26678 "vec4 color2 = texture2D(uImage, vTexCoord2);\n" + 26679 "color.a = color2.a;\n" + 26680 "gl_FragColor = color;\n" + 26681 "}", 26682 }, 26683 26684 /** 26685 * Retrieves the cached shader. 26686 * @param {Object} options 26687 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 26688 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 26689 */ 26690 retrieveShader: function (options) { 26691 var cacheKey = this.type + "_" + this.mode; 26692 var shaderSource = this.fragmentSource[this.mode]; 26693 if (!options.programCache.hasOwnProperty(cacheKey)) { 26694 options.programCache[cacheKey] = this.createProgram( 26695 options.context, 26696 shaderSource, 26697 ); 26698 } 26699 return options.programCache[cacheKey]; 26700 }, 26701 26702 applyToWebGL: function (options) { 26703 // load texture to blend. 26704 var gl = options.context, 26705 texture = this.createTexture(options.filterBackend, this.image); 26706 this.bindAdditionalTexture(gl, texture, gl.TEXTURE1); 26707 this.callSuper("applyToWebGL", options); 26708 this.unbindAdditionalTexture(gl, gl.TEXTURE1); 26709 }, 26710 26711 createTexture: function (backend, image) { 26712 return backend.getCachedTexture(image.cacheKey, image._element); 26713 }, 26714 26715 /** 26716 * Calculate a transformMatrix to adapt the image to blend over 26717 * @param {Object} options 26718 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 26719 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 26720 */ 26721 calculateMatrix: function () { 26722 var image = this.image, 26723 width = image._element.width, 26724 height = image._element.height; 26725 return [ 26726 1 / image.scaleX, 26727 0, 26728 0, 26729 0, 26730 1 / image.scaleY, 26731 0, 26732 -image.left / width, 26733 -image.top / height, 26734 1, 26735 ]; 26736 }, 26737 26738 /** 26739 * Apply the Blend operation to a Uint8ClampedArray representing the pixels of an image. 26740 * 26741 * @param {Object} options 26742 * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. 26743 */ 26744 applyTo2d: function (options) { 26745 var imageData = options.imageData, 26746 resources = options.filterBackend.resources, 26747 data = imageData.data, 26748 iLen = data.length, 26749 width = imageData.width, 26750 height = imageData.height, 26751 tr, 26752 tg, 26753 tb, 26754 ta, 26755 r, 26756 g, 26757 b, 26758 a, 26759 canvas1, 26760 context, 26761 image = this.image, 26762 blendData; 26763 26764 if (!resources.blendImage) { 26765 resources.blendImage = fabric.util.createCanvasElement(); 26766 } 26767 canvas1 = resources.blendImage; 26768 context = canvas1.getContext("2d"); 26769 if (canvas1.width !== width || canvas1.height !== height) { 26770 canvas1.width = width; 26771 canvas1.height = height; 26772 } else { 26773 context.clearRect(0, 0, width, height); 26774 } 26775 context.setTransform( 26776 image.scaleX, 26777 0, 26778 0, 26779 image.scaleY, 26780 image.left, 26781 image.top, 26782 ); 26783 context.drawImage(image._element, 0, 0, width, height); 26784 blendData = context.getImageData(0, 0, width, height).data; 26785 for (var i = 0; i < iLen; i += 4) { 26786 r = data[i]; 26787 g = data[i + 1]; 26788 b = data[i + 2]; 26789 a = data[i + 3]; 26790 26791 tr = blendData[i]; 26792 tg = blendData[i + 1]; 26793 tb = blendData[i + 2]; 26794 ta = blendData[i + 3]; 26795 26796 switch (this.mode) { 26797 case "multiply": 26798 data[i] = (r * tr) / 255; 26799 data[i + 1] = (g * tg) / 255; 26800 data[i + 2] = (b * tb) / 255;
26801 data[i + 3] = (a * ta) / 255; 26802 break; 26803 case "mask": 26804 data[i + 3] = ta; 26805 break; 26806 } 26807 } 26808 }, 26809 26810 /** 26811 * Return WebGL uniform locations for this filter's shader. 26812 * 26813 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 26814 * @param {WebGLShaderProgram} program This filter's compiled shader program. 26815 */ 26816 getUniformLocations: function (gl, program) { 26817 return { 26818 uTransformMatrix: gl.getUniformLocation(program, "uTransformMatrix"), 26819 uImage: gl.getUniformLocation(program, "uImage"), 26820 }; 26821 }, 26822 26823 /** 26824 * Send data from this filter to its shader program's uniforms. 26825 * 26826 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 26827 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 26828 */ 26829 sendUniformData: function (gl, uniformLocations) { 26830 var matrix = this.calculateMatrix(); 26831 gl.uniform1i(uniformLocations.uImage, 1); // texture unit 1. 26832 gl.uniformMatrix3fv(uniformLocations.uTransformMatrix, false, matrix); 26833 }, 26834 26835 /** 26836 * Returns object representation of an instance 26837 * @return {Object} Object representation of an instance 26838 */ 26839 toObject: function () { 26840 return { 26841 type: this.type, 26842 image: this.image && this.image.toObject(), 26843 mode: this.mode, 26844 alpha: this.alpha, 26845 }; 26846 }, 26847 }, 26848 ); 26849 26850 /** 26851 * Returns filter instance from an object representation 26852 * @static 26853 * @param {Object} object Object to create an instance from 26854 * @param {function} callback to be invoked after filter creation 26855 * @return {fabric.Image.filters.BlendImage} Instance of fabric.Image.filters.BlendImage 26856 */ 26857 fabric.Image.filters.BlendImage.fromObject = function (object, callback) { 26858 fabric.Image.fromObject(object.image, function (image) { 26859 var options = fabric.util.object.clone(object); 26860 options.image = image; 26861 callback(new fabric.Image.filters.BlendImage(options)); 26862 }); 26863 }; 26864})(typeof exports !== "undefined" ? exports : this); 26865 26866(function (global) { 26867 "use strict"; 26868 26869 var fabric = global.fabric || (global.fabric = {}), 26870 pow = Math.pow, 26871 floor = Math.floor, 26872 sqrt = Math.sqrt, 26873 abs = Math.abs, 26874 round = Math.round, 26875 sin = Math.sin, 26876 ceil = Math.ceil, 26877 filters = fabric.Image.filters, 26878 createClass = fabric.util.createClass; 26879 26880 /** 26881 * Resize image filter class 26882 * @class fabric.Image.filters.Resize 26883 * @memberOf fabric.Image.filters 26884 * @extends fabric.Image.filters.BaseFilter 26885 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 26886 * @example 26887 * var filter = new fabric.Image.filters.Resize(); 26888 * object.filters.push(filter); 26889 * object.applyFilters(canvas.renderAll.bind(canvas)); 26890 */ 26891 filters.Resize = createClass( 26892 filters.BaseFilter, 26893 /** @lends fabric.Image.filters.Resize.prototype */ { 26894 /** 26895 * Filter type 26896 * @param {String} type 26897 * @default 26898 */ 26899 type: "Resize", 26900 26901 /** 26902 * Resize type 26903 * for webgl resizeType is just lanczos, for canvas2d can be: 26904 * bilinear, hermite, sliceHack, lanczos. 26905 * @param {String} resizeType 26906 * @default 26907 */ 26908 resizeType: "hermite", 26909 26910 /** 26911 * Scale factor for resizing, x axis 26912 * @param {Number} scaleX 26913 * @default 26914 */ 26915 scaleX: 1, 26916 26917 /** 26918 * Scale factor for resizing, y axis 26919 * @param {Number} scaleY 26920 * @default 26921 */ 26922 scaleY: 1, 26923 26924 /** 26925 * LanczosLobes parameter for lanczos filter, valid for resizeType lanczos 26926 * @param {Number} lanczosLobes 26927 * @default 26928 */ 26929 lanczosLobes: 3, 26930 26931 /** 26932 * Return WebGL uniform locations for this filter's shader. 26933 * 26934 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 26935 * @param {WebGLShaderProgram} program This filter's compiled shader program. 26936 */ 26937 getUniformLocations: function (gl, program) { 26938 return { 26939 uDelta: gl.getUniformLocation(program, "uDelta"), 26940 uTaps: gl.getUniformLocation(program, "uTaps"), 26941 }; 26942 }, 26943 26944 /** 26945 * Send data from this filter to its shader program's uniforms. 26946 * 26947 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 26948 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 26949 */ 26950 sendUniformData: function (gl, uniformLocations) { 26951 gl.uniform2fv( 26952 uniformLocations.uDelta, 26953 this.horizontal ? [1 / this.width, 0] : [0, 1 / this.height], 26954 ); 26955 gl.uniform1fv(uniformLocations.uTaps, this.taps); 26956 }, 26957 26958 /** 26959 * Retrieves the cached shader. 26960 * @param {Object} options 26961 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 26962 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 26963 */ 26964 retrieveShader: function (options) { 26965 var filterWindow = this.getFilterWindow(), 26966 cacheKey = this.type + "_" + filterWindow; 26967 if (!options.programCache.hasOwnProperty(cacheKey)) { 26968 var fragmentShader = this.generateShader(filterWindow); 26969 options.programCache[cacheKey] = this.createProgram( 26970 options.context, 26971 fragmentShader, 26972 ); 26973 } 26974 return options.programCache[cacheKey]; 26975 }, 26976 26977 getFilterWindow: function () { 26978 var scale = this.tempScale; 26979 return Math.ceil(this.lanczosLobes / scale); 26980 }, 26981 26982 getTaps: function () { 26983 var lobeFunction = this.lanczosCreate(this.lanczosLobes), 26984 scale = this.tempScale, 26985 filterWindow = this.getFilterWindow(), 26986 taps = new Array(filterWindow); 26987 for (var i = 1; i <= filterWindow; i++) { 26988 taps[i - 1] = lobeFunction(i * scale); 26989 } 26990 return taps; 26991 }, 26992 26993 /** 26994 * Generate vertex and shader sources from the necessary steps numbers 26995 * @param {Number} filterWindow 26996 */ 26997 generateShader: function (filterWindow) { 26998 var offsets = new Array(filterWindow), 26999 fragmentShader = this.fragmentSourceTOP, 27000 filterWindow; 27001 27002 for (var i = 1; i <= filterWindow; i++) { 27003 offsets[i - 1] = i + ".0 * uDelta"; 27004 } 27005 27006 fragmentShader += "uniform float uTaps[" + filterWindow + "];\n"; 27007 fragmentShader += "void main() {\n"; 27008 fragmentShader += " vec4 color = texture2D(uTexture, vTexCoord);\n"; 27009 fragmentShader += " float sum = 1.0;\n"; 27010
vendor: 14,315 bytes, lines 27011-27437
27011 offsets.forEach(function (offset, i) { 27012 fragmentShader += 27013 " color += texture2D(uTexture, vTexCoord + " + 27014 offset + 27015 ") * uTaps[" + 27016 i + 27017 "];\n"; 27018 fragmentShader += 27019 " color += texture2D(uTexture, vTexCoord - " + 27020 offset + 27021 ") * uTaps[" + 27022 i + 27023 "];\n"; 27024 fragmentShader += " sum += 2.0 * uTaps[" + i + "];\n"; 27025 }); 27026 fragmentShader += " gl_FragColor = color / sum;\n"; 27027 fragmentShader += "}"; 27028 return fragmentShader; 27029 }, 27030 27031 fragmentSourceTOP: 27032 "precision highp float;\n" + 27033 "uniform sampler2D uTexture;\n" + 27034 "uniform vec2 uDelta;\n" + 27035 "varying vec2 vTexCoord;\n", 27036 27037 /** 27038 * Apply the resize filter to the image 27039 * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. 27040 * 27041 * @param {Object} options 27042 * @param {Number} options.passes The number of filters remaining to be executed 27043 * @param {Boolean} options.webgl Whether to use webgl to render the filter. 27044 * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. 27045 * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. 27046 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 27047 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 27048 */ 27049 applyTo: function (options) { 27050 if (options.webgl) { 27051 options.passes++; 27052 this.width = options.sourceWidth; 27053 this.horizontal = true; 27054 this.dW = Math.round(this.width * this.scaleX); 27055 this.dH = options.sourceHeight; 27056 this.tempScale = this.dW / this.width; 27057 this.taps = this.getTaps(); 27058 options.destinationWidth = this.dW; 27059 this._setupFrameBuffer(options); 27060 this.applyToWebGL(options); 27061 this._swapTextures(options); 27062 options.sourceWidth = options.destinationWidth; 27063 27064 this.height = options.sourceHeight; 27065 this.horizontal = false; 27066 this.dH = Math.round(this.height * this.scaleY); 27067 this.tempScale = this.dH / this.height; 27068 this.taps = this.getTaps(); 27069 options.destinationHeight = this.dH; 27070 this._setupFrameBuffer(options); 27071 this.applyToWebGL(options); 27072 this._swapTextures(options); 27073 options.sourceHeight = options.destinationHeight; 27074 } else { 27075 this.applyTo2d(options); 27076 } 27077 }, 27078 27079 isNeutralState: function () { 27080 return this.scaleX === 1 && this.scaleY === 1; 27081 }, 27082 27083 lanczosCreate: function (lobes) { 27084 return function (x) { 27085 if (x >= lobes || x <= -lobes) { 27086 return 0.0; 27087 } 27088 if (x < 1.1920929e-7 && x > -1.1920929e-7) { 27089 return 1.0; 27090 } 27091 x *= Math.PI; 27092 var xx = x / lobes; 27093 return ((sin(x) / x) * sin(xx)) / xx; 27094 }; 27095 }, 27096 27097 /** 27098 * Applies filter to canvas element 27099 * @memberOf fabric.Image.filters.Resize.prototype 27100 * @param {Object} canvasEl Canvas element to apply filter to 27101 * @param {Number} scaleX 27102 * @param {Number} scaleY 27103 */ 27104 applyTo2d: function (options) { 27105 var imageData = options.imageData, 27106 scaleX = this.scaleX, 27107 scaleY = this.scaleY; 27108 27109 this.rcpScaleX = 1 / scaleX; 27110 this.rcpScaleY = 1 / scaleY; 27111 27112 var oW = imageData.width, 27113 oH = imageData.height, 27114 dW = round(oW * scaleX), 27115 dH = round(oH * scaleY), 27116 newData; 27117 27118 if (this.resizeType === "sliceHack") { 27119 newData = this.sliceByTwo(options, oW, oH, dW, dH); 27120 } else if (this.resizeType === "hermite") { 27121 newData = this.hermiteFastResize(options, oW, oH, dW, dH); 27122 } else if (this.resizeType === "bilinear") { 27123 newData = this.bilinearFiltering(options, oW, oH, dW, dH); 27124 } else if (this.resizeType === "lanczos") { 27125 newData = this.lanczosResize(options, oW, oH, dW, dH); 27126 } 27127 options.imageData = newData; 27128 }, 27129 27130 /** 27131 * Filter sliceByTwo 27132 * @param {Object} canvasEl Canvas element to apply filter to 27133 * @param {Number} oW Original Width 27134 * @param {Number} oH Original Height 27135 * @param {Number} dW Destination Width 27136 * @param {Number} dH Destination Height 27137 * @returns {ImageData} 27138 */ 27139 sliceByTwo: function (options, oW, oH, dW, dH) { 27140 var imageData = options.imageData, 27141 mult = 0.5, 27142 doneW = false, 27143 doneH = false, 27144 stepW = oW * mult, 27145 stepH = oH * mult, 27146 resources = fabric.filterBackend.resources, 27147 tmpCanvas, 27148 ctx, 27149 sX = 0, 27150 sY = 0, 27151 dX = oW, 27152 dY = 0; 27153 if (!resources.sliceByTwo) { 27154 resources.sliceByTwo = document.createElement("canvas"); 27155 } 27156 tmpCanvas = resources.sliceByTwo; 27157 if (tmpCanvas.width < oW * 1.5 || tmpCanvas.height < oH) { 27158 tmpCanvas.width = oW * 1.5; 27159 tmpCanvas.height = oH; 27160 } 27161 ctx = tmpCanvas.getContext("2d"); 27162 ctx.clearRect(0, 0, oW * 1.5, oH); 27163 ctx.putImageData(imageData, 0, 0); 27164 27165 dW = floor(dW); 27166 dH = floor(dH); 27167 27168 while (!doneW || !doneH) { 27169 oW = stepW; 27170 oH = stepH; 27171 if (dW < floor(stepW * mult)) { 27172 stepW = floor(stepW * mult); 27173 } else { 27174 stepW = dW; 27175 doneW = true; 27176 } 27177 if (dH < floor(stepH * mult)) { 27178 stepH = floor(stepH * mult); 27179 } else { 27180 stepH = dH; 27181 doneH = true; 27182 } 27183 ctx.drawImage(tmpCanvas, sX, sY, oW, oH, dX, dY, stepW, stepH); 27184 sX = dX; 27185 sY = dY; 27186 dY += stepH; 27187 } 27188 return ctx.getImageData(sX, sY, dW, dH); 27189 }, 27190 27191 /** 27192 * Filter lanczosResize 27193 * @param {Object} canvasEl Canvas element to apply filter to 27194 * @param {Number} oW Original Width 27195 * @param {Number} oH Original Height 27196 * @param {Number} dW Destination Width 27197 * @param {Number} dH Destination Height 27198 * @returns {ImageData} 27199 */ 27200 lanczosResize: function (options, oW, oH, dW, dH) { 27201 function process(u) { 27202 var v, i, weight, idx, a, red, green, blue, alpha, fX, fY; 27203 center.x = (u + 0.5) * ratioX; 27204 icenter.x = floor(center.x); 27205 for (v = 0; v < dH; v++) { 27206 center.y = (v + 0.5) * ratioY; 27207 icenter.y = floor(center.y); 27208 a = 0; 27209 red = 0; 27210 green = 0; 27211 blue = 0; 27212 alpha = 0; 27213 for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) { 27214 if (i < 0 || i >= oW) { 27215 continue; 27216 } 27217 fX = floor(1000 * abs(i - center.x)); 27218 if (!cacheLanc[fX]) { 27219 cacheLanc[fX] = {}; 27220 } 27221 for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) { 27222 if (j < 0 || j >= oH) { 27223 continue; 27224 } 27225 fY = floor(1000 * abs(j - center.y)); 27226 if (!cacheLanc[fX][fY]) { 27227 cacheLanc[fX][fY] = lanczos( 27228 sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 27229 1000, 27230 ); 27231 } 27232 weight = cacheLanc[fX][fY]; 27233 if (weight > 0) { 27234 idx = (j * oW + i) * 4; 27235 a += weight; 27236 red += weight * srcData[idx]; 27237 green += weight * srcData[idx + 1]; 27238 blue += weight * srcData[idx + 2]; 27239 alpha += weight * srcData[idx + 3]; 27240 } 27241 } 27242 } 27243 idx = (v * dW + u) * 4; 27244 destData[idx] = red / a; 27245 destData[idx + 1] = green / a; 27246 destData[idx + 2] = blue / a; 27247 destData[idx + 3] = alpha / a; 27248 } 27249 27250 if (++u < dW) { 27251 return process(u); 27252 } else { 27253 return destImg; 27254 } 27255 } 27256 27257 var srcData = options.imageData.data, 27258 destImg = options.ctx.createImageData(dW, dH), 27259 destData = destImg.data, 27260 lanczos = this.lanczosCreate(this.lanczosLobes), 27261 ratioX = this.rcpScaleX, 27262 ratioY = this.rcpScaleY, 27263 rcpRatioX = 2 / this.rcpScaleX, 27264 rcpRatioY = 2 / this.rcpScaleY, 27265 range2X = ceil((ratioX * this.lanczosLobes) / 2), 27266 range2Y = ceil((ratioY * this.lanczosLobes) / 2), 27267 cacheLanc = {}, 27268 center = {}, 27269 icenter = {}; 27270 27271 return process(0); 27272 }, 27273 27274 /** 27275 * bilinearFiltering 27276 * @param {Object} canvasEl Canvas element to apply filter to 27277 * @param {Number} oW Original Width 27278 * @param {Number} oH Original Height 27279 * @param {Number} dW Destination Width 27280 * @param {Number} dH Destination Height 27281 * @returns {ImageData} 27282 */ 27283 bilinearFiltering: function (options, oW, oH, dW, dH) { 27284 var a, 27285 b, 27286 c, 27287 d, 27288 x, 27289 y, 27290 i, 27291 j, 27292 xDiff, 27293 yDiff, 27294 chnl, 27295 color, 27296 offset = 0, 27297 origPix, 27298 ratioX = this.rcpScaleX, 27299 ratioY = this.rcpScaleY, 27300 w4 = 4 * (oW - 1), 27301 img = options.imageData, 27302 pixels = img.data, 27303 destImage = options.ctx.createImageData(dW, dH), 27304 destPixels = destImage.data; 27305 for (i = 0; i < dH; i++) { 27306 for (j = 0; j < dW; j++) { 27307 x = floor(ratioX * j); 27308 y = floor(ratioY * i); 27309 xDiff = ratioX * j - x; 27310 yDiff = ratioY * i - y; 27311 origPix = 4 * (y * oW + x); 27312 27313 for (chnl = 0; chnl < 4; chnl++) { 27314 a = pixels[origPix + chnl]; 27315 b = pixels[origPix + 4 + chnl]; 27316 c = pixels[origPix + w4 + chnl]; 27317 d = pixels[origPix + w4 + 4 + chnl]; 27318 color = 27319 a * (1 - xDiff) * (1 - yDiff) + 27320 b * xDiff * (1 - yDiff) + 27321 c * yDiff * (1 - xDiff) + 27322 d * xDiff * yDiff; 27323 destPixels[offset++] = color; 27324 } 27325 } 27326 } 27327 return destImage; 27328 }, 27329 27330 /** 27331 * hermiteFastResize 27332 * @param {Object} canvasEl Canvas element to apply filter to 27333 * @param {Number} oW Original Width 27334 * @param {Number} oH Original Height 27335 * @param {Number} dW Destination Width 27336 * @param {Number} dH Destination Height 27337 * @returns {ImageData} 27338 */ 27339 hermiteFastResize: function (options, oW, oH, dW, dH) { 27340 var ratioW = this.rcpScaleX, 27341 ratioH = this.rcpScaleY, 27342 ratioWHalf = ceil(ratioW / 2), 27343 ratioHHalf = ceil(ratioH / 2), 27344 img = options.imageData, 27345 data = img.data, 27346 img2 = options.ctx.createImageData(dW, dH), 27347 data2 = img2.data; 27348 for (var j = 0; j < dH; j++) { 27349 for (var i = 0; i < dW; i++) { 27350 var x2 = (i + j * dW) * 4, 27351 weight = 0, 27352 weights = 0, 27353 weightsAlpha = 0, 27354 gxR = 0, 27355 gxG = 0, 27356 gxB = 0, 27357 gxA = 0, 27358 centerY = (j + 0.5) * ratioH; 27359 for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) { 27360 var dy = abs(centerY - (yy + 0.5)) / ratioHHalf, 27361 centerX = (i + 0.5) * ratioW, 27362 w0 = dy * dy; 27363 for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) { 27364 var dx = abs(centerX - (xx + 0.5)) / ratioWHalf, 27365 w = sqrt(w0 + dx * dx); 27366 /* eslint-disable max-depth */ 27367 if (w > 1 && w < -1) { 27368 continue; 27369 } 27370 //hermite filter 27371 weight = 2 * w * w * w - 3 * w * w + 1; 27372 if (weight > 0) { 27373 dx = 4 * (xx + yy * oW); 27374 //alpha 27375 gxA += weight * data[dx + 3]; 27376 weightsAlpha += weight; 27377 //colors 27378 if (data[dx + 3] < 255) { 27379 weight = (weight * data[dx + 3]) / 250; 27380 } 27381 gxR += weight * data[dx]; 27382 gxG += weight * data[dx + 1]; 27383 gxB += weight * data[dx + 2]; 27384 weights += weight; 27385 } 27386 /* eslint-enable max-depth */ 27387 } 27388 } 27389 data2[x2] = gxR / weights; 27390 data2[x2 + 1] = gxG / weights; 27391 data2[x2 + 2] = gxB / weights; 27392 data2[x2 + 3] = gxA / weightsAlpha; 27393 } 27394 } 27395 return img2; 27396 }, 27397 27398 /** 27399 * Returns object representation of an instance 27400 * @return {Object} Object representation of an instance 27401 */ 27402 toObject: function () { 27403 return { 27404 type: this.type, 27405 scaleX: this.scaleX, 27406 scaleY: this.scaleY, 27407 resizeType: this.resizeType, 27408 lanczosLobes: this.lanczosLobes, 27409 }; 27410 }, 27411 }, 27412 ); 27413 27414 /** 27415 * Returns filter instance from an object representation 27416 * @static 27417 * @param {Object} object Object to create an instance from 27418 * @param {Function} [callback] to be invoked after filter creation 27419 * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize 27420 */ 27421 fabric.Image.filters.Resize.fromObject = 27422 fabric.Image.filters.BaseFilter.fromObject; 27423})(typeof exports !== "undefined" ? exports : this); 27424 27425(function (global) { 27426 "use strict"; 27427 27428 var fabric = global.fabric || (global.fabric = {}), 27429 filters = fabric.Image.filters, 27430 createClass = fabric.util.createClass; 27431 27432 /** 27433 * Contrast filter class 27434 * @class fabric.Image.filters.Contrast 27435 * @memberOf fabric.Image.filters 27436 * @extends fabric.Image.filters.BaseFilter 27437 * @see {@link fabric.Image.filters.Contrast#initialize}
27437 for constructor definition 27438 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 27439 * @example 27440 * var filter = new fabric.Image.filters.Contrast({ 27441 * contrast: 0.25 27442 * }); 27443 * object.filters.push(filter); 27444 * object.applyFilters(); 27445 */ 27446 filters.Contrast = createClass( 27447 filters.BaseFilter, 27448 /** @lends fabric.Image.filters.Contrast.prototype */ { 27449 /** 27450 * Filter type 27451 * @param {String} type 27452 * @default 27453 */ 27454 type: "Contrast", 27455 27456 fragmentSource: 27457 "precision highp float;\n" + 27458 "uniform sampler2D uTexture;\n" + 27459 "uniform float uContrast;\n" + 27460 "varying vec2 vTexCoord;\n" + 27461 "void main() {\n" + 27462 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 27463 "float contrastF = 1.015 * (uContrast + 1.0) / (1.0 * (1.015 - uContrast));\n" + 27464 "color.rgb = contrastF * (color.rgb - 0.5) + 0.5;\n" + 27465 "gl_FragColor = color;\n" + 27466 "}", 27467 27468 /** 27469 * contrast value, range from -1 to 1. 27470 * @param {Number} contrast 27471 * @default 0 27472 */ 27473 contrast: 0, 27474 27475 mainParameter: "contrast", 27476 27477 /** 27478 * Constructor 27479 * @memberOf fabric.Image.filters.Contrast.prototype 27480 * @param {Object} [options] Options object 27481 * @param {Number} [options.contrast=0] Value to contrast the image up (-1...1) 27482 */ 27483 27484 /** 27485 * Apply the Contrast operation to a Uint8Array representing the pixels of an image. 27486 * 27487 * @param {Object} options 27488 * @param {ImageData} options.imageData The Uint8Array to be filtered. 27489 */ 27490 applyTo2d: function (options) { 27491 if (this.contrast === 0) { 27492 return; 27493 } 27494 var imageData = options.imageData, 27495 i, 27496 len, 27497 data = imageData.data, 27498 len = data.length, 27499 contrast = Math.floor(this.contrast * 255), 27500 contrastF = (259 * (contrast + 255)) / (255 * (259 - contrast)); 27501 27502 for (i = 0; i < len; i += 4) { 27503 data[i] = contrastF * (data[i] - 128) + 128; 27504 data[i + 1] = contrastF * (data[i + 1] - 128) + 128; 27505 data[i + 2] = contrastF * (data[i + 2] - 128) + 128; 27506 } 27507 }, 27508 27509 /** 27510 * Return WebGL uniform locations for this filter's shader. 27511 * 27512 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 27513 * @param {WebGLShaderProgram} program This filter's compiled shader program. 27514 */ 27515 getUniformLocations: function (gl, program) { 27516 return { 27517 uContrast: gl.getUniformLocation(program, "uContrast"), 27518 }; 27519 }, 27520 27521 /** 27522 * Send data from this filter to its shader program's uniforms. 27523 * 27524 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 27525 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 27526 */ 27527 sendUniformData: function (gl, uniformLocations) { 27528 gl.uniform1f(uniformLocations.uContrast, this.contrast); 27529 }, 27530 }, 27531 ); 27532 27533 /** 27534 * Returns filter instance from an object representation 27535 * @static 27536 * @param {Object} object Object to create an instance from 27537 * @param {function} [callback] to be invoked after filter creation 27538 * @return {fabric.Image.filters.Contrast} Instance of fabric.Image.filters.Contrast 27539 */ 27540 fabric.Image.filters.Contrast.fromObject = 27541 fabric.Image.filters.BaseFilter.fromObject; 27542})(typeof exports !== "undefined" ? exports : this); 27543 27544(function (global) { 27545 "use strict"; 27546 27547 var fabric = global.fabric || (global.fabric = {}), 27548 filters = fabric.Image.filters, 27549 createClass = fabric.util.createClass; 27550 27551 /** 27552 * Saturate filter class 27553 * @class fabric.Image.filters.Saturation 27554 * @memberOf fabric.Image.filters 27555 * @extends fabric.Image.filters.BaseFilter 27556 * @see {@link fabric.Image.filters.Saturation#initialize} for constructor definition 27557 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 27558 * @example 27559 * var filter = new fabric.Image.filters.Saturation({ 27560 * saturation: 1 27561 * }); 27562 * object.filters.push(filter); 27563 * object.applyFilters(); 27564 */ 27565 filters.Saturation = createClass( 27566 filters.BaseFilter, 27567 /** @lends fabric.Image.filters.Saturation.prototype */ { 27568 /** 27569 * Filter type 27570 * @param {String} type 27571 * @default 27572 */ 27573 type: "Saturation", 27574 27575 fragmentSource: 27576 "precision highp float;\n" + 27577 "uniform sampler2D uTexture;\n" + 27578 "uniform float uSaturation;\n" + 27579 "varying vec2 vTexCoord;\n" + 27580 "void main() {\n" + 27581 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 27582 "float rgMax = max(color.r, color.g);\n" + 27583 "float rgbMax = max(rgMax, color.b);\n" + 27584 "color.r += rgbMax != color.r ? (rgbMax - color.r) * uSaturation : 0.00;\n" + 27585 "color.g += rgbMax != color.g ? (rgbMax - color.g) * uSaturation : 0.00;\n" + 27586 "color.b += rgbMax != color.b ? (rgbMax - color.b) * uSaturation : 0.00;\n" + 27587 "gl_FragColor = color;\n" + 27588 "}", 27589 27590 /** 27591 * Saturation value, from -1 to 1. 27592 * Increases/decreases the color saturation. 27593 * A value of 0 has no effect. 27594 * 27595 * @param {Number} saturation 27596 * @default 27597 */ 27598 saturation: 0, 27599 27600 mainParameter: "saturation", 27601 27602 /** 27603 * Constructor 27604 * @memberOf fabric.Image.filters.Saturate.prototype 27605 * @param {Object} [options] Options object 27606 * @param {Number} [options.saturate=0] Value to saturate the image (-1...1) 27607 */ 27608 27609 /** 27610 * Apply the Saturation operation to a Uint8ClampedArray representing the pixels of an image. 27611 * 27612 * @param {Object} options 27613 * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. 27614 */ 27615 applyTo2d: function (options) { 27616 if (this.saturation === 0) { 27617 return; 27618 } 27619 var imageData = options.imageData, 27620 data = imageData.data, 27621 len = data.length, 27622 adjust = -this.saturation, 27623 i, 27624 max; 27625 27626 for (i = 0; i < len; i += 4) { 27627 max = Math.max(data[i], data[i + 1], data[i + 2]); 27628 data[i] += max !== data[i] ? (max - data[i]) * adjust : 0; 27629 data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * adjust : 0; 27630 data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * adjust : 0; 27631 } 27632 }, 27633 27634 /** 27635 * Return WebGL uniform locations for this filter's shader. 27636 * 27637 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 27638 * @param {WebGLShaderProgram} program This filter's compiled shader program. 27639 */ 27640 getUniformLocations: function (gl, program) { 27641 return { 27642 uSaturation: gl.getUniformLocation(program, "uSaturation"), 27643 }; 27644 }, 27645 27646 /** 27647 * Send data from this filter to its shader program's uniforms. 27648 * 27649 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 27650 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 27651 */ 27652 sendUniformData: function (gl, uniformLocations) { 27653 gl.uniform1f(uniformLocations.uSaturation, -this.saturation); 27654 }, 27655 }, 27656 ); 27657 27658 /** 27659 * Returns filter instance from an object representation 27660 * @static 27661 * @param {Object} object Object to create an instance from 27662 * @param {Function} [callback] to be invoked after filter creation 27663 * @return {fabric.Image.filters.Saturation} Instance of fabric.Image.filters.Saturate 27664 */ 27665 fabric.Image.filters.Saturation.fromObject = 27666 fabric.Image.filters.BaseFilter.fromObject; 27667})(typeof exports !== "undefined" ? exports : this); 27668 27669(function (global) { 27670 "use strict"; 27671 27672 var fabric = global.fabric || (global.fabric = {}), 27673 filters = fabric.Image.filters, 27674 createClass = fabric.util.createClass; 27675 27676 /** 27677 * Vibrance filter class 27678 * @class fabric.Image.filters.Vibrance 27679 * @memberOf fabric.Image.filters 27680 * @extends fabric.Image.filters.BaseFilter 27681 * @see {@link fabric.Image.filters.Vibrance#initialize}
27681 for constructor definition 27682 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 27683 * @example 27684 * var filter = new fabric.Image.filters.Vibrance({ 27685 * vibrance: 1 27686 * }); 27687 * object.filters.push(filter); 27688 * object.applyFilters(); 27689 */ 27690 filters.Vibrance = createClass( 27691 filters.BaseFilter, 27692 /** @lends fabric.Image.filters.Vibrance.prototype */ { 27693 /** 27694 * Filter type 27695 * @param {String} type 27696 * @default 27697 */ 27698 type: "Vibrance", 27699 27700 fragmentSource: 27701 "precision highp float;\n" + 27702 "uniform sampler2D uTexture;\n" + 27703 "uniform float uVibrance;\n" + 27704 "varying vec2 vTexCoord;\n" + 27705 "void main() {\n" + 27706 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 27707 "float max = max(color.r, max(color.g, color.b));\n" + 27708 "float avg = (color.r + color.g + color.b) / 3.0;\n" + 27709 "float amt = (abs(max - avg) * 2.0) * uVibrance;\n" + 27710 "color.r += max != color.r ? (max - color.r) * amt : 0.00;\n" + 27711 "color.g += max != color.g ? (max - color.g) * amt : 0.00;\n" + 27712 "color.b += max != color.b ? (max - color.b) * amt : 0.00;\n" + 27713 "gl_FragColor = color;\n" + 27714 "}", 27715 27716 /** 27717 * Vibrance value, from -1 to 1. 27718 * Increases/decreases the saturation of more muted colors with less effect on saturated colors. 27719 * A value of 0 has no effect. 27720 * 27721 * @param {Number} vibrance 27722 * @default 27723 */ 27724 vibrance: 0, 27725 27726 mainParameter: "vibrance", 27727 27728 /** 27729 * Constructor 27730 * @memberOf fabric.Image.filters.Vibrance.prototype 27731 * @param {Object} [options] Options object 27732 * @param {Number} [options.vibrance=0] Vibrance value for the image (between -1 and 1) 27733 */ 27734 27735 /** 27736 * Apply the Vibrance operation to a Uint8ClampedArray representing the pixels of an image. 27737 * 27738 * @param {Object} options 27739 * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. 27740 */ 27741 applyTo2d: function (options) { 27742 if (this.vibrance === 0) { 27743 return; 27744 } 27745 var imageData = options.imageData, 27746 data = imageData.data, 27747 len = data.length, 27748 adjust = -this.vibrance, 27749 i, 27750 max, 27751 avg, 27752 amt; 27753 27754 for (i = 0; i < len; i += 4) { 27755 max = Math.max(data[i], data[i + 1], data[i + 2]); 27756 avg = (data[i] + data[i + 1] + data[i + 2]) / 3; 27757 amt = ((Math.abs(max - avg) * 2) / 255) * adjust; 27758 data[i] += max !== data[i] ? (max - data[i]) * amt : 0; 27759 data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * amt : 0; 27760 data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * amt : 0; 27761 } 27762 }, 27763 27764 /** 27765 * Return WebGL uniform locations for this filter's shader. 27766 * 27767 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 27768 * @param {WebGLShaderProgram} program This filter's compiled shader program. 27769 */ 27770 getUniformLocations: function (gl, program) { 27771 return { 27772 uVibrance: gl.getUniformLocation(program, "uVibrance"), 27773 }; 27774 }, 27775 27776 /** 27777 * Send data from this filter to its shader program's uniforms. 27778 * 27779 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 27780 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 27781 */ 27782 sendUniformData: function (gl, uniformLocations) { 27783 gl.uniform1f(uniformLocations.uVibrance, -this.vibrance); 27784 }, 27785 }, 27786 ); 27787 27788 /** 27789 * Returns filter instance from an object representation 27790 * @static 27791 * @param {Object} object Object to create an instance from 27792 * @param {Function} [callback] to be invoked after filter creation 27793 * @return {fabric.Image.filters.Vibrance} Instance of fabric.Image.filters.Vibrance 27794 */ 27795 fabric.Image.filters.Vibrance.fromObject = 27796 fabric.Image.filters.BaseFilter.fromObject; 27797})(typeof exports !== "undefined" ? exports : this); 27798 27799(function (global) { 27800 "use strict"; 27801 27802 var fabric = global.fabric || (global.fabric = {}), 27803 filters = fabric.Image.filters, 27804 createClass = fabric.util.createClass; 27805 27806 /** 27807 * Blur filter class 27808 * @class fabric.Image.filters.Blur 27809 * @memberOf fabric.Image.filters 27810 * @extends fabric.Image.filters.BaseFilter 27811 * @see {@link fabric.Image.filters.Blur#initialize}
27811 for constructor definition 27812 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 27813 * @example 27814 * var filter = new fabric.Image.filters.Blur({ 27815 * blur: 0.5 27816 * }); 27817 * object.filters.push(filter); 27818 * object.applyFilters(); 27819 * canvas.renderAll(); 27820 */ 27821 filters.Blur = createClass( 27822 filters.BaseFilter, 27823 /** @lends fabric.Image.filters.Blur.prototype */ { 27824 type: "Blur", 27825 27826 /* 27827 'gl_FragColor = vec4(0.0);', 27828 'gl_FragColor += texture2D(texture, vTexCoord + -7 * uDelta)*0.0044299121055113265;', 27829 'gl_FragColor += texture2D(texture, vTexCoord + -6 * uDelta)*0.00895781211794;', 27830 'gl_FragColor += texture2D(texture, vTexCoord + -5 * uDelta)*0.0215963866053;', 27831 'gl_FragColor += texture2D(texture, vTexCoord + -4 * uDelta)*0.0443683338718;', 27832 'gl_FragColor += texture2D(texture, vTexCoord + -3 * uDelta)*0.0776744219933;', 27833 'gl_FragColor += texture2D(texture, vTexCoord + -2 * uDelta)*0.115876621105;', 27834 'gl_FragColor += texture2D(texture, vTexCoord + -1 * uDelta)*0.147308056121;', 27835 'gl_FragColor += texture2D(texture, vTexCoord )*0.159576912161;', 27836 'gl_FragColor += texture2D(texture, vTexCoord + 1 * uDelta)*0.147308056121;', 27837 'gl_FragColor += texture2D(texture, vTexCoord + 2 * uDelta)*0.115876621105;', 27838 'gl_FragColor += texture2D(texture, vTexCoord + 3 * uDelta)*0.0776744219933;', 27839 'gl_FragColor += texture2D(texture, vTexCoord + 4 * uDelta)*0.0443683338718;', 27840 'gl_FragColor += texture2D(texture, vTexCoord + 5 * uDelta)*0.0215963866053;', 27841 'gl_FragColor += texture2D(texture, vTexCoord + 6 * uDelta)*0.00895781211794;', 27842 'gl_FragColor += texture2D(texture, vTexCoord + 7 * uDelta)*0.0044299121055113265;', 27843 */ 27844 27845 /* eslint-disable max-len */ 27846 fragmentSource: 27847 "precision highp float;\n" + 27848 "uniform sampler2D uTexture;\n" + 27849 "uniform vec2 uDelta;\n" + 27850 "varying vec2 vTexCoord;\n" + 27851 "const float nSamples = 15.0;\n" + 27852 "vec3 v3offset = vec3(12.9898, 78.233, 151.7182);\n" + 27853 "float random(vec3 scale) {\n" + 27854 /* use the fragment position for a different seed per-pixel */ 27855 "return fract(sin(dot(gl_FragCoord.xyz, scale)) * 43758.5453);\n" + 27856 "}\n" + 27857 "void main() {\n" + 27858 "vec4 color = vec4(0.0);\n" + 27859 "float total = 0.0;\n" + 27860 "float offset = random(v3offset);\n" + 27861 "for (float t = -nSamples; t <= nSamples; t++) {\n" + 27862 "float percent = (t + offset - 0.5) / nSamples;\n" + 27863 "float weight = 1.0 - abs(percent);\n" + 27864 "color += texture2D(uTexture, vTexCoord + uDelta * percent) * weight;\n" + 27865 "total += weight;\n" + 27866 "}\n" + 27867 "gl_FragColor = color / total;\n" + 27868 "}", 27869 /* eslint-enable max-len */ 27870 27871 /** 27872 * blur value, in percentage of image dimensions. 27873 * specific to keep the image blur constant at different resolutions 27874 * range between 0 and 1. 27875 * @type Number 27876 * @default 27877 */ 27878 blur: 0, 27879 27880 mainParameter: "blur", 27881 27882 applyTo: function (options) { 27883 if (options.webgl) { 27884 // this aspectRatio is used to give the same blur to vertical and horizontal 27885 this.aspectRatio = options.sourceWidth / options.sourceHeight; 27886 options.passes++; 27887 this._setupFrameBuffer(options); 27888 this.horizontal = true; 27889 this.applyToWebGL(options); 27890 this._swapTextures(options); 27891 this._setupFrameBuffer(options); 27892 this.horizontal = false; 27893 this.applyToWebGL(options); 27894 this._swapTextures(options); 27895 } else { 27896 this.applyTo2d(options); 27897 } 27898 }, 27899 27900 applyTo2d: function (options) { 27901 // paint canvasEl with current image data. 27902 //options.ctx.putImageData(options.imageData, 0, 0); 27903 options.imageData = this.simpleBlur(options); 27904 }, 27905 27906 simpleBlur: function (options) { 27907 var resources = options.filterBackend.resources, 27908 canvas1, 27909 canvas2, 27910 width = options.imageData.width, 27911 height = options.imageData.height; 27912 27913 if (!resources.blurLayer1) { 27914 resources.blurLayer1 = fabric.util.createCanvasElement(); 27915 resources.blurLayer2 = fabric.util.createCanvasElement(); 27916 } 27917 canvas1 = resources.blurLayer1; 27918 canvas2 = resources.blurLayer2; 27919 if (canvas1.width !== width || canvas1.height !== height) { 27920 canvas2.width = canvas1.width = width; 27921 canvas2.height = canvas1.height = height; 27922 } 27923 var ctx1 = canvas1.getContext("2d"), 27924 ctx2 = canvas2.getContext("2d"), 27925 nSamples = 15, 27926 random, 27927 percent, 27928 j, 27929 i, 27930 blur = this.blur * 0.06 * 0.5; 27931 27932 // load first canvas 27933 ctx1.putImageData(options.imageData, 0, 0); 27934 ctx2.clearRect(0, 0, width, height); 27935 27936 for (i = -nSamples; i <= nSamples; i++) { 27937 random = (Math.random() - 0.5) / 4; 27938 percent = i / nSamples; 27939 j = blur * percent * width + random; 27940 ctx2.globalAlpha = 1 - Math.abs(percent); 27941 ctx2.drawImage(canvas1, j, random);
vendor: 8,633 bytes, lines 27942-28207
27942 ctx1.drawImage(canvas2, 0, 0); 27943 ctx2.globalAlpha = 1; 27944 ctx2.clearRect(0, 0, canvas2.width, canvas2.height); 27945 } 27946 for (i = -nSamples; i <= nSamples; i++) { 27947 random = (Math.random() - 0.5) / 4; 27948 percent = i / nSamples; 27949 j = blur * percent * height + random; 27950 ctx2.globalAlpha = 1 - Math.abs(percent); 27951 ctx2.drawImage(canvas1, random, j); 27952 ctx1.drawImage(canvas2, 0, 0); 27953 ctx2.globalAlpha = 1; 27954 ctx2.clearRect(0, 0, canvas2.width, canvas2.height); 27955 } 27956 options.ctx.drawImage(canvas1, 0, 0); 27957 var newImageData = options.ctx.getImageData( 27958 0, 27959 0, 27960 canvas1.width, 27961 canvas1.height, 27962 ); 27963 ctx1.globalAlpha = 1; 27964 ctx1.clearRect(0, 0, canvas1.width, canvas1.height); 27965 return newImageData; 27966 }, 27967 27968 /** 27969 * Return WebGL uniform locations for this filter's shader. 27970 * 27971 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 27972 * @param {WebGLShaderProgram} program This filter's compiled shader program. 27973 */ 27974 getUniformLocations: function (gl, program) { 27975 return { 27976 delta: gl.getUniformLocation(program, "uDelta"), 27977 }; 27978 }, 27979 27980 /** 27981 * Send data from this filter to its shader program's uniforms. 27982 * 27983 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 27984 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 27985 */ 27986 sendUniformData: function (gl, uniformLocations) { 27987 var delta = this.chooseRightDelta(); 27988 gl.uniform2fv(uniformLocations.delta, delta); 27989 }, 27990 27991 /** 27992 * choose right value of image percentage to blur with 27993 * @returns {Array} a numeric array with delta values 27994 */ 27995 chooseRightDelta: function () { 27996 var blurScale = 1, 27997 delta = [0, 0], 27998 blur; 27999 if (this.horizontal) { 28000 if (this.aspectRatio > 1) { 28001 // image is wide, i want to shrink radius horizontal 28002 blurScale = 1 / this.aspectRatio; 28003 } 28004 } else { 28005 if (this.aspectRatio < 1) { 28006 // image is tall, i want to shrink radius vertical 28007 blurScale = this.aspectRatio; 28008 } 28009 } 28010 blur = blurScale * this.blur * 0.12; 28011 if (this.horizontal) { 28012 delta[0] = blur; 28013 } else { 28014 delta[1] = blur; 28015 } 28016 return delta; 28017 }, 28018 }, 28019 ); 28020 28021 /** 28022 * Deserialize a JSON definition of a BlurFilter into a concrete instance. 28023 */ 28024 filters.Blur.fromObject = fabric.Image.filters.BaseFilter.fromObject; 28025})(typeof exports !== "undefined" ? exports : this); 28026 28027(function (global) { 28028 "use strict"; 28029 28030 var fabric = global.fabric || (global.fabric = {}), 28031 filters = fabric.Image.filters, 28032 createClass = fabric.util.createClass; 28033 28034 /** 28035 * Gamma filter class 28036 * @class fabric.Image.filters.Gamma 28037 * @memberOf fabric.Image.filters 28038 * @extends fabric.Image.filters.BaseFilter 28039 * @see {@link fabric.Image.filters.Gamma#initialize} for constructor definition 28040 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 28041 * @example 28042 * var filter = new fabric.Image.filters.Gamma({ 28043 * gamma: [1, 0.5, 2.1] 28044 * }); 28045 * object.filters.push(filter); 28046 * object.applyFilters(); 28047 */ 28048 filters.Gamma = createClass( 28049 filters.BaseFilter, 28050 /** @lends fabric.Image.filters.Gamma.prototype */ { 28051 /** 28052 * Filter type 28053 * @param {String} type 28054 * @default 28055 */ 28056 type: "Gamma", 28057 28058 fragmentSource: 28059 "precision highp float;\n" + 28060 "uniform sampler2D uTexture;\n" + 28061 "uniform vec3 uGamma;\n" + 28062 "varying vec2 vTexCoord;\n" + 28063 "void main() {\n" + 28064 "vec4 color = texture2D(uTexture, vTexCoord);\n" + 28065 "vec3 correction = (1.0 / uGamma);\n" + 28066 "color.r = pow(color.r, correction.r);\n" + 28067 "color.g = pow(color.g, correction.g);\n" + 28068 "color.b = pow(color.b, correction.b);\n" + 28069 "gl_FragColor = color;\n" + 28070 "gl_FragColor.rgb *= color.a;\n" + 28071 "}", 28072 28073 /** 28074 * Gamma array value, from 0.01 to 2.2. 28075 * @param {Array} gamma 28076 * @default 28077 */ 28078 gamma: [1, 1, 1], 28079 28080 /** 28081 * Describe the property that is the filter parameter 28082 * @param {String} m 28083 * @default 28084 */ 28085 mainParameter: "gamma", 28086 28087 /** 28088 * Constructor 28089 * @param {Object} [options] Options object 28090 */ 28091 initialize: function (options) { 28092 this.gamma = [1, 1, 1]; 28093 filters.BaseFilter.prototype.initialize.call(this, options); 28094 }, 28095 28096 /** 28097 * Apply the Gamma operation to a Uint8Array representing the pixels of an image. 28098 * 28099 * @param {Object} options 28100 * @param {ImageData} options.imageData The Uint8Array to be filtered. 28101 */ 28102 applyTo2d: function (options) { 28103 var imageData = options.imageData, 28104 data = imageData.data, 28105 gamma = this.gamma, 28106 len = data.length, 28107 rInv = 1 / gamma[0], 28108 gInv = 1 / gamma[1], 28109 bInv = 1 / gamma[2], 28110 i; 28111 28112 if (!this.rVals) { 28113 // eslint-disable-next-line 28114 this.rVals = new Uint8Array(256); 28115 // eslint-disable-next-line 28116 this.gVals = new Uint8Array(256); 28117 // eslint-disable-next-line 28118 this.bVals = new Uint8Array(256); 28119 } 28120 28121 // This is an optimization - pre-compute a look-up table for each color channel 28122 // instead of performing these pow calls for each pixel in the image. 28123 for (i = 0, len = 256; i < len; i++) { 28124 this.rVals[i] = Math.pow(i / 255, rInv) * 255; 28125 this.gVals[i] = Math.pow(i / 255, gInv) * 255; 28126 this.bVals[i] = Math.pow(i / 255, bInv) * 255; 28127 } 28128 for (i = 0, len = data.length; i < len; i += 4) { 28129 data[i] = this.rVals[data[i]]; 28130 data[i + 1] = this.gVals[data[i + 1]]; 28131 data[i + 2] = this.bVals[data[i + 2]]; 28132 } 28133 }, 28134 28135 /** 28136 * Return WebGL uniform locations for this filter's shader. 28137 * 28138 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 28139 * @param {WebGLShaderProgram} program This filter's compiled shader program. 28140 */ 28141 getUniformLocations: function (gl, program) { 28142 return { 28143 uGamma: gl.getUniformLocation(program, "uGamma"), 28144 }; 28145 }, 28146 28147 /** 28148 * Send data from this filter to its shader program's uniforms. 28149 * 28150 * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. 28151 * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects 28152 */ 28153 sendUniformData: function (gl, uniformLocations) { 28154 gl.uniform3fv(uniformLocations.uGamma, this.gamma); 28155 }, 28156 }, 28157 ); 28158 28159 /** 28160 * Returns filter instance from an object representation 28161 * @static 28162 * @param {Object} object Object to create an instance from 28163 * @param {function} [callback] to be invoked after filter creation 28164 * @return {fabric.Image.filters.Gamma} Instance of fabric.Image.filters.Gamma 28165 */ 28166 fabric.Image.filters.Gamma.fromObject = 28167 fabric.Image.filters.BaseFilter.fromObject; 28168})(typeof exports !== "undefined" ? exports : this); 28169 28170(function (global) { 28171 "use strict"; 28172 28173 var fabric = global.fabric || (global.fabric = {}), 28174 filters = fabric.Image.filters, 28175 createClass = fabric.util.createClass; 28176 28177 /** 28178 * A container class that knows how to apply a sequence of filters to an input image. 28179 */ 28180 filters.Composed = createClass( 28181 filters.BaseFilter, 28182 /** @lends fabric.Image.filters.Composed.prototype */ { 28183 type: "Composed", 28184 28185 /** 28186 * A non sparse array of filters to apply 28187 */ 28188 subFilters: [], 28189 28190 /** 28191 * Constructor 28192 * @param {Object} [options] Options object 28193 */ 28194 initialize: function (options) { 28195 this.callSuper("initialize", options); 28196 // create a new array instead mutating the prototype with push 28197 this.subFilters = this.subFilters.slice(0); 28198 }, 28199 28200 /** 28201 * Apply this container's filters to the input image provided. 28202 * 28203 * @param {Object} options 28204 * @param {Number} options.passes The number of filters remaining to be applied. 28205 */ 28206 applyTo: function (options) { 28207 options.passes += this.subFilters.length - 1;
28208 this.subFilters.forEach(function (filter) { 28209 filter.applyTo(options); 28210 }); 28211 }, 28212 28213 /** 28214 * Serialize this filter into JSON. 28215 * 28216 * @returns {Object} A JSON representation of this filter. 28217 */ 28218 toObject: function () { 28219 return fabric.util.object.extend(this.callSuper("toObject"), { 28220 subFilters: this.subFilters.map(function (filter) { 28221 return filter.toObject(); 28222 }), 28223 }); 28224 }, 28225 28226 isNeutralState: function () { 28227 return !this.subFilters.some(function (filter) { 28228 return !filter.isNeutralState(); 28229 }); 28230 }, 28231 }, 28232 ); 28233 28234 /** 28235 * Deserialize a JSON definition of a ComposedFilter into a concrete instance. 28236 */ 28237 fabric.Image.filters.Composed.fromObject = function (object, callback) { 28238 var filters = object.subFilters || [], 28239 subFilters = filters.map(function (filter) { 28240 return new fabric.Image.filters[filter.type](filter); 28241 }), 28242 instance = new fabric.Image.filters.Composed({ subFilters: subFilters }); 28243 callback && callback(instance); 28244 return instance; 28245 }; 28246})(typeof exports !== "undefined" ? exports : this); 28247 28248(function (global) { 28249 "use strict"; 28250 28251 var fabric = global.fabric || (global.fabric = {}), 28252 filters = fabric.Image.filters, 28253 createClass = fabric.util.createClass; 28254 28255 /** 28256 * HueRotation filter class 28257 * @class fabric.Image.filters.HueRotation 28258 * @memberOf fabric.Image.filters 28259 * @extends fabric.Image.filters.BaseFilter 28260 * @see {@link fabric.Image.filters.HueRotation#initialize} for constructor definition 28261 * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} 28262 * @example 28263 * var filter = new fabric.Image.filters.HueRotation({ 28264 * rotation: -0.5 28265 * }); 28266 * object.filters.push(filter); 28267 * object.applyFilters(); 28268 */ 28269 filters.HueRotation = createClass( 28270 filters.ColorMatrix, 28271 /** @lends fabric.Image.filters.HueRotation.prototype */ { 28272 /** 28273 * Filter type 28274 * @param {String} type 28275 * @default 28276 */ 28277 type: "HueRotation", 28278 28279 /** 28280 * HueRotation value, from -1 to 1. 28281 * the unit is radians 28282 * @param {Number} myParameter 28283 * @default 28284 */ 28285 rotation: 0, 28286 28287 /** 28288 * Describe the property that is the filter parameter 28289 * @param {String} m 28290 * @default 28291 */ 28292 mainParameter: "rotation", 28293 28294 calculateMatrix: function () { 28295 var rad = this.rotation * Math.PI, 28296 cos = fabric.util.cos(rad), 28297 sin = fabric.util.sin(rad), 28298 aThird = 1 / 3, 28299 aThirdSqtSin = Math.sqrt(aThird) * sin, 28300 OneMinusCos = 1 - cos; 28301 this.matrix = [ 28302 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 28303 ]; 28304 this.matrix[0] = cos + OneMinusCos / 3; 28305 this.matrix[1] = aThird * OneMinusCos - aThirdSqtSin; 28306 this.matrix[2] = aThird * OneMinusCos + aThirdSqtSin; 28307 this.matrix[5] = aThird * OneMinusCos + aThirdSqtSin; 28308 this.matrix[6] = cos + aThird * OneMinusCos; 28309 this.matrix[7] = aThird * OneMinusCos - aThirdSqtSin; 28310 this.matrix[10] = aThird * OneMinusCos - aThirdSqtSin; 28311 this.matrix[11] = aThird * OneMinusCos + aThirdSqtSin; 28312 this.matrix[12] = cos + aThird * OneMinusCos; 28313 }, 28314 28315 /** 28316 * HueRotation isNeutralState implementation 28317 * Used only in image applyFilters to discard filters that will not have an effect 28318 * on the image 28319 * @param {Object} options 28320 **/ 28321 isNeutralState: function (options) { 28322 this.calculateMatrix(); 28323 return filters.BaseFilter.prototype.isNeutralState.call(this, options); 28324 }, 28325 28326 /** 28327 * Apply this filter to the input image data provided. 28328 * 28329 * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. 28330 * 28331 * @param {Object} options 28332 * @param {Number} options.passes The number of filters remaining to be executed 28333 * @param {Boolean} options.webgl Whether to use webgl to render the filter. 28334 * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. 28335 * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. 28336 * @param {WebGLRenderingContext} options.context The GL context used for rendering. 28337 * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. 28338 */ 28339 applyTo: function (options) { 28340 this.calculateMatrix(); 28341 filters.BaseFilter.prototype.applyTo.call(this, options); 28342 }, 28343 }, 28344 ); 28345 28346 /** 28347 * Returns filter instance from an object representation 28348 * @static 28349 * @param {Object} object Object to create an instance from 28350 * @param {function} [callback] to be invoked after filter creation 28351 * @return {fabric.Image.filters.HueRotation} Instance of fabric.Image.filters.HueRotation 28352 */ 28353 fabric.Image.filters.HueRotation.fromObject = 28354 fabric.Image.filters.BaseFilter.fromObject; 28355})(typeof exports !== "undefined" ? exports : this); 28356 28357(function (global) { 28358 "use strict"; 28359 28360 var fabric = global.fabric || (global.fabric = {}), 28361 clone = fabric.util.object.clone; 28362 28363 if (fabric.Text) { 28364 fabric.warn("fabric.Text is already defined"); 28365 return; 28366 } 28367 28368 var additionalProps = ( 28369 "fontFamily fontWeight fontSize text underline overline linethrough" +
28370 " textAlign fontStyle lineHeight textBackgroundColor charSpacing styles" + 28371 " direction path pathStartOffset pathSide pathAlign" 28372 ).split(" "); 28373 28374 /** 28375 * Text class 28376 * @class fabric.Text 28377 * @extends fabric.Object 28378 * @return {fabric.Text} thisArg 28379 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#text} 28380 * @see {@link fabric.Text#initialize} for constructor definition 28381 */ 28382 fabric.Text = fabric.util.createClass( 28383 fabric.Object, 28384 /** @lends fabric.Text.prototype */ { 28385 /** 28386 * Properties which when set cause object to change dimensions 28387 * @type Array 28388 * @private 28389 */ 28390 _dimensionAffectingProps: [ 28391 "fontSize", 28392 "fontWeight", 28393 "fontFamily", 28394 "fontStyle", 28395 "lineHeight", 28396 "text", 28397 "charSpacing", 28398 "textAlign", 28399 "styles", 28400 "path", 28401 "pathStartOffset", 28402 "pathSide", 28403 "pathAlign", 28404 ], 28405 28406 /** 28407 * @private 28408 */ 28409 _reNewline: /\r?\n/, 28410 28411 /** 28412 * Use this regular expression to filter for whitespaces that is not a new line. 28413 * Mostly used when text is 'justify' aligned. 28414 * @private 28415 */ 28416 _reSpacesAndTabs: /[ \t\r]/g, 28417 28418 /** 28419 * Use this regular expression to filter for whitespace that is not a new line. 28420 * Mostly used when text is 'justify' aligned. 28421 * @private 28422 */ 28423 _reSpaceAndTab: /[ \t\r]/, 28424 28425 /** 28426 * Use this regular expression to filter consecutive groups of non spaces. 28427 * Mostly used when text is 'justify' aligned. 28428 * @private 28429 */ 28430 _reWords: /\S+/g, 28431 28432 /** 28433 * Type of an object
28434 * @type String 28435 * @default 28436 */ 28437 type: "text", 28438 28439 /** 28440 * Font size (in pixels) 28441 * @type Number 28442 * @default 28443 */ 28444 fontSize: 40, 28445 28446 /** 28447 * Font weight (e.g. bold, normal, 400, 600, 800) 28448 * @type {(Number|String)} 28449 * @default 28450 */ 28451 fontWeight: "normal", 28452 28453 /** 28454 * Font family 28455 * @type String 28456 * @default 28457 */ 28458 fontFamily: "Times New Roman", 28459 28460 /** 28461 * Text decoration underline. 28462 * @type Boolean 28463 * @default 28464 */ 28465 underline: false, 28466 28467 /** 28468 * Text decoration overline. 28469 * @type Boolean 28470 * @default 28471 */ 28472 overline: false, 28473 28474 /** 28475 * Text decoration linethrough. 28476 * @type Boolean 28477 * @default 28478 */ 28479 linethrough: false, 28480 28481 /** 28482 * Text alignment. Possible values: "left", "center", "right", "justify", 28483 * "justify-left", "justify-center" or "justify-right". 28484 * @type String 28485 * @default 28486 */ 28487 textAlign: "left", 28488 28489 /** 28490 * Font style . Possible values: "", "normal", "italic" or "oblique".
28491 * @type String 28492 * @default 28493 */ 28494 fontStyle: "normal", 28495 28496 /** 28497 * Line height 28498 * @type Number 28499 * @default 28500 */ 28501 lineHeight: 1.16, 28502 28503 /** 28504 * Superscript schema object (minimum overlap) 28505 * @type {Object} 28506 * @default 28507 */ 28508 superscript: { 28509 size: 0.6, // fontSize factor 28510 baseline: -0.35, // baseline-shift factor (upwards) 28511 }, 28512 28513 /** 28514 * Subscript schema object (minimum overlap) 28515 * @type {Object} 28516 * @default 28517 */ 28518 subscript: { 28519 size: 0.6, // fontSize factor 28520 baseline: 0.11, // baseline-shift factor (downwards) 28521 }, 28522 28523 /** 28524 * Background color of text lines 28525 * @type String 28526 * @default 28527 */ 28528 textBackgroundColor: "", 28529 28530 /** 28531 * List of properties to consider when checking if 28532 * state of an object is changed ({@link fabric.Object#hasStateChanged}) 28533 * as well as for history (undo/redo) purposes 28534 * @type Array 28535 */ 28536 stateProperties: 28537 fabric.Object.prototype.stateProperties.concat(additionalProps), 28538 28539 /** 28540 * List of properties to consider when checking if cache needs refresh 28541 * @type Array 28542 */ 28543 cacheProperties: 28544 fabric.Object.prototype.cacheProperties.concat(additionalProps), 28545 28546 /** 28547 * When defined, an object is rendered via stroke and this property specifies its color. 28548 * <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6
28549 * @type String 28550 * @default 28551 */ 28552 stroke: null, 28553 28554 /** 28555 * Shadow object representing shadow of this shape. 28556 * <b>Backwards incompatibility note:</b> This property was named "textShadow" (String) until v1.2.11 28557 * @type fabric.Shadow 28558 * @default 28559 */ 28560 shadow: null, 28561 28562 /** 28563 * fabric.Path that the text should follow. 28564 * since 4.6.0 the path will be drawn automatically. 28565 * if you want to make the path visible, give it a stroke and strokeWidth or fill value 28566 * if you want it to be hidden, assign visible = false to the path. 28567 * This feature is in BETA, and SVG import/export is not yet supported. 28568 * @type fabric.Path 28569 * @example 28570 * var textPath = new fabric.Text('Text on a path', { 28571 * top: 150, 28572 * left: 150, 28573 * textAlign: 'center', 28574 * charSpacing: -50, 28575 * path: new fabric.Path('M 0 0 C 50 -100 150 -100 200 0', { 28576 * strokeWidth: 1, 28577 * visible: false 28578 * }), 28579 * pathSide: 'left', 28580 * pathStartOffset: 0 28581 * }); 28582 * @default 28583 */ 28584 path: null, 28585 28586 /** 28587 * Offset amount for text path starting position 28588 * Only used when text has a path 28589 * @type Number 28590 * @default 28591 */ 28592 pathStartOffset: 0, 28593 28594 /** 28595 * Which side of the path the text should be drawn on. 28596 * Only used when text has a path 28597 * @type {String} 'left|right' 28598 * @default 28599 */ 28600 pathSide: "left", 28601 28602 /** 28603 * How text is aligned to the path. This property determines 28604 * the perpendicular position of each character relative to the path. 28605 * (one of "baseline", "center", "ascender", "descender") 28606 * This feature is in BETA, and its behavior may change
28607 * @type String 28608 * @default 28609 */ 28610 pathAlign: "baseline", 28611 28612 /** 28613 * @private 28614 */ 28615 _fontSizeFraction: 0.222, 28616 28617 /** 28618 * @private 28619 */ 28620 offsets: { 28621 underline: 0.1, 28622 linethrough: -0.315, 28623 overline: -0.88, 28624 }, 28625 28626 /** 28627 * Text Line proportion to font Size (in pixels) 28628 * @type Number 28629 * @default 28630 */ 28631 _fontSizeMult: 1.13, 28632 28633 /** 28634 * additional space between characters 28635 * expressed in thousands of em unit 28636 * @type Number 28637 * @default 28638 */ 28639 charSpacing: 0, 28640 28641 /** 28642 * Object containing character styles - top-level properties -> line numbers, 28643 * 2nd-level properties - character numbers 28644 * @type Object 28645 * @default 28646 */ 28647 styles: null, 28648 28649 /** 28650 * Reference to a context to measure text char or couple of chars 28651 * the cacheContext of the canvas will be used or a freshly created one if the object is not on canvas 28652 * once created it will be referenced on fabric._measuringContext to avoid creating a canvas for every 28653 * text object created. 28654 * @type {CanvasRenderingContext2D} 28655 * @default 28656 */ 28657 _measuringContext: null, 28658 28659 /** 28660 * Baseline shift, styles only, keep at 0 for the main text object 28661 * @type {Number} 28662 * @default 28663 */ 28664 deltaY: 0, 28665 28666 /** 28667 * WARNING: EXPERIMENTAL. NOT SUPPORTED YET 28668 * determine the direction of the text. 28669 * This has to be set manually together with textAlign and originX for proper 28670 * experience. 28671 * some interesting link for the future 28672 * https://www.w3.org/International/questions/qa-bidi-unicode-controls 28673 * @since 4.5.0 28674 * @type {String} 'ltr|rtl' 28675 * @default 28676 */ 28677 direction: "ltr", 28678 28679 /** 28680 * Array of properties that define a style unit (of 'styles'). 28681 * @type {Array} 28682 * @default 28683 */ 28684 _styleProperties: [ 28685 "stroke", 28686 "strokeWidth", 28687 "fill", 28688 "fontFamily", 28689 "fontSize", 28690 "fontWeight", 28691 "fontStyle", 28692 "underline", 28693 "overline", 28694 "linethrough", 28695 "deltaY", 28696 "textBackgroundColor", 28697 ], 28698 28699 /** 28700 * contains characters bounding boxes 28701 */ 28702 __charBounds: [], 28703 28704 /** 28705 * use this size when measuring text. To avoid IE11 rounding errors 28706 * @type {Number} 28707 * @default 28708 * @readonly 28709 * @private 28710 */ 28711 CACHE_FONT_SIZE: 400, 28712 28713 /** 28714 * contains the min text width to avoid getting 0 28715 * @type {Number} 28716 * @default 28717 */ 28718 MIN_TEXT_WIDTH: 2, 28719 28720 /** 28721 * Constructor 28722 * @param {String} text Text string 28723 * @param {Object} [options] Options object 28724 * @return {fabric.Text} thisArg 28725 */ 28726 initialize: function (text, options) { 28727 this.styles = options ? options.styles || {} : {}; 28728 this.text = text; 28729 this.__skipDimension = true; 28730 this.callSuper("initialize", options); 28731 if (this.path) { 28732 this.setPathInfo(); 28733 } 28734 this.__skipDimension = false; 28735 this.initDimensions(); 28736 this.setCoords(); 28737 this.setupState({ propertySet: "_dimensionAffectingProps" }); 28738 }, 28739 28740 /** 28741 * If text has a path, it will add the extra information needed 28742 * for path and text calculations 28743 * @return {fabric.Text} thisArg 28744 */ 28745 setPathInfo: function () { 28746 var path = this.path; 28747 if (path) { 28748 path.segmentsInfo = fabric.util.getPathSegmentsInfo(path.path); 28749 } 28750 }, 28751 28752 /** 28753 * Return a context for measurement of text string. 28754 * if created it gets stored for reuse 28755 * this is for internal use, please do not use it 28756 * @private 28757 * @param {String} text Text string 28758 * @param {Object} [options] Options object 28759 * @return {fabric.Text} thisArg 28760 */ 28761 getMeasuringContext: function () { 28762 // if we did not return we have to measure something. 28763 if (!fabric._measuringContext) { 28764 fabric._measuringContext = 28765 (this.canvas && this.canvas.contextCache) || 28766 fabric.util.createCanvasElement().getContext("2d"); 28767 } 28768 return fabric._measuringContext; 28769 }, 28770 28771 /** 28772 * @private 28773 * Divides text into lines of text and lines of graphemes. 28774 */ 28775 _splitText: function () { 28776 var newLines = this._splitTextIntoLines(this.text); 28777 this.textLines = newLines.lines; 28778 this._textLines = newLines.graphemeLines; 28779 this._unwrappedTextLines = newLines._unwrappedLines; 28780 this._text = newLines.graphemeText; 28781 return newLines; 28782 }, 28783 28784 /** 28785 * Initialize or update text dimensions. 28786 * Updates this.width and this.height with the proper values. 28787 * Does not return dimensions. 28788 */ 28789 initDimensions: function () { 28790 if (this.__skipDimension) { 28791 return; 28792 } 28793 this._splitText(); 28794 this._clearCache(); 28795 if (this.path) { 28796 this.width = this.path.width; 28797 this.height = this.path.height; 28798 } else { 28799 this.width = 28800 this.calcTextWidth() || this.cursorWidth || this.MIN_TEXT_WIDTH; 28801 this.height = this.calcTextHeight(); 28802 } 28803 if (this.textAlign.indexOf("justify") !== -1) { 28804 // once text is measured we need to make space fatter to make justified text.
28805 this.enlargeSpaces(); 28806 } 28807 this.saveState({ propertySet: "_dimensionAffectingProps" }); 28808 }, 28809 28810 /** 28811 * Enlarge space boxes and shift the others 28812 */ 28813 enlargeSpaces: function () { 28814 var diffSpace, 28815 currentLineWidth, 28816 numberOfSpaces, 28817 accumulatedSpace, 28818 line, 28819 charBound, 28820 spaces; 28821 for (var i = 0, len = this._textLines.length; i < len; i++) { 28822 if ( 28823 this.textAlign !== "justify" && 28824 (i === len - 1 || this.isEndOfWrapping(i)) 28825 ) { 28826 continue; 28827 } 28828 accumulatedSpace = 0; 28829 line = this._textLines[i]; 28830 currentLineWidth = this.getLineWidth(i); 28831 if ( 28832 currentLineWidth < this.width && 28833 (spaces = this.textLines[i].match(this._reSpacesAndTabs)) 28834 ) { 28835 numberOfSpaces = spaces.length; 28836 diffSpace = (this.width - currentLineWidth) / numberOfSpaces; 28837 for (var j = 0, jlen = line.length; j <= jlen; j++) { 28838 charBound = this.__charBounds[i][j]; 28839 if (this._reSpaceAndTab.test(line[j])) { 28840 charBound.width += diffSpace; 28841 charBound.kernedWidth += diffSpace; 28842 charBound.left += accumulatedSpace; 28843 accumulatedSpace += diffSpace; 28844 } else { 28845 charBound.left += accumulatedSpace; 28846 } 28847 } 28848 } 28849 } 28850 }, 28851 28852 /** 28853 * Detect if the text line is ended with an hard break 28854 * text and itext do not have wrapping, return false 28855 * @return {Boolean} 28856 */ 28857 isEndOfWrapping: function (lineIndex) { 28858 return lineIndex === this._textLines.length - 1; 28859 }, 28860 28861 /** 28862 * Detect if a line has a linebreak and so we need to account for it when moving 28863 * and counting style. 28864 * It return always for text and Itext. 28865 * @return Number 28866 */ 28867 missingNewlineOffset: function () { 28868 return 1; 28869 }, 28870 28871 /** 28872 * Returns string representation of an instance 28873 * @return {String} String representation of text object 28874 */ 28875 toString: function () { 28876 return ( 28877 "#<fabric.Text (" + 28878 this.complexity() + 28879 '): { "text": "' + 28880 this.text + 28881 '", "fontFamily": "' + 28882 this.fontFamily + 28883 '" }>' 28884 ); 28885 }, 28886 28887 /** 28888 * Return the dimension and the zoom level needed to create a cache canvas 28889 * big enough to host the object to be cached. 28890 * @private 28891 * @param {Object} dim.x width of object to be cached 28892 * @param {Object} dim.y height of object to be cached 28893 * @return {Object}.width width of canvas 28894 * @return {Object}.height height of canvas 28895 * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache 28896 * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache 28897 */ 28898 _getCacheCanvasDimensions: function () { 28899 var dims = this.callSuper("_getCacheCanvasDimensions"); 28900 var fontSize = this.fontSize; 28901 dims.width += fontSize * dims.zoomX; 28902 dims.height += fontSize * dims.zoomY; 28903 return dims; 28904 }, 28905 28906 /** 28907 * @private 28908 * @param {CanvasRenderingContext2D} ctx Context to render on 28909 */ 28910 _render: function (ctx) { 28911 var path = this.path; 28912 path && !path.isNotVisible() && path._render(ctx); 28913 this._setTextStyles(ctx); 28914 this._renderTextLinesBackground(ctx); 28915 this._renderTextDecoration(ctx, "underline"); 28916 this._renderText(ctx); 28917 this._renderTextDecoration(ctx, "overline"); 28918 this._renderTextDecoration(ctx, "linethrough"); 28919 }, 28920 28921 /** 28922 * @private 28923 * @param {CanvasRenderingContext2D} ctx Context to render on 28924 */ 28925 _renderText: function (ctx) { 28926 if (this.paintFirst === "stroke") { 28927 this._renderTextStroke(ctx); 28928 this._renderTextFill(ctx); 28929 } else { 28930 this._renderTextFill(ctx); 28931 this._renderTextStroke(ctx); 28932 } 28933 }, 28934 28935 /** 28936 * Set the font parameter of the context with the object properties or with charStyle 28937 * @private 28938 * @param {CanvasRenderingContext2D} ctx Context to render on 28939 * @param {Object} [charStyle] object with font style properties 28940 * @param {String} [charStyle.fontFamily] Font Family 28941 * @param {Number} [charStyle.fontSize] Font size in pixels. ( without px suffix ) 28942 * @param {String} [charStyle.fontWeight] Font weight 28943 * @param {String} [charStyle.fontStyle] Font style (italic|normal) 28944 */ 28945 _setTextStyles: function (ctx, charStyle, forMeasuring) { 28946 ctx.textBaseline = "alphabetical"; 28947 if (this.path) { 28948 switch (this.pathAlign) { 28949 case "center": 28950 ctx.textBaseline = "middle"; 28951 break; 28952 case "ascender": 28953 ctx.textBaseline = "top"; 28954 break; 28955 case "descender": 28956 ctx.textBaseline = "bottom"; 28957 break; 28958 } 28959 }
28960 ctx.font = this._getFontDeclaration(charStyle, forMeasuring); 28961 }, 28962 28963 /** 28964 * calculate and return the text Width measuring each line. 28965 * @private 28966 * @param {CanvasRenderingContext2D} ctx Context to render on 28967 * @return {Number} Maximum width of fabric.Text object 28968 */ 28969 calcTextWidth: function () { 28970 var maxWidth = this.getLineWidth(0); 28971 28972 for (var i = 1, len = this._textLines.length; i < len; i++) { 28973 var currentLineWidth = this.getLineWidth(i); 28974 if (currentLineWidth > maxWidth) { 28975 maxWidth = currentLineWidth; 28976 } 28977 } 28978 return maxWidth; 28979 }, 28980 28981 /** 28982 * @private 28983 * @param {String} method Method name ("fillText" or "strokeText") 28984 * @param {CanvasRenderingContext2D} ctx Context to render on 28985 * @param {String} line Text to render 28986 * @param {Number} left Left position of text 28987 * @param {Number} top Top position of text 28988 * @param {Number} lineIndex Index of a line in a text 28989 */ 28990 _renderTextLine: function (method, ctx, line, left, top, lineIndex) { 28991 this._renderChars(method, ctx, line, left, top, lineIndex); 28992 }, 28993 28994 /** 28995 * Renders the text background for lines, taking care of style 28996 * @private 28997 * @param {CanvasRenderingContext2D} ctx Context to render on 28998 */ 28999 _renderTextLinesBackground: function (ctx) { 29000 if ( 29001 !this.textBackgroundColor && 29002 !this.styleHas("textBackgroundColor") 29003 ) { 29004 return; 29005 } 29006 var heightOfLine, 29007 lineLeftOffset, 29008 originalFill = ctx.fillStyle, 29009 line, 29010 lastColor, 29011 leftOffset = this._getLeftOffset(), 29012 lineTopOffset = this._getTopOffset(), 29013 boxStart = 0, 29014 boxWidth = 0, 29015 charBox, 29016 currentColor, 29017 path = this.path, 29018 drawStart; 29019 29020 for (var i = 0, len = this._textLines.length; i < len; i++) { 29021 heightOfLine = this.getHeightOfLine(i); 29022 if ( 29023 !this.textBackgroundColor && 29024 !this.styleHas("textBackgroundColor", i) 29025 ) { 29026 lineTopOffset += heightOfLine; 29027 continue; 29028 } 29029 line = this._textLines[i]; 29030 lineLeftOffset = this._getLineLeftOffset(i); 29031 boxWidth = 0; 29032 boxStart = 0; 29033 lastColor = this.getValueOfPropertyAt(i, 0, "textBackgroundColor"); 29034 for (var j = 0, jlen = line.length; j < jlen; j++) { 29035 charBox = this.__charBounds[i][j]; 29036 currentColor = this.getValueOfPropertyAt( 29037 i, 29038 j, 29039 "textBackgroundColor", 29040 ); 29041 if (path) { 29042 ctx.save(); 29043 ctx.translate(charBox.renderLeft, charBox.renderTop); 29044 ctx.rotate(charBox.angle); 29045 ctx.fillStyle = currentColor; 29046 currentColor && 29047 ctx.fillRect( 29048 -charBox.width / 2, 29049 (-heightOfLine / this.lineHeight) * 29050 (1 - this._fontSizeFraction), 29051 charBox.width, 29052 heightOfLine / this.lineHeight, 29053 ); 29054 ctx.restore(); 29055 } else if (currentColor !== lastColor) { 29056 drawStart = leftOffset + lineLeftOffset + boxStart; 29057 if (this.direction === "rtl") { 29058 drawStart = this.width - drawStart - boxWidth; 29059 }
29060 ctx.fillStyle = lastColor; 29061 lastColor && 29062 ctx.fillRect( 29063 drawStart, 29064 lineTopOffset, 29065 boxWidth, 29066 heightOfLine / this.lineHeight, 29067 ); 29068 boxStart = charBox.left; 29069 boxWidth = charBox.width; 29070 lastColor = currentColor; 29071 } else { 29072 boxWidth += charBox.kernedWidth; 29073 } 29074 } 29075 if (currentColor && !path) { 29076 drawStart = leftOffset + lineLeftOffset + boxStart; 29077 if (this.direction === "rtl") { 29078 drawStart = this.width - drawStart - boxWidth; 29079 } 29080 ctx.fillStyle = currentColor; 29081 ctx.fillRect( 29082 drawStart, 29083 lineTopOffset, 29084 boxWidth, 29085 heightOfLine / this.lineHeight, 29086 ); 29087 } 29088 lineTopOffset += heightOfLine; 29089 } 29090 ctx.fillStyle = originalFill; 29091 // if there is text background color no 29092 // other shadows should be casted 29093 this._removeShadow(ctx); 29094 }, 29095 29096 /** 29097 * @private 29098 * @param {Object} decl style declaration for cache 29099 * @param {String} decl.fontFamily fontFamily 29100 * @param {String} decl.fontStyle fontStyle 29101 * @param {String} decl.fontWeight fontWeight 29102 * @return {Object} reference to cache 29103 */ 29104 getFontCache: function (decl) { 29105 var fontFamily = decl.fontFamily.toLowerCase(); 29106 if (!fabric.charWidthsCache[fontFamily]) { 29107 fabric.charWidthsCache[fontFamily] = {}; 29108 } 29109 var cache = fabric.charWidthsCache[fontFamily], 29110 cacheProp = 29111 decl.fontStyle.toLowerCase() + 29112 "_" + 29113 (decl.fontWeight + "").toLowerCase(); 29114 if (!cache[cacheProp]) { 29115 cache[cacheProp] = {}; 29116 } 29117 return cache[cacheProp]; 29118 }, 29119 29120 /** 29121 * measure and return the width of a single character. 29122 * possibly overridden to accommodate different measure logic or 29123 * to hook some external lib for character measurement 29124 * @private 29125 * @param {String} _char, char to be measured 29126 * @param {Object} charStyle style of char to be measured 29127 * @param {String} [previousChar] previous char 29128 * @param {Object} [prevCharStyle] style of previous char 29129 */ 29130 _measureChar: function (_char, charStyle, previousChar, prevCharStyle) { 29131 // first i try to return from cache 29132 var fontCache = this.getFontCache(charStyle),
vendor: 4,436 bytes, lines 29133-29256
29133 fontDeclaration = this._getFontDeclaration(charStyle), 29134 previousFontDeclaration = this._getFontDeclaration(prevCharStyle), 29135 couple = previousChar + _char, 29136 stylesAreEqual = fontDeclaration === previousFontDeclaration, 29137 width, 29138 coupleWidth, 29139 previousWidth, 29140 fontMultiplier = charStyle.fontSize / this.CACHE_FONT_SIZE, 29141 kernedWidth; 29142 29143 if (previousChar && fontCache[previousChar] !== undefined) { 29144 previousWidth = fontCache[previousChar]; 29145 } 29146 if (fontCache[_char] !== undefined) { 29147 kernedWidth = width = fontCache[_char]; 29148 } 29149 if (stylesAreEqual && fontCache[couple] !== undefined) { 29150 coupleWidth = fontCache[couple]; 29151 kernedWidth = coupleWidth - previousWidth; 29152 } 29153 if ( 29154 width === undefined || 29155 previousWidth === undefined || 29156 coupleWidth === undefined 29157 ) { 29158 var ctx = this.getMeasuringContext(); 29159 // send a TRUE to specify measuring font size CACHE_FONT_SIZE 29160 this._setTextStyles(ctx, charStyle, true); 29161 } 29162 if (width === undefined) { 29163 kernedWidth = width = ctx.measureText(_char).width; 29164 fontCache[_char] = width; 29165 } 29166 if (previousWidth === undefined && stylesAreEqual && previousChar) { 29167 previousWidth = ctx.measureText(previousChar).width; 29168 fontCache[previousChar] = previousWidth; 29169 } 29170 if (stylesAreEqual && coupleWidth === undefined) { 29171 // we can measure the kerning couple and subtract the width of the previous character 29172 coupleWidth = ctx.measureText(couple).width; 29173 fontCache[couple] = coupleWidth; 29174 kernedWidth = coupleWidth - previousWidth; 29175 } 29176 return { 29177 width: width * fontMultiplier, 29178 kernedWidth: kernedWidth * fontMultiplier, 29179 }; 29180 }, 29181 29182 /** 29183 * Computes height of character at given position 29184 * @param {Number} line the line index number 29185 * @param {Number} _char the character index number 29186 * @return {Number} fontSize of the character 29187 */ 29188 getHeightOfChar: function (line, _char) { 29189 return this.getValueOfPropertyAt(line, _char, "fontSize"); 29190 }, 29191 29192 /** 29193 * measure a text line measuring all characters. 29194 * @param {Number} lineIndex line number 29195 * @return {Number} Line width 29196 */ 29197 measureLine: function (lineIndex) { 29198 var lineInfo = this._measureLine(lineIndex); 29199 if (this.charSpacing !== 0) { 29200 lineInfo.width -= this._getWidthOfCharSpacing(); 29201 } 29202 if (lineInfo.width < 0) { 29203 lineInfo.width = 0; 29204 } 29205 return lineInfo; 29206 }, 29207 29208 /** 29209 * measure every grapheme of a line, populating __charBounds 29210 * @param {Number} lineIndex 29211 * @return {Object} object.width total width of characters 29212 * @return {Object} object.widthOfSpaces length of chars that match this._reSpacesAndTabs 29213 */ 29214 _measureLine: function (lineIndex) { 29215 var width = 0, 29216 i, 29217 grapheme, 29218 line = this._textLines[lineIndex], 29219 prevGrapheme, 29220 graphemeInfo, 29221 numOfSpaces = 0, 29222 lineBounds = new Array(line.length), 29223 positionInPath = 0, 29224 startingPoint, 29225 totalPathLength, 29226 path = this.path, 29227 reverse = this.pathSide === "right"; 29228 29229 this.__charBounds[lineIndex] = lineBounds; 29230 for (i = 0; i < line.length; i++) { 29231 grapheme = line[i]; 29232 graphemeInfo = this._getGraphemeBox( 29233 grapheme, 29234 lineIndex, 29235 i, 29236 prevGrapheme, 29237 ); 29238 lineBounds[i] = graphemeInfo; 29239 width += graphemeInfo.kernedWidth; 29240 prevGrapheme = grapheme; 29241 } 29242 // this latest bound box represent the last character of the line 29243 // to simplify cursor handling in interactive mode. 29244 lineBounds[i] = { 29245 left: graphemeInfo ? graphemeInfo.left + graphemeInfo.width : 0, 29246 width: 0, 29247 kernedWidth: 0, 29248 height: this.fontSize, 29249 }; 29250 if (path) { 29251 totalPathLength = 29252 path.segmentsInfo[path.segmentsInfo.length - 1].length; 29253 startingPoint = fabric.util.getPointOnPath( 29254 path.path, 29255 0, 29256 path.segmentsInfo,
29257 ); 29258 startingPoint.x += path.pathOffset.x; 29259 startingPoint.y += path.pathOffset.y; 29260 switch (this.textAlign) { 29261 case "left": 29262 positionInPath = reverse ? totalPathLength - width : 0; 29263 break; 29264 case "center": 29265 positionInPath = (totalPathLength - width) / 2; 29266 break; 29267 case "right": 29268 positionInPath = reverse ? 0 : totalPathLength - width; 29269 break; 29270 //todo - add support for justify 29271 } 29272 positionInPath += this.pathStartOffset * (reverse ? -1 : 1); 29273 for ( 29274 i = reverse ? line.length - 1 : 0; 29275 reverse ? i >= 0 : i < line.length; 29276 reverse ? i-- : i++ 29277 ) { 29278 graphemeInfo = lineBounds[i]; 29279 if (positionInPath > totalPathLength) { 29280 positionInPath %= totalPathLength; 29281 } else if (positionInPath < 0) { 29282 positionInPath += totalPathLength; 29283 } 29284 // it would probably much faster to send all the grapheme position for a line 29285 // and calculate path position/angle at once. 29286 this._setGraphemeOnPath( 29287 positionInPath, 29288 graphemeInfo, 29289 startingPoint, 29290 ); 29291 positionInPath += graphemeInfo.kernedWidth; 29292 } 29293 } 29294 return { width: width, numOfSpaces: numOfSpaces }; 29295 }, 29296 29297 /** 29298 * Calculate the angle and the left,top position of the char that follow a path. 29299 * It appends it to graphemeInfo to be reused later at rendering 29300 * @private 29301 * @param {Number} positionInPath to be measured 29302 * @param {Object} graphemeInfo current grapheme box information 29303 * @param {Object} startingPoint position of the point 29304 */ 29305 _setGraphemeOnPath: function ( 29306 positionInPath, 29307 graphemeInfo, 29308 startingPoint, 29309 ) { 29310 var centerPosition = positionInPath + graphemeInfo.kernedWidth / 2, 29311 path = this.path; 29312 29313 // we are at currentPositionOnPath. we want to know what point on the path is. 29314 var info = fabric.util.getPointOnPath( 29315 path.path, 29316 centerPosition, 29317 path.segmentsInfo, 29318 ); 29319 graphemeInfo.renderLeft = info.x - startingPoint.x; 29320 graphemeInfo.renderTop = info.y - startingPoint.y; 29321 graphemeInfo.angle = 29322 info.angle + (this.pathSide === "right" ? Math.PI : 0); 29323 }, 29324 29325 /** 29326 * Measure and return the info of a single grapheme. 29327 * needs the the info of previous graphemes already filled 29328 * @private 29329 * @param {String} grapheme to be measured 29330 * @param {Number} lineIndex index of the line where the char is 29331 * @param {Number} charIndex position in the line 29332 * @param {String} [prevGrapheme] character preceding the one to be measured 29333 */ 29334 _getGraphemeBox: function ( 29335 grapheme, 29336 lineIndex, 29337 charIndex, 29338 prevGrapheme, 29339 skipLeft, 29340 ) { 29341 var style = this.getCompleteStyleDeclaration(lineIndex, charIndex), 29342 prevStyle = prevGrapheme 29343 ? this.getCompleteStyleDeclaration(lineIndex, charIndex - 1) 29344 : {}, 29345 info = this._measureChar(grapheme, style, prevGrapheme, prevStyle), 29346 kernedWidth = info.kernedWidth, 29347 width = info.width, 29348 charSpacing; 29349 29350 if (this.charSpacing !== 0) { 29351 charSpacing = this._getWidthOfCharSpacing(); 29352 width += charSpacing; 29353 kernedWidth += charSpacing; 29354 } 29355 29356 var box = { 29357 width: width, 29358 left: 0, 29359 height: style.fontSize, 29360 kernedWidth: kernedWidth, 29361 deltaY: style.deltaY, 29362 }; 29363 if (charIndex > 0 && !skipLeft) { 29364 var previousBox = this.__charBounds[lineIndex][charIndex - 1]; 29365 box.left = 29366 previousBox.left + 29367 previousBox.width + 29368 info.kernedWidth - 29369 info.width; 29370 } 29371 return box; 29372 }, 29373 29374 /** 29375 * Calculate height of line at 'lineIndex' 29376 * @param {Number} lineIndex index of line to calculate 29377 * @return {Number} 29378 */ 29379 getHeightOfLine: function (lineIndex) { 29380 if (this.__lineHeights[lineIndex]) { 29381 return this.__lineHeights[lineIndex]; 29382 } 29383 29384 var line = this._textLines[lineIndex], 29385 // char 0 is measured before the line cycle because it nneds to char 29386 // emptylines 29387 maxHeight = this.getHeightOfChar(lineIndex, 0); 29388 for (var i = 1, len = line.length; i < len; i++) { 29389 maxHeight = Math.max(this.getHeightOfChar(lineIndex, i), maxHeight); 29390 } 29391 29392 return (this.__lineHeights[lineIndex] = 29393 maxHeight * this.lineHeight * this._fontSizeMult); 29394 }, 29395 29396 /**
29397 * Calculate text box height 29398 */ 29399 calcTextHeight: function () { 29400 var lineHeight, 29401 height = 0; 29402 for (var i = 0, len = this._textLines.length; i < len; i++) { 29403 lineHeight = this.getHeightOfLine(i); 29404 height += i === len - 1 ? lineHeight / this.lineHeight : lineHeight; 29405 } 29406 return height; 29407 }, 29408 29409 /** 29410 * @private 29411 * @return {Number} Left offset 29412 */ 29413 _getLeftOffset: function () { 29414 return this.direction === "ltr" ? -this.width / 2 : this.width / 2; 29415 }, 29416 29417 /** 29418 * @private 29419 * @return {Number} Top offset 29420 */ 29421 _getTopOffset: function () { 29422 return -this.height / 2; 29423 }, 29424 29425 /** 29426 * @private 29427 * @param {CanvasRenderingContext2D} ctx Context to render on 29428 * @param {String} method Method name ("fillText" or "strokeText") 29429 */ 29430 _renderTextCommon: function (ctx, method) { 29431 ctx.save(); 29432 var lineHeights = 0, 29433 left = this._getLeftOffset(), 29434 top = this._getTopOffset(); 29435 for (var i = 0, len = this._textLines.length; i < len; i++) { 29436 var heightOfLine = this.getHeightOfLine(i), 29437 maxHeight = heightOfLine / this.lineHeight, 29438 leftOffset = this._getLineLeftOffset(i); 29439 this._renderTextLine( 29440 method, 29441 ctx, 29442 this._textLines[i], 29443 left + leftOffset, 29444 top + lineHeights + maxHeight, 29445 i, 29446 ); 29447 lineHeights += heightOfLine; 29448 } 29449 ctx.restore(); 29450 }, 29451 29452 /** 29453 * @private 29454 * @param {CanvasRenderingContext2D} ctx Context to render on 29455 */ 29456 _renderTextFill: function (ctx) { 29457 if (!this.fill && !this.styleHas("fill")) { 29458 return; 29459 } 29460 29461 this._renderTextCommon(ctx, "fillText"); 29462 }, 29463 29464 /** 29465 * @private 29466 * @param {CanvasRenderingContext2D} ctx Context to render on 29467 */ 29468 _renderTextStroke: function (ctx) { 29469 if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) { 29470 return; 29471 } 29472 29473 if (this.shadow && !this.shadow.affectStroke) { 29474 this._removeShadow(ctx); 29475 } 29476 29477 ctx.save(); 29478 this._setLineDash(ctx, this.strokeDashArray); 29479 ctx.beginPath(); 29480 this._renderTextCommon(ctx, "strokeText"); 29481 ctx.closePath(); 29482 ctx.restore(); 29483 }, 29484 29485 /** 29486 * @private 29487 * @param {String} method fillText or strokeText. 29488 * @param {CanvasRenderingContext2D} ctx Context to render on 29489 * @param {Array} line Content of the line, splitted in an array by grapheme 29490 * @param {Number} left 29491 * @param {Number} top 29492 * @param {Number} lineIndex 29493 */ 29494 _renderChars: function (method, ctx, line, left, top, lineIndex) { 29495 // set proper line offset 29496 var lineHeight = this.getHeightOfLine(lineIndex), 29497 isJustify = this.textAlign.indexOf("justify") !== -1, 29498 actualStyle, 29499 nextStyle, 29500 charsToRender = "", 29501 charBox, 29502 boxWidth = 0, 29503 timeToRender, 29504 path = this.path, 29505 shortCut = 29506 !isJustify && 29507 this.charSpacing === 0 && 29508 this.isEmptyStyles(lineIndex) && 29509 !path, 29510 isLtr = this.direction === "ltr", 29511 sign = this.direction === "ltr" ? 1 : -1, 29512 drawingLeft, 29513 currentDirection = ctx.canvas.getAttribute("dir"); 29514 ctx.save(); 29515 if (currentDirection !== this.direction) { 29516 ctx.canvas.setAttribute("dir", isLtr ? "ltr" : "rtl"); 29517 ctx.direction = isLtr ? "ltr" : "rtl"; 29518 ctx.textAlign = isLtr ? "left" : "right"; 29519 } 29520 top -= (lineHeight * this._fontSizeFraction) / this.lineHeight; 29521 if (shortCut) { 29522 // render all the line in one pass without checking 29523 // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex); 29524 this._renderChar( 29525 method, 29526 ctx, 29527 lineIndex, 29528 0, 29529 line.join(""), 29530 left, 29531 top, 29532 lineHeight, 29533 );
29534 ctx.restore(); 29535 return; 29536 } 29537 for (var i = 0, len = line.length - 1; i <= len; i++) { 29538 timeToRender = i === len || this.charSpacing || path; 29539 charsToRender += line[i]; 29540 charBox = this.__charBounds[lineIndex][i]; 29541 if (boxWidth === 0) { 29542 left += sign * (charBox.kernedWidth - charBox.width); 29543 boxWidth += charBox.width; 29544 } else { 29545 boxWidth += charBox.kernedWidth; 29546 } 29547 if (isJustify && !timeToRender) { 29548 if (this._reSpaceAndTab.test(line[i])) { 29549 timeToRender = true; 29550 } 29551 } 29552 if (!timeToRender) { 29553 // if we have charSpacing, we render char by char 29554 actualStyle = 29555 actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); 29556 nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); 29557 timeToRender = fabric.util.hasStyleChanged( 29558 actualStyle, 29559 nextStyle, 29560 false, 29561 ); 29562 } 29563 if (timeToRender) { 29564 if (path) { 29565 ctx.save(); 29566 ctx.translate(charBox.renderLeft, charBox.renderTop); 29567 ctx.rotate(charBox.angle); 29568 this._renderChar( 29569 method, 29570 ctx, 29571 lineIndex, 29572 i, 29573 charsToRender, 29574 -boxWidth / 2, 29575 0, 29576 lineHeight, 29577 ); 29578 ctx.restore(); 29579 } else { 29580 drawingLeft = left; 29581 this._renderChar( 29582 method, 29583 ctx, 29584 lineIndex, 29585 i, 29586 charsToRender, 29587 drawingLeft, 29588 top, 29589 lineHeight, 29590 ); 29591 } 29592 charsToRender = ""; 29593 actualStyle = nextStyle; 29594 left += sign * boxWidth; 29595 boxWidth = 0; 29596 } 29597 } 29598 ctx.restore(); 29599 }, 29600 29601 /** 29602 * This function try to patch the missing gradientTransform on canvas gradients. 29603 * transforming a context to transform the gradient, is going to transform the stroke too. 29604 * we want to transform the gradient but not the stroke operation, so we create 29605 * a transformed gradient on a pattern and then we use the pattern instead of the gradient. 29606 * this method has drawbacks: is slow, is in low resolution, needs a patch for when the size 29607 * is limited. 29608 * @private 29609 * @param {fabric.Gradient} filler a fabric gradient instance 29610 * @return {CanvasPattern} a pattern to use as fill/stroke style 29611 */ 29612 _applyPatternGradientTransformText: function (filler) { 29613 var pCanvas = fabric.util.createCanvasElement(), 29614 pCtx, 29615 // TODO: verify compatibility with strokeUniform 29616 width = this.width + this.strokeWidth, 29617 height = this.height + this.strokeWidth; 29618 pCanvas.width = width; 29619 pCanvas.height = height; 29620 pCtx = pCanvas.getContext("2d"); 29621 pCtx.beginPath(); 29622 pCtx.moveTo(0, 0); 29623 pCtx.lineTo(width, 0); 29624 pCtx.lineTo(width, height); 29625 pCtx.lineTo(0, height); 29626 pCtx.closePath(); 29627 pCtx.translate(width / 2, height / 2); 29628 pCtx.fillStyle = filler.toLive(pCtx); 29629 this._applyPatternGradientTransform(pCtx, filler); 29630 pCtx.fill(); 29631 return pCtx.createPattern(pCanvas, "no-repeat"); 29632 }, 29633 29634 handleFiller: function (ctx, property, filler) { 29635 var offsetX, offsetY; 29636 if (filler.toLive) { 29637 if ( 29638 filler.gradientUnits === "percentage" || 29639 filler.gradientTransform || 29640 filler.patternTransform 29641 ) { 29642 // need to transform gradient in a pattern. 29643 // this is a slow process. If you are hitting this codepath, and the object 29644 // is not using caching, you should consider switching it on. 29645 // we need a canvas as big as the current object caching canvas. 29646 offsetX = -this.width / 2; 29647 offsetY = -this.height / 2; 29648 ctx.translate(offsetX, offsetY); 29649 ctx[property] = this._applyPatternGradientTransformText(filler); 29650 return { offsetX: offsetX, offsetY: offsetY }; 29651 } else { 29652 // is a simple gradient or pattern 29653 ctx[property] = filler.toLive(ctx, this); 29654 return this._applyPatternGradientTransform(ctx, filler); 29655 } 29656 } else { 29657 // is a color 29658 ctx[property] = filler; 29659 } 29660 return { offsetX: 0, offsetY: 0 }; 29661 }, 29662 29663 _setStrokeStyles: function (ctx, decl) { 29664 ctx.lineWidth = decl.strokeWidth; 29665 ctx.lineCap = this.strokeLineCap; 29666 ctx.lineDashOffset = this.strokeDashOffset; 29667 ctx.lineJoin = this.strokeLineJoin; 29668 ctx.miterLimit = this.strokeMiterLimit; 29669 return this.handleFiller(ctx, "strokeStyle", decl.stroke); 29670 }, 29671 29672 _setFillStyles: function (ctx, decl) { 29673 return this.handleFiller(ctx, "fillStyle", decl.fill); 29674 }, 29675 29676 /** 29677 * @private 29678 * @param {String} method 29679 * @param {CanvasRenderingContext2D} ctx Context to render on 29680 * @param {Number} lineIndex 29681 * @param {Number} charIndex 29682 * @param {String} _char 29683 * @param {Number} left Left coordinate 29684 * @param {Number} top Top coordinate 29685 * @param {Number} lineHeight Height of the line 29686 */ 29687 _renderChar: function ( 29688 method, 29689 ctx, 29690 lineIndex, 29691 charIndex, 29692 _char, 29693 left, 29694 top, 29695 ) { 29696 var decl = this._getStyleDeclaration(lineIndex, charIndex), 29697 fullDecl = this.getCompleteStyleDeclaration(lineIndex, charIndex), 29698 shouldFill = method === "fillText" && fullDecl.fill, 29699 shouldStroke = 29700 method === "strokeText" && fullDecl.stroke && fullDecl.strokeWidth, 29701 fillOffsets, 29702 strokeOffsets; 29703 29704 if (!shouldStroke && !shouldFill) { 29705 return; 29706 } 29707 ctx.save(); 29708 29709 shouldFill && (fillOffsets = this._setFillStyles(ctx, fullDecl)); 29710 shouldStroke && (strokeOffsets = this._setStrokeStyles(ctx, fullDecl)); 29711 29712 ctx.font = this._getFontDeclaration(fullDecl); 29713
29714 if (decl && decl.textBackgroundColor) { 29715 this._removeShadow(ctx); 29716 } 29717 if (decl && decl.deltaY) { 29718 top += decl.deltaY; 29719 } 29720 shouldFill && 29721 ctx.fillText( 29722 _char, 29723 left - fillOffsets.offsetX, 29724 top - fillOffsets.offsetY, 29725 ); 29726 shouldStroke && 29727 ctx.strokeText( 29728 _char, 29729 left - strokeOffsets.offsetX, 29730 top - strokeOffsets.offsetY, 29731 ); 29732 ctx.restore(); 29733 }, 29734 29735 /** 29736 * Turns the character into a 'superior figure' (i.e. 'superscript') 29737 * @param {Number} start selection start 29738 * @param {Number} end selection end 29739 * @returns {fabric.Text} thisArg 29740 * @chainable 29741 */ 29742 setSuperscript: function (start, end) { 29743 return this._setScript(start, end, this.superscript); 29744 }, 29745 29746 /** 29747 * Turns the character into an 'inferior figure' (i.e. 'subscript') 29748 * @param {Number} start selection start 29749 * @param {Number} end selection end 29750 * @returns {fabric.Text} thisArg 29751 * @chainable 29752 */ 29753 setSubscript: function (start, end) { 29754 return this._setScript(start, end, this.subscript); 29755 }, 29756 29757 /** 29758 * Applies 'schema' at given position 29759 * @private 29760 * @param {Number} start selection start 29761 * @param {Number} end selection end 29762 * @param {Number} schema 29763 * @returns {fabric.Text} thisArg 29764 * @chainable 29765 */ 29766 _setScript: function (start, end, schema) { 29767 var loc = this.get2DCursorLocation(start, true), 29768 fontSize = this.getValueOfPropertyAt( 29769 loc.lineIndex, 29770 loc.charIndex, 29771 "fontSize", 29772 ), 29773 dy = this.getValueOfPropertyAt( 29774 loc.lineIndex, 29775 loc.charIndex, 29776 "deltaY", 29777 ), 29778 style = { 29779 fontSize: fontSize * schema.size, 29780 deltaY: dy + fontSize * schema.baseline, 29781 }; 29782 this.setSelectionStyles(style, start, end); 29783 return this; 29784 }, 29785 29786 /** 29787 * @private 29788 * @param {Number} lineIndex index text line 29789 * @return {Number} Line left offset 29790 */ 29791 _getLineLeftOffset: function (lineIndex) { 29792 var lineWidth = this.getLineWidth(lineIndex), 29793 lineDiff = this.width - lineWidth, 29794 textAlign = this.textAlign, 29795 direction = this.direction, 29796 isEndOfWrapping, 29797 leftOffset = 0, 29798 isEndOfWrapping = this.isEndOfWrapping(lineIndex); 29799 if ( 29800 textAlign === "justify" || 29801 (textAlign === "justify-center" && !isEndOfWrapping) || 29802 (textAlign === "justify-right" && !isEndOfWrapping) || 29803 (textAlign === "justify-left" && !isEndOfWrapping) 29804 ) { 29805 return 0; 29806 } 29807 if (textAlign === "center") { 29808 leftOffset = lineDiff / 2; 29809 } 29810 if (textAlign === "right") { 29811 leftOffset = lineDiff; 29812 } 29813 if (textAlign === "justify-center") { 29814 leftOffset = lineDiff / 2; 29815 } 29816 if (textAlign === "justify-right") { 29817 leftOffset = lineDiff; 29818 } 29819 if (direction === "rtl") { 29820 leftOffset -= lineDiff; 29821 } 29822 return leftOffset; 29823 }, 29824 29825 /** 29826 * @private 29827 */ 29828 _clearCache: function () { 29829 this.__lineWidths = []; 29830 this.__lineHeights = []; 29831 this.__charBounds = []; 29832 }, 29833 29834 /** 29835 * @private 29836 */ 29837 _shouldClearDimensionCache: function () { 29838 var shouldClear = this._forceClearCache; 29839 shouldClear || 29840 (shouldClear = this.hasStateChanged("_dimensionAffectingProps")); 29841 if (shouldClear) { 29842 this.dirty = true; 29843 this._forceClearCache = false; 29844 } 29845 return shouldClear; 29846 }, 29847 29848 /** 29849 * Measure a single line given its index. Used to calculate the initial 29850 * text bounding box. The values are calculated and stored in __lineWidths cache. 29851 * @private 29852 * @param {Number} lineIndex line number 29853 * @return {Number} Line width 29854 */ 29855 getLineWidth: function (lineIndex) { 29856 if (this.__lineWidths[lineIndex] !== undefined) { 29857 return this.__lineWidths[lineIndex]; 29858 } 29859 29860 var lineInfo = this.measureLine(lineIndex); 29861 var width = lineInfo.width; 29862 this.__lineWidths[lineIndex] = width; 29863 return width; 29864 }, 29865 29866 _getWidthOfCharSpacing: function () { 29867 if (this.charSpacing !== 0) { 29868 return (this.fontSize * this.charSpacing) / 1000; 29869 } 29870 return 0; 29871 }, 29872 29873 /** 29874 * Retrieves the value of property at given character position 29875 * @param {Number} lineIndex the line number 29876 * @param {Number} charIndex the character number 29877 * @param {String} property the property name 29878 * @returns the value of 'property' 29879 */ 29880 getValueOfPropertyAt: function (lineIndex, charIndex, property) { 29881 var charStyle = this._getStyleDeclaration(lineIndex, charIndex); 29882 if (charStyle && typeof charStyle[property] !== "undefined") { 29883 return charStyle[property]; 29884 } 29885 return this[property]; 29886 }, 29887 29888 /** 29889 * @private 29890 * @param {CanvasRenderingContext2D} ctx Context to render on 29891 */ 29892 _renderTextDecoration: function (ctx, type) { 29893 if (!this[type] && !this.styleHas(type)) { 29894 return; 29895 } 29896 var heightOfLine, 29897 size, 29898 _size, 29899 lineLeftOffset, 29900 dy, 29901 _dy, 29902 line, 29903 lastDecoration, 29904 leftOffset = this._getLeftOffset(), 29905 topOffset = this._getTopOffset(), 29906 top, 29907 boxStart, 29908 boxWidth, 29909 charBox, 29910 currentDecoration, 29911 maxHeight, 29912 currentFill, 29913 lastFill, 29914 path = this.path, 29915 charSpacing = this._getWidthOfCharSpacing(), 29916 offsetY = this.offsets[type]; 29917 29918 for (var i = 0, len = this._textLines.length; i < len; i++) { 29919 heightOfLine = this.getHeightOfLine(i); 29920 if (!this[type] && !this.styleHas(type, i)) { 29921 topOffset += heightOfLine; 29922 continue; 29923 } 29924 line = this._textLines[i]; 29925 maxHeight = heightOfLine / this.lineHeight; 29926 lineLeftOffset = this._getLineLeftOffset(i); 29927 boxStart = 0; 29928 boxWidth = 0; 29929 lastDecoration = this.getValueOfPropertyAt(i, 0, type); 29930 lastFill = this.getValueOfPropertyAt(i, 0, "fill"); 29931 top = topOffset + maxHeight * (1 - this._fontSizeFraction); 29932 size = this.getHeightOfChar(i, 0); 29933 dy = this.getValueOfPropertyAt(i, 0, "deltaY"); 29934 for (var j = 0, jlen = line.length; j < jlen; j++) { 29935 charBox = this.__charBounds[i][j]; 29936 currentDecoration = this.getValueOfPropertyAt(i, j, type); 29937 currentFill = this.getValueOfPropertyAt(i, j, "fill"); 29938 _size = this.getHeightOfChar(i, j); 29939 _dy = this.getValueOfPropertyAt(i, j, "deltaY"); 29940 if (path && currentDecoration && currentFill) { 29941 ctx.save(); 29942 ctx.fillStyle = lastFill; 29943 ctx.translate(charBox.renderLeft, charBox.renderTop); 29944 ctx.rotate(charBox.angle); 29945 ctx.fillRect( 29946 -charBox.kernedWidth / 2, 29947 offsetY * _size + _dy, 29948 charBox.kernedWidth, 29949 this.fontSize / 15, 29950 );
29951 ctx.restore(); 29952 } else if ( 29953 (currentDecoration !== lastDecoration || 29954 currentFill !== lastFill || 29955 _size !== size || 29956 _dy !== dy) && 29957 boxWidth > 0 29958 ) { 29959 var drawStart = leftOffset + lineLeftOffset + boxStart; 29960 if (this.direction === "rtl") { 29961 drawStart = this.width - drawStart - boxWidth; 29962 } 29963 if (lastDecoration && lastFill) { 29964 ctx.fillStyle = lastFill; 29965 ctx.fillRect( 29966 drawStart, 29967 top + offsetY * size + dy, 29968 boxWidth, 29969 this.fontSize / 15, 29970 ); 29971 } 29972 boxStart = charBox.left; 29973 boxWidth = charBox.width; 29974 lastDecoration = currentDecoration; 29975 lastFill = currentFill; 29976 size = _size; 29977 dy = _dy; 29978 } else { 29979 boxWidth += charBox.kernedWidth; 29980 } 29981 } 29982 var drawStart = leftOffset + lineLeftOffset + boxStart; 29983 if (this.direction === "rtl") { 29984 drawStart = this.width - drawStart - boxWidth; 29985 } 29986 ctx.fillStyle = currentFill; 29987 currentDecoration && 29988 currentFill && 29989 ctx.fillRect( 29990 drawStart, 29991 top + offsetY * size + dy, 29992 boxWidth - charSpacing, 29993 this.fontSize / 15, 29994 ); 29995 topOffset += heightOfLine; 29996 } 29997 // if there is text background color no 29998 // other shadows should be casted 29999 this._removeShadow(ctx); 30000 }, 30001 30002 /** 30003 * return font declaration string for canvas context 30004 * @param {Object} [styleObject] object 30005 * @returns {String} font declaration formatted for canvas context. 30006 */ 30007 _getFontDeclaration: function (styleObject, forMeasuring) { 30008 var style = styleObject || this, 30009 family = this.fontFamily, 30010 fontIsGeneric = 30011 fabric.Text.genericFonts.indexOf(family.toLowerCase()) > -1; 30012 var fontFamily = 30013 family === undefined || 30014 family.indexOf("'") > -1 || 30015 family.indexOf(",") > -1 || 30016 family.indexOf('"') > -1 || 30017 fontIsGeneric 30018 ? style.fontFamily 30019 : '"' + style.fontFamily + '"'; 30020 return [ 30021 // node-canvas needs "weight style", while browsers need "style weight" 30022 // verify if this can be fixed in JSDOM 30023 fabric.isLikelyNode ? style.fontWeight : style.fontStyle, 30024 fabric.isLikelyNode ? style.fontStyle : style.fontWeight, 30025 forMeasuring ? this.CACHE_FONT_SIZE + "px" : style.fontSize + "px", 30026 fontFamily, 30027 ].join(" "); 30028 }, 30029 30030 /** 30031 * Renders text instance on a specified context 30032 * @param {CanvasRenderingContext2D} ctx Context to render on 30033 */ 30034 render: function (ctx) { 30035 // do not render if object is not visible 30036 if (!this.visible) { 30037 return; 30038 } 30039 if ( 30040 this.canvas && 30041 this.canvas.skipOffscreen && 30042 !this.group && 30043 !this.isOnScreen() 30044 ) { 30045 return; 30046 } 30047 if (this._shouldClearDimensionCache()) { 30048 this.initDimensions(); 30049 } 30050 this.callSuper("render", ctx); 30051 }, 30052 30053 /** 30054 * Returns the text as an array of lines. 30055 * @param {String} text text to split 30056 * @returns {Array} Lines in the text 30057 */ 30058 _splitTextIntoLines: function (text) { 30059 var lines = text.split(this._reNewline), 30060 newLines = new Array(lines.length), 30061 newLine = ["\n"], 30062 newText = []; 30063 for (var i = 0; i < lines.length; i++) { 30064 newLines[i] = fabric.util.string.graphemeSplit(lines[i]); 30065 newText = newText.concat(newLines[i], newLine); 30066 } 30067 newText.pop(); 30068 return { 30069 _unwrappedLines: newLines, 30070 lines: lines, 30071 graphemeText: newText, 30072 graphemeLines: newLines, 30073 }; 30074 }, 30075 30076 /** 30077 * Returns object representation of an instance 30078 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 30079 * @return {Object} Object representation of an instance 30080 */ 30081 toObject: function (propertiesToInclude) { 30082 var allProperties = additionalProps.concat(propertiesToInclude); 30083 var obj = this.callSuper("toObject", allProperties); 30084 obj.styles = fabric.util.stylesToArray( 30085 this.styles ? this.styles : {}, 30086 this.text, 30087 ); 30088 if (obj.path) { 30089 obj.path = this.path.toObject(); 30090 } 30091 return obj; 30092 }, 30093 30094 /** 30095 * 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/control
30095s. If you need to update those, call `setCoords()`. 30096 * @param {String|Object} key Property name or object (if object, iterate over the object properties) 30097 * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) 30098 * @return {fabric.Object} thisArg 30099 * @chainable 30100 */ 30101 set: function (key, value) { 30102 this.callSuper("set", key, value); 30103 var needsDims = false; 30104 var isAddingPath = false; 30105 if (typeof key === "object") { 30106 for (var _key in key) { 30107 if (_key === "path") { 30108 this.setPathInfo(); 30109 } 30110 needsDims = 30111 needsDims || this._dimensionAffectingProps.indexOf(_key) !== -1; 30112 isAddingPath = isAddingPath || _key === "path"; 30113 } 30114 } else { 30115 needsDims = this._dimensionAffectingProps.indexOf(key) !== -1; 30116 isAddingPath = key === "path"; 30117 } 30118 if (isAddingPath) { 30119 this.setPathInfo(); 30120 } 30121 if (needsDims) { 30122 this.initDimensions(); 30123 this.setCoords(); 30124 } 30125 return this; 30126 }, 30127 30128 /** 30129 * Returns complexity of an instance 30130 * @return {Number} complexity 30131 */ 30132 complexity: function () { 30133 return 1; 30134 }, 30135 }, 30136 ); 30137 30138 /* _FROM_SVG_START_ */ 30139 /** 30140 * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) 30141 * @static 30142 * @memberOf fabric.Text 30143 * @see: http://www.w3.org/TR/SVG/text.html#TextElement 30144 */ 30145 fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( 30146 "x y dx dy font-family font-style font-weight font-size letter-spacing text-decoration text-anchor".split( 30147 " ", 30148 ), 30149 ); 30150 30151 /** 30152 * Default SVG font size 30153 * @static 30154 * @memberOf fabric.Text 30155 */ 30156 fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; 30157 30158 /** 30159 * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>) 30160 * @static 30161 * @memberOf fabric.Text 30162 * @param {SVGElement} element Element to parse 30163 * @param {Function} callback callback function invoked after parsing 30164 * @param {Object} [options] Options object 30165 */ 30166 fabric.Text.fromElement = function (element, callback, options) { 30167 if (!element) { 30168 return callback(null); 30169 } 30170 30171 var parsedAttributes = fabric.parseAttributes( 30172 element, 30173 fabric.Text.ATTRIBUTE_NAMES, 30174 ), 30175 parsedAnchor = parsedAttributes.textAnchor || "left"; 30176 options = fabric.util.object.extend( 30177 options ? clone(options) : {}, 30178 parsedAttributes, 30179 ); 30180 30181 options.top = options.top || 0; 30182 options.left = options.left || 0; 30183 if (parsedAttributes.textDecoration) { 30184 var textDecoration = parsedAttributes.textDecoration; 30185 if (textDecoration.indexOf("underline") !== -1) { 30186 options.underline = true; 30187 } 30188 if (textDecoration.indexOf("overline") !== -1) { 30189 options.overline = true; 30190 } 30191 if (textDecoration.indexOf("line-through") !== -1) { 30192 options.linethrough = true; 30193 } 30194 delete options.textDecoration; 30195 } 30196 if ("dx" in parsedAttributes) { 30197 options.left += parsedAttributes.dx; 30198 } 30199 if ("dy" in parsedAttributes) { 30200 options.top += parsedAttributes.dy; 30201 } 30202 if (!("fontSize" in options)) { 30203 options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; 30204 } 30205 30206 var textContent = ""; 30207 30208 // The XML is not properly parsed in IE9 so a workaround to get 30209 // textContent is through firstChild.data. Another workaround would be 30210 // to convert XML loaded from a file to be converted using DOMParser (same way loadSVGFromString() does) 30211 if (!("textContent" in element)) { 30212 if ("firstChild" in element && element.firstChild !== null) { 30213 if ("data" in element.firstChild && element.firstChild.data !== null) { 30214 textContent = element.firstChild.data; 30215 } 30216 } 30217 } else { 30218 textContent = element.textContent; 30219 } 30220 30221 textContent = textContent 30222 .replace(/^\s+|\s+$|\n+/g, "") 30223 .replace(/\s+/g, " "); 30224 var originalStrokeWidth = options.strokeWidth; 30225 options.strokeWidth = 0; 30226 30227 var text = new fabric.Text(textContent, options), 30228 textHeightScaleFactor = text.getScaledHeight() / text.height, 30229 lineHeightDiff = 30230 (text.height + text.strokeWidth) * text.lineHeight - text.height, 30231 scaledDiff = lineHeightDiff * textHeightScaleFactor, 30232 textHeight = text.getScaledHeight() + scaledDiff, 30233 offX = 0; 30234 /* 30235 Adjust positioning: 30236 x/y attributes in SVG correspond to the bottom-left corner of text bounding box 30237 fabric output by default at top, left. 30238 */ 30239 if (parsedAnchor === "center") { 30240 offX = text.getScaledWidth() / 2; 30241 } 30242 if (parsedAnchor === "right") { 30243 offX = text.getScaledWidth(); 30244 } 30245 text.set({ 30246 left: text.left - offX, 30247 top: 30248 text.top - 30249 (textHeight - text.fontSize * (0.07 + text._fontSizeFraction)) / 30250 text.lineHeight, 30251 strokeWidth: 30252 typeof originalStrokeWidth !== "undefined" ? originalStrokeWidth : 1, 30253 }); 30254 callback(text); 30255 }; 30256 /* _FROM_SVG_END_ */ 30257 30258 /** 30259 * Returns fabric.Text instance from an object representation 30260 * @static 30261 * @memberOf fabric.Text 30262 * @param {Object}
30262 object plain js Object to create an instance from 30263 * @param {Function} [callback] Callback to invoke when an fabric.Text instance is created 30264 */ 30265 fabric.Text.fromObject = function (object, callback) { 30266 var objectCopy = clone(object), 30267 path = object.path; 30268 delete objectCopy.path; 30269 return fabric.Object._fromObject( 30270 "Text", 30271 objectCopy, 30272 function (textInstance) { 30273 textInstance.styles = fabric.util.stylesFromArray( 30274 object.styles, 30275 object.text, 30276 ); 30277 if (path) { 30278 fabric.Object._fromObject( 30279 "Path", 30280 path, 30281 function (pathInstance) { 30282 textInstance.set("path", pathInstance); 30283 callback(textInstance); 30284 }, 30285 "path", 30286 ); 30287 } else { 30288 callback(textInstance); 30289 } 30290 }, 30291 "text", 30292 ); 30293 }; 30294 30295 fabric.Text.genericFonts = [ 30296 "sans-serif", 30297 "serif", 30298 "cursive", 30299 "fantasy", 30300 "monospace", 30301 ]; 30302 30303 fabric.util.createAccessors && fabric.util.createAccessors(fabric.Text); 30304})(typeof exports !== "undefined" ? exports : this); 30305 30306(function () { 30307 fabric.util.object.extend( 30308 fabric.Text.prototype, 30309 /** @lends fabric.Text.prototype */ { 30310 /** 30311 * Returns true if object has no styling or no styling in a line 30312 * @param {Number} lineIndex , lineIndex is on wrapped lines. 30313 * @return {Boolean} 30314 */ 30315 isEmptyStyles: function (lineIndex) { 30316 if (!this.styles) { 30317 return true; 30318 } 30319 if (typeof lineIndex !== "undefined" && !this.styles[lineIndex]) { 30320 return true; 30321 } 30322 var obj = 30323 typeof lineIndex === "undefined" 30324 ? this.styles 30325 : { line: this.styles[lineIndex] }; 30326 for (var p1 in obj) { 30327 for (var p2 in obj[p1]) { 30328 // eslint-disable-next-line no-unused-vars 30329 for (var p3 in obj[p1][p2]) { 30330 return false; 30331 } 30332 } 30333 } 30334 return true; 30335 }, 30336 30337 /** 30338 * Returns true if object has a style property or has it ina specified line 30339 * This function is used to detect if a text will use a particular property or not. 30340 * @param {String} property to check for 30341 * @param {Number} lineIndex to check the style on 30342 * @return {Boolean} 30343 */ 30344 styleHas: function (property, lineIndex) { 30345 if (!this.styles || !property || property === "") { 30346 return false; 30347 } 30348 if (typeof lineIndex !== "undefined" && !this.styles[lineIndex]) { 30349 return false; 30350 } 30351 var obj = 30352 typeof lineIndex === "undefined" 30353 ? this.styles 30354 : { 0: this.styles[lineIndex] }; 30355 // eslint-disable-next-line 30356 for (var p1 in obj) { 30357 // eslint-disable-next-line 30358 for (var p2 in obj[p1]) { 30359 if (typeof obj[p1][p2][property] !== "undefined") { 30360 return true; 30361 } 30362 } 30363 } 30364 return false; 30365 }, 30366 30367 /** 30368 * Check if characters in a text have a value for a property 30369 * whose value matches the textbox's value for that property. If so, 30370 * the character-level property is deleted. If the character 30371 * has no other properties, then it is also deleted. Finally, 30372 * if the line containing that character has no other characters 30373 * then it also is deleted. 30374 * 30375 * @param {string} property The property to compare between characters and text. 30376 */ 30377 cleanStyle: function (property) { 30378 if (!this.styles || !property || property === "") { 30379 return false; 30380 } 30381 var obj = this.styles, 30382 stylesCount = 0, 30383 letterCount, 30384 stylePropertyValue, 30385 allStyleObjectPropertiesMatch = true, 30386 graphemeCount = 0, 30387 styleObject; 30388 // eslint-disable-next-line 30389 for (var p1 in obj) { 30390 letterCount = 0; 30391 // eslint-disable-next-line 30392 for (var p2 in obj[p1]) { 30393 var styleObject = obj[p1][p2], 30394 stylePropertyHasBeenSet = styleObject.hasOwnProperty(property); 30395 30396 stylesCount++; 30397 30398 if (stylePropertyHasBeenSet) { 30399 if (!stylePropertyValue) { 30400 stylePropertyValue = styleObject[property]; 30401 } else if (styleObject[property] !== stylePropertyValue) { 30402 allStyleObjectPropertiesMatch = false;
vendor: 9,208 bytes, lines 30403-30680
30403 } 30404 30405 if (styleObject[property] === this[property]) { 30406 delete styleObject[property]; 30407 } 30408 } else { 30409 allStyleObjectPropertiesMatch = false; 30410 } 30411 30412 if (Object.keys(styleObject).length !== 0) { 30413 letterCount++; 30414 } else { 30415 delete obj[p1][p2]; 30416 } 30417 } 30418 30419 if (letterCount === 0) { 30420 delete obj[p1]; 30421 } 30422 } 30423 // if every grapheme has the same style set then 30424 // delete those styles and set it on the parent 30425 for (var i = 0; i < this._textLines.length; i++) { 30426 graphemeCount += this._textLines[i].length; 30427 } 30428 if (allStyleObjectPropertiesMatch && stylesCount === graphemeCount) { 30429 this[property] = stylePropertyValue; 30430 this.removeStyle(property); 30431 } 30432 }, 30433 30434 /** 30435 * Remove a style property or properties from all individual character styles 30436 * in a text object. Deletes the character style object if it contains no other style 30437 * props. Deletes a line style object if it contains no other character styles. 30438 * 30439 * @param {String} props The property to remove from character styles. 30440 */ 30441 removeStyle: function (property) { 30442 if (!this.styles || !property || property === "") { 30443 return; 30444 } 30445 var obj = this.styles, 30446 line, 30447 lineNum, 30448 charNum; 30449 for (lineNum in obj) { 30450 line = obj[lineNum]; 30451 for (charNum in line) { 30452 delete line[charNum][property]; 30453 if (Object.keys(line[charNum]).length === 0) { 30454 delete line[charNum]; 30455 } 30456 } 30457 if (Object.keys(line).length === 0) { 30458 delete obj[lineNum]; 30459 } 30460 } 30461 }, 30462 30463 /** 30464 * @private 30465 */ 30466 _extendStyles: function (index, styles) { 30467 var loc = this.get2DCursorLocation(index); 30468 30469 if (!this._getLineStyle(loc.lineIndex)) { 30470 this._setLineStyle(loc.lineIndex); 30471 } 30472 30473 if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) { 30474 this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {}); 30475 } 30476 30477 fabric.util.object.extend( 30478 this._getStyleDeclaration(loc.lineIndex, loc.charIndex), 30479 styles, 30480 ); 30481 }, 30482 30483 /** 30484 * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start) 30485 * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. 30486 * @param {Boolean} [skipWrapping] consider the location for unwrapped lines. useful to manage styles. 30487 */ 30488 get2DCursorLocation: function (selectionStart, skipWrapping) { 30489 if (typeof selectionStart === "undefined") { 30490 selectionStart = this.selectionStart; 30491 } 30492 var lines = skipWrapping ? this._unwrappedTextLines : this._textLines, 30493 len = lines.length; 30494 for (var i = 0; i < len; i++) { 30495 if (selectionStart <= lines[i].length) { 30496 return { 30497 lineIndex: i, 30498 charIndex: selectionStart, 30499 }; 30500 } 30501 selectionStart -= lines[i].length + this.missingNewlineOffset(i); 30502 } 30503 return { 30504 lineIndex: i - 1, 30505 charIndex: 30506 lines[i - 1].length < selectionStart 30507 ? lines[i - 1].length 30508 : selectionStart, 30509 }; 30510 }, 30511 30512 /** 30513 * Gets style of a current selection/cursor (at the start position) 30514 * if startIndex or endIndex are not provided, selectionStart or selectionEnd will be used. 30515 * @param {Number} [startIndex] Start index to get styles at 30516 * @param {Number} [endIndex] End index to get styles at, if not specified selectionEnd or startIndex + 1 30517 * @param {Boolean} [complete] get full style or not 30518 * @return {Array} styles an array with one, zero or more Style objects 30519 */ 30520 getSelectionStyles: function (startIndex, endIndex, complete) { 30521 if (typeof startIndex === "undefined") { 30522 startIndex = this.selectionStart || 0; 30523 } 30524 if (typeof endIndex === "undefined") { 30525 endIndex = this.selectionEnd || startIndex; 30526 } 30527 var styles = []; 30528 for (var i = startIndex; i < endIndex; i++) { 30529 styles.push(this.getStyleAtPosition(i, complete)); 30530 } 30531 return styles; 30532 }, 30533 30534 /** 30535 * Gets style of a current selection/cursor position 30536 * @param {Number} position to get styles at 30537 * @param {Boolean} [complete] full style if true 30538 * @return {Object} style Style object at a specified index 30539 * @private 30540 */ 30541 getStyleAtPosition: function (position, complete) { 30542 var loc = this.get2DCursorLocation(position), 30543 style = complete 30544 ? this.getCompleteStyleDeclaration(loc.lineIndex, loc.charIndex) 30545 : this._getStyleDeclaration(loc.lineIndex, loc.charIndex); 30546 return style || {}; 30547 }, 30548 30549 /** 30550 * Sets style of a current selection, if no selection exist, do not set anything. 30551 * @param {Object} [styles] Styles object 30552 * @param {Number} [startIndex] Start index to get styles at 30553 * @param {Number} [endIndex] End index to get styles at, if not specified selectionEnd or startIndex + 1 30554 * @return {fabric.IText} thisArg 30555 * @chainable 30556 */ 30557 setSelectionStyles: function (styles, startIndex, endIndex) { 30558 if (typeof startIndex === "undefined") { 30559 startIndex = this.selectionStart || 0; 30560 } 30561 if (typeof endIndex === "undefined") { 30562 endIndex = this.selectionEnd || startIndex; 30563 } 30564 for (var i = startIndex; i < endIndex; i++) { 30565 this._extendStyles(i, styles); 30566 } 30567 /* not included in _extendStyles to avoid clearing cache more than once */ 30568 this._forceClearCache = true; 30569 return this; 30570 }, 30571 30572 /** 30573 * get the reference, not a clone, of the style object for a given character 30574 * @param {Number} lineIndex 30575 * @param {Number} charIndex 30576 * @return {Object} style object 30577 */ 30578 _getStyleDeclaration: function (lineIndex, charIndex) { 30579 var lineStyle = this.styles && this.styles[lineIndex]; 30580 if (!lineStyle) { 30581 return null; 30582 } 30583 return lineStyle[charIndex]; 30584 }, 30585 30586 /** 30587 * return a new object that contains all the style property for a character 30588 * the object returned is newly created 30589 * @param {Number} lineIndex of the line where the character is 30590 * @param {Number} charIndex position of the character on the line 30591 * @return {Object} style object 30592 */ 30593 getCompleteStyleDeclaration: function (lineIndex, charIndex) { 30594 var style = this._getStyleDeclaration(lineIndex, charIndex) || {}, 30595 styleObject = {}, 30596 prop; 30597 for (var i = 0; i < this._styleProperties.length; i++) { 30598 prop = this._styleProperties[i]; 30599 styleObject[prop] = 30600 typeof style[prop] === "undefined" ? this[prop] : style[prop]; 30601 } 30602 return styleObject; 30603 }, 30604 30605 /** 30606 * @param {Number} lineIndex 30607 * @param {Number} charIndex 30608 * @param {Object} style 30609 * @private 30610 */ 30611 _setStyleDeclaration: function (lineIndex, charIndex, style) { 30612 this.styles[lineIndex][charIndex] = style; 30613 }, 30614 30615 /** 30616 * 30617 * @param {Number} lineIndex 30618 * @param {Number} charIndex 30619 * @private 30620 */ 30621 _deleteStyleDeclaration: function (lineIndex, charIndex) { 30622 delete this.styles[lineIndex][charIndex]; 30623 }, 30624 30625 /** 30626 * @param {Number} lineIndex 30627 * @return {Boolean} if the line exists or not 30628 * @private 30629 */ 30630 _getLineStyle: function (lineIndex) { 30631 return !!this.styles[lineIndex]; 30632 }, 30633 30634 /** 30635 * Set the line style to an empty object so that is initialized 30636 * @param {Number} lineIndex 30637 * @private 30638 */ 30639 _setLineStyle: function (lineIndex) { 30640 this.styles[lineIndex] = {}; 30641 }, 30642 30643 /** 30644 * @param {Number} lineIndex 30645 * @private 30646 */ 30647 _deleteLineStyle: function (lineIndex) { 30648 delete this.styles[lineIndex]; 30649 }, 30650 }, 30651 ); 30652})(); 30653 30654(function () { 30655 function parseDecoration(object) { 30656 if (object.textDecoration) { 30657 object.textDecoration.indexOf("underline") > -1 && 30658 (object.underline = true); 30659 object.textDecoration.indexOf("line-through") > -1 && 30660 (object.linethrough = true); 30661 object.textDecoration.indexOf("overline") > -1 && 30662 (object.overline = true); 30663 delete object.textDecoration; 30664 } 30665 } 30666 30667 /** 30668 * IText class (introduced in <b>v1.4</b>) Events are also fired with "text:" 30669 * prefix when observing canvas. 30670 * @class fabric.IText 30671 * @extends fabric.Text 30672 * @mixes fabric.Observable 30673 * 30674 * @fires changed 30675 * @fires selection:changed 30676 * @fires editing:entered 30677 * @fires editing:exited 30678 * 30679 * @return {fabric.IText} thisArg 30680 * @see {@link fabric.IText#initialize}
30680 for constructor definition 30681 * 30682 * <p>Supported key combinations:</p> 30683 * <pre> 30684 * Move cursor: left, right, up, down 30685 * Select character: shift + left, shift + right 30686 * Select text vertically: shift + up, shift + down 30687 * Move cursor by word: alt + left, alt + right 30688 * Select words: shift + alt + left, shift + alt + right 30689 * Move cursor to line start/end: cmd + left, cmd + right or home, end 30690 * Select till start/end of line: cmd + shift + left, cmd + shift + right or shift + home, shift + end 30691 * Jump to start/end of text: cmd + up, cmd + down 30692 * Select till start/end of text: cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown 30693 * Delete character: backspace 30694 * Delete word: alt + backspace 30695 * Delete line: cmd + backspace 30696 * Forward delete: delete 30697 * Copy text: ctrl/cmd + c 30698 * Paste text: ctrl/cmd + v 30699 * Cut text: ctrl/cmd + x 30700 * Select entire text: ctrl/cmd + a 30701 * Quit editing tab or esc 30702 * </pre> 30703 * 30704 * <p>Supported mouse/touch combination</p> 30705 * <pre> 30706 * Position cursor: click/touch 30707 * Create selection: click/touch & drag 30708 * Create selection: click & shift + click 30709 * Select word: double click 30710 * Select line: triple click 30711 * </pre> 30712 */ 30713 fabric.IText = fabric.util.createClass( 30714 fabric.Text, 30715 fabric.Observable, 30716 /** @lends fabric.IText.prototype */ { 30717 /** 30718 * Type of an object
30719 * @type String 30720 * @default 30721 */ 30722 type: "i-text", 30723 30724 /** 30725 * Index where text selection starts (or where cursor is when there is no selection) 30726 * @type Number 30727 * @default 30728 */ 30729 selectionStart: 0, 30730 30731 /** 30732 * Index where text selection ends 30733 * @type Number 30734 * @default 30735 */ 30736 selectionEnd: 0, 30737 30738 /** 30739 * Color of text selection 30740 * @type String 30741 * @default 30742 */ 30743 selectionColor: "rgba(17,119,255,0.3)", 30744 30745 /** 30746 * Indicates whether text is in editing mode 30747 * @type Boolean 30748 * @default 30749 */ 30750 isEditing: false, 30751 30752 /** 30753 * Indicates whether a text can be edited 30754 * @type Boolean 30755 * @default 30756 */ 30757 editable: true, 30758 30759 /** 30760 * Border color of text object while it's in editing mode 30761 * @type String 30762 * @default 30763 */ 30764 editingBorderColor: "rgba(102,153,255,0.25)", 30765 30766 /** 30767 * Width of cursor (in px) 30768 * @type Number 30769 * @default 30770 */ 30771 cursorWidth: 2, 30772 30773 /** 30774 * Color of text cursor color in editing mode. 30775 * if not set (default) will take color from the text. 30776 * if set to a color value that fabric can understand, it will 30777 * be used instead of the color of the text at the current position.
vendor: 8,736 bytes, lines 30778-31067
30778 * @type String 30779 * @default 30780 */ 30781 cursorColor: "", 30782 30783 /** 30784 * Delay between cursor blink (in ms) 30785 * @type Number 30786 * @default 30787 */ 30788 cursorDelay: 1000, 30789 30790 /** 30791 * Duration of cursor fadein (in ms) 30792 * @type Number 30793 * @default 30794 */ 30795 cursorDuration: 600, 30796 30797 /** 30798 * Indicates whether internal text char widths can be cached 30799 * @type Boolean 30800 * @default 30801 */ 30802 caching: true, 30803 30804 /** 30805 * DOM container to append the hiddenTextarea. 30806 * An alternative to attaching to the document.body. 30807 * Useful to reduce laggish redraw of the full document.body tree and 30808 * also with modals event capturing that won't let the textarea take focus. 30809 * @type HTMLElement 30810 * @default 30811 */ 30812 hiddenTextareaContainer: null, 30813 30814 /** 30815 * @private 30816 */ 30817 _reSpace: /\s|\n/, 30818 30819 /** 30820 * @private 30821 */ 30822 _currentCursorOpacity: 0, 30823 30824 /** 30825 * @private 30826 */ 30827 _selectionDirection: null, 30828 30829 /** 30830 * @private 30831 */ 30832 _abortCursorAnimation: false, 30833 30834 /** 30835 * @private 30836 */ 30837 __widthOfSpace: [], 30838 30839 /** 30840 * Helps determining when the text is in composition, so that the cursor 30841 * rendering is altered. 30842 */ 30843 inCompositionMode: false, 30844 30845 /** 30846 * Constructor 30847 * @param {String} text Text string 30848 * @param {Object} [options] Options object 30849 * @return {fabric.IText} thisArg 30850 */ 30851 initialize: function (text, options) { 30852 this.callSuper("initialize", text, options); 30853 this.initBehavior(); 30854 }, 30855 30856 /** 30857 * Sets selection start (left boundary of a selection) 30858 * @param {Number} index Index to set selection start to 30859 */ 30860 setSelectionStart: function (index) { 30861 index = Math.max(index, 0); 30862 this._updateAndFire("selectionStart", index); 30863 }, 30864 30865 /** 30866 * Sets selection end (right boundary of a selection) 30867 * @param {Number} index Index to set selection end to 30868 */ 30869 setSelectionEnd: function (index) { 30870 index = Math.min(index, this.text.length); 30871 this._updateAndFire("selectionEnd", index); 30872 }, 30873 30874 /** 30875 * @private 30876 * @param {String} property 'selectionStart' or 'selectionEnd' 30877 * @param {Number} index new position of property 30878 */ 30879 _updateAndFire: function (property, index) { 30880 if (this[property] !== index) { 30881 this._fireSelectionChanged(); 30882 this[property] = index; 30883 } 30884 this._updateTextarea(); 30885 }, 30886 30887 /** 30888 * Fires the even of selection changed 30889 * @private 30890 */ 30891 _fireSelectionChanged: function () { 30892 this.fire("selection:changed"); 30893 this.canvas && 30894 this.canvas.fire("text:selection:changed", { target: this }); 30895 }, 30896 30897 /** 30898 * Initialize text dimensions. Render all text on given context 30899 * or on a offscreen canvas to get the text width with measureText. 30900 * Updates this.width and this.height with the proper values. 30901 * Does not return dimensions. 30902 * @private 30903 */ 30904 initDimensions: function () { 30905 this.isEditing && this.initDelayedCursor(); 30906 this.clearContextTop(); 30907 this.callSuper("initDimensions"); 30908 }, 30909 30910 /** 30911 * @private 30912 * @param {CanvasRenderingContext2D} ctx Context to render on 30913 */ 30914 render: function (ctx) { 30915 this.clearContextTop(); 30916 this.callSuper("render", ctx); 30917 // clear the cursorOffsetCache, so we ensure to calculate once per renderCursor 30918 // the correct position but not at every cursor animation. 30919 this.cursorOffsetCache = {}; 30920 this.renderCursorOrSelection(); 30921 }, 30922 30923 /** 30924 * @private 30925 * @param {CanvasRenderingContext2D} ctx Context to render on 30926 */ 30927 _render: function (ctx) { 30928 this.callSuper("_render", ctx); 30929 }, 30930 30931 /** 30932 * Prepare and clean the contextTop 30933 */ 30934 clearContextTop: function (skipRestore) { 30935 if (!this.isEditing || !this.canvas || !this.canvas.contextTop) { 30936 return; 30937 } 30938 var ctx = this.canvas.contextTop, 30939 v = this.canvas.viewportTransform; 30940 ctx.save(); 30941 ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); 30942 this.transform(ctx); 30943 this._clearTextArea(ctx); 30944 skipRestore || ctx.restore(); 30945 }, 30946 /** 30947 * Renders cursor or selection (depending on what exists) 30948 * it does on the contextTop. If contextTop is not available, do nothing. 30949 */ 30950 renderCursorOrSelection: function () { 30951 if (!this.isEditing || !this.canvas || !this.canvas.contextTop) { 30952 return; 30953 } 30954 var boundaries = this._getCursorBoundaries(), 30955 ctx = this.canvas.contextTop; 30956 this.clearContextTop(true); 30957 if (this.selectionStart === this.selectionEnd) { 30958 this.renderCursor(boundaries, ctx); 30959 } else { 30960 this.renderSelection(boundaries, ctx); 30961 } 30962 ctx.restore(); 30963 }, 30964 30965 _clearTextArea: function (ctx) { 30966 // we add 4 pixel, to be sure to do not leave any pixel out 30967 var width = this.width + 4, 30968 height = this.height + 4; 30969 ctx.clearRect(-width / 2, -height / 2, width, height); 30970 }, 30971 30972 /** 30973 * Returns cursor boundaries (left, top, leftOffset, topOffset) 30974 * @private 30975 * @param {Array} chars Array of characters 30976 * @param {String} typeOfBoundaries 30977 */ 30978 _getCursorBoundaries: function (position) { 30979 // left/top are left/top of entire text box 30980 // leftOffset/topOffset are offset from that left/top point of a text box 30981 30982 if (typeof position === "undefined") { 30983 position = this.selectionStart; 30984 } 30985 30986 var left = this._getLeftOffset(), 30987 top = this._getTopOffset(), 30988 offsets = this._getCursorBoundariesOffsets(position); 30989 return { 30990 left: left, 30991 top: top, 30992 leftOffset: offsets.left, 30993 topOffset: offsets.top, 30994 }; 30995 }, 30996 30997 /** 30998 * @private 30999 */ 31000 _getCursorBoundariesOffsets: function (position) { 31001 if (this.cursorOffsetCache && "top" in this.cursorOffsetCache) { 31002 return this.cursorOffsetCache; 31003 } 31004 var lineLeftOffset, 31005 lineIndex, 31006 charIndex, 31007 topOffset = 0, 31008 leftOffset = 0, 31009 boundaries, 31010 cursorPosition = this.get2DCursorLocation(position); 31011 charIndex = cursorPosition.charIndex; 31012 lineIndex = cursorPosition.lineIndex; 31013 for (var i = 0; i < lineIndex; i++) { 31014 topOffset += this.getHeightOfLine(i); 31015 } 31016 lineLeftOffset = this._getLineLeftOffset(lineIndex); 31017 var bound = this.__charBounds[lineIndex][charIndex]; 31018 bound && (leftOffset = bound.left); 31019 if ( 31020 this.charSpacing !== 0 && 31021 charIndex === this._textLines[lineIndex].length 31022 ) { 31023 leftOffset -= this._getWidthOfCharSpacing(); 31024 } 31025 boundaries = { 31026 top: topOffset, 31027 left: lineLeftOffset + (leftOffset > 0 ? leftOffset : 0), 31028 }; 31029 if (this.direction === "rtl") { 31030 boundaries.left *= -1; 31031 } 31032 this.cursorOffsetCache = boundaries; 31033 return this.cursorOffsetCache; 31034 }, 31035 31036 /** 31037 * Renders cursor 31038 * @param {Object} boundaries 31039 * @param {CanvasRenderingContext2D} ctx transformed context to draw on 31040 */ 31041 renderCursor: function (boundaries, ctx) { 31042 var cursorLocation = this.get2DCursorLocation(), 31043 lineIndex = cursorLocation.lineIndex, 31044 charIndex = 31045 cursorLocation.charIndex > 0 ? cursorLocation.charIndex - 1 : 0, 31046 charHeight = this.getValueOfPropertyAt( 31047 lineIndex, 31048 charIndex, 31049 "fontSize", 31050 ), 31051 multiplier = this.scaleX * this.canvas.getZoom(), 31052 cursorWidth = this.cursorWidth / multiplier, 31053 topOffset = boundaries.topOffset, 31054 dy = this.getValueOfPropertyAt(lineIndex, charIndex, "deltaY"); 31055 topOffset += 31056 ((1 - this._fontSizeFraction) * this.getHeightOfLine(lineIndex)) / 31057 this.lineHeight - 31058 charHeight * (1 - this._fontSizeFraction); 31059 31060 if (this.inCompositionMode) { 31061 this.renderSelection(boundaries, ctx); 31062 } 31063 ctx.fillStyle = 31064 this.cursorColor || 31065 this.getValueOfPropertyAt(lineIndex, charIndex, "fill"); 31066 ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; 31067 ctx.fillRect(
31068 boundaries.left + boundaries.leftOffset - cursorWidth / 2, 31069 topOffset + boundaries.top + dy, 31070 cursorWidth, 31071 charHeight, 31072 ); 31073 }, 31074 31075 /** 31076 * Renders text selection 31077 * @param {Object} boundaries Object with left/top/leftOffset/topOffset 31078 * @param {CanvasRenderingContext2D} ctx transformed context to draw on 31079 */ 31080 renderSelection: function (boundaries, ctx) { 31081 var selectionStart = this.inCompositionMode 31082 ? this.hiddenTextarea.selectionStart 31083 : this.selectionStart, 31084 selectionEnd = this.inCompositionMode 31085 ? this.hiddenTextarea.selectionEnd 31086 : this.selectionEnd, 31087 isJustify = this.textAlign.indexOf("justify") !== -1, 31088 start = this.get2DCursorLocation(selectionStart), 31089 end = this.get2DCursorLocation(selectionEnd), 31090 startLine = start.lineIndex, 31091 endLine = end.lineIndex, 31092 startChar = start.charIndex < 0 ? 0 : start.charIndex, 31093 endChar = end.charIndex < 0 ? 0 : end.charIndex; 31094 31095 for (var i = startLine; i <= endLine; i++) { 31096 var lineOffset = this._getLineLeftOffset(i) || 0, 31097 lineHeight = this.getHeightOfLine(i), 31098 realLineHeight = 0, 31099 boxStart = 0, 31100 boxEnd = 0; 31101 31102 if (i === startLine) { 31103 boxStart = this.__charBounds[startLine][startChar].left; 31104 } 31105 if (i >= startLine && i < endLine) { 31106 boxEnd = 31107 isJustify && !this.isEndOfWrapping(i) 31108 ? this.width 31109 : this.getLineWidth(i) || 5; // WTF is this 5? 31110 } else if (i === endLine) { 31111 if (endChar === 0) { 31112 boxEnd = this.__charBounds[endLine][endChar].left; 31113 } else { 31114 var charSpacing = this._getWidthOfCharSpacing(); 31115 boxEnd = 31116 this.__charBounds[endLine][endChar - 1].left + 31117 this.__charBounds[endLine][endChar - 1].width - 31118 charSpacing; 31119 } 31120 } 31121 realLineHeight = lineHeight; 31122 if (this.lineHeight < 1 || (i === endLine && this.lineHeight > 1)) { 31123 lineHeight /= this.lineHeight; 31124 } 31125 var drawStart = boundaries.left + lineOffset + boxStart, 31126 drawWidth = boxEnd - boxStart, 31127 drawHeight = lineHeight, 31128 extraTop = 0; 31129 if (this.inCompositionMode) { 31130 ctx.fillStyle = this.compositionColor || "black"; 31131 drawHeight = 1; 31132 extraTop = lineHeight; 31133 } else { 31134 ctx.fillStyle = this.selectionColor; 31135 } 31136 if (this.direction === "rtl") { 31137 drawStart = this.width - drawStart - drawWidth; 31138 } 31139 ctx.fillRect( 31140 drawStart, 31141 boundaries.top + boundaries.topOffset + extraTop, 31142 drawWidth, 31143 drawHeight, 31144 ); 31145 boundaries.topOffset += realLineHeight; 31146 } 31147 }, 31148 31149 /** 31150 * High level function to know the height of the cursor. 31151 * the currentChar is the one that precedes the cursor 31152 * Returns fontSize of char at the current cursor 31153 * Unused from the library, is for the end user 31154 * @return {Number} Character font size 31155 */ 31156 getCurrentCharFontSize: function () { 31157 var cp = this._getCurrentCharIndex(); 31158 return this.getValueOfPropertyAt(cp.l, cp.c, "fontSize"); 31159 }, 31160 31161 /** 31162 * High level function to know the color of the cursor. 31163 * the currentChar is the one that precedes the cursor 31164 * Returns color (fill) of char at the current cursor 31165 * if the text object has a pattern or gradient for filler, it will return that. 31166 * Unused by the library, is for the end user 31167 * @return {String | fabric.Gradient | fabric.Pattern} Character color (fill) 31168 */ 31169 getCurrentCharColor: function () { 31170 var cp = this._getCurrentCharIndex(); 31171 return this.getValueOfPropertyAt(cp.l, cp.c, "fill"); 31172 }, 31173 31174 /** 31175 * Returns the cursor position for the getCurrent.. functions 31176 * @private 31177 */ 31178 _getCurrentCharIndex: function () { 31179 var cursorPosition = this.get2DCursorLocation( 31180 this.selectionStart, 31181 true, 31182 ), 31183 charIndex = 31184 cursorPosition.charIndex > 0 ? cursorPosition.charIndex - 1 : 0;
31185 return { l: cursorPosition.lineIndex, c: charIndex }; 31186 }, 31187 }, 31188 ); 31189 31190 /** 31191 * Returns fabric.IText instance from an object representation 31192 * @static 31193 * @memberOf fabric.IText 31194 * @param {Object} object Object to create an instance from 31195 * @param {function} [callback] invoked with new instance as argument 31196 */ 31197 fabric.IText.fromObject = function (object, callback) { 31198 var styles = fabric.util.stylesFromArray(object.styles, object.text); 31199 //copy object to prevent mutation 31200 var objCopy = Object.assign({}, object, { styles: styles }); 31201 parseDecoration(objCopy); 31202 if (objCopy.styles) { 31203 for (var i in objCopy.styles) { 31204 for (var j in objCopy.styles[i]) { 31205 parseDecoration(objCopy.styles[i][j]); 31206 } 31207 } 31208 } 31209 fabric.Object._fromObject("IText", objCopy, callback, "text"); 31210 }; 31211})(); 31212 31213(function () { 31214 var clone = fabric.util.object.clone; 31215 31216 fabric.util.object.extend( 31217 fabric.IText.prototype, 31218 /** @lends fabric.IText.prototype */ { 31219 /** 31220 * Initializes all the interactive behavior of IText 31221 */ 31222 initBehavior: function () { 31223 this.initAddedHandler(); 31224 this.initRemovedHandler(); 31225 this.initCursorSelectionHandlers(); 31226 this.initDoubleClickSimulation(); 31227 this.mouseMoveHandler = this.mouseMoveHandler.bind(this); 31228 }, 31229 31230 onDeselect: function () { 31231 this.isEditing && this.exitEditing(); 31232 this.selected = false; 31233 }, 31234 31235 /** 31236 * Initializes "added" event handler 31237 */ 31238 initAddedHandler: function () { 31239 var _this = this; 31240 this.on("added", function () { 31241 var canvas = _this.canvas; 31242 if (canvas) { 31243 if (!canvas._hasITextHandlers) { 31244 canvas._hasITextHandlers = true; 31245 _this._initCanvasHandlers(canvas); 31246 } 31247 canvas._iTextInstances = canvas._iTextInstances || []; 31248 canvas._iTextInstances.push(_this); 31249 } 31250 }); 31251 }, 31252 31253 initRemovedHandler: function () { 31254 var _this = this; 31255 this.on("removed", function () { 31256 var canvas = _this.canvas; 31257 if (canvas) { 31258 canvas._iTextInstances = canvas._iTextInstances || []; 31259 fabric.util.removeFromArray(canvas._iTextInstances, _this); 31260 if (canvas._iTextInstances.length === 0) { 31261 canvas._hasITextHandlers = false; 31262 _this._removeCanvasHandlers(canvas); 31263 } 31264 } 31265 }); 31266 }, 31267 31268 /** 31269 * register canvas event to manage exiting on other instances 31270 * @private 31271 */ 31272 _initCanvasHandlers: function (canvas) { 31273 canvas._mouseUpITextHandler = function () { 31274 if (canvas._iTextInstances) { 31275 canvas._iTextInstances.forEach(function (obj) { 31276 obj.__isMousedown = false; 31277 }); 31278 } 31279 }; 31280 canvas.on("mouse:up", canvas._mouseUpITextHandler); 31281 }, 31282 31283 /** 31284 * remove canvas event to manage exiting on other instances 31285 * @private 31286 */ 31287 _removeCanvasHandlers: function (canvas) { 31288 canvas.off("mouse:up", canvas._mouseUpITextHandler); 31289 }, 31290 31291 /** 31292 * @private 31293 */ 31294 _tick: function () { 31295 this._currentTickState = this._animateCursor( 31296 this, 31297 1, 31298 this.cursorDuration, 31299 "_onTickComplete", 31300 ); 31301 }, 31302 31303 /** 31304 * @private 31305 */ 31306 _animateCursor: function (obj, targetOpacity, duration, completeMethod) { 31307 var tickState; 31308 31309 tickState = { 31310 isAborted: false, 31311 abort: function () { 31312 this.isAborted = true; 31313 }, 31314 }; 31315 31316 obj.animate("_currentCursorOpacity", targetOpacity, { 31317 duration: duration, 31318 onComplete: function () { 31319 if (!tickState.isAborted) { 31320 obj[completeMethod](); 31321 } 31322 }, 31323 onChange: function () { 31324 // we do not want to animate a selection, only cursor 31325 if (obj.canvas && obj.selectionStart === obj.selectionEnd) { 31326 obj.renderCursorOrSelection(); 31327 } 31328 }, 31329 abort: function () { 31330 return tickState.isAborted; 31331 }, 31332 }); 31333 return tickState; 31334 }, 31335 31336 /** 31337 * @private 31338 */ 31339 _onTickComplete: function () { 31340 var _this = this; 31341 31342 if (this._cursorTimeout1) { 31343 clearTimeout(this._cursorTimeout1); 31344 } 31345 this._cursorTimeout1 = setTimeout(function () { 31346 _this._currentTickCompleteState = _this._animateCursor( 31347 _this, 31348 0, 31349 this.cursorDuration / 2, 31350 "_tick", 31351 ); 31352 }, 100); 31353 }, 31354 31355 /** 31356 * Initializes delayed cursor 31357 */ 31358 initDelayedCursor: function (restart) { 31359 var _this = this,
vendor: 7,251 bytes, lines 31360-31599
31360 delay = restart ? 0 : this.cursorDelay; 31361 31362 this.abortCursorAnimation(); 31363 this._currentCursorOpacity = 1; 31364 this._cursorTimeout2 = setTimeout(function () { 31365 _this._tick(); 31366 }, delay); 31367 }, 31368 31369 /** 31370 * Aborts cursor animation and clears all timeouts 31371 */ 31372 abortCursorAnimation: function () { 31373 var shouldClear = 31374 this._currentTickState || this._currentTickCompleteState, 31375 canvas = this.canvas; 31376 this._currentTickState && this._currentTickState.abort(); 31377 this._currentTickCompleteState && 31378 this._currentTickCompleteState.abort(); 31379 31380 clearTimeout(this._cursorTimeout1); 31381 clearTimeout(this._cursorTimeout2); 31382 31383 this._currentCursorOpacity = 0; 31384 // to clear just itext area we need to transform the context 31385 // it may not be worth it 31386 if (shouldClear && canvas) { 31387 canvas.clearContext(canvas.contextTop || canvas.contextContainer); 31388 } 31389 }, 31390 31391 /** 31392 * Selects entire text 31393 * @return {fabric.IText} thisArg 31394 * @chainable 31395 */ 31396 selectAll: function () { 31397 this.selectionStart = 0; 31398 this.selectionEnd = this._text.length; 31399 this._fireSelectionChanged(); 31400 this._updateTextarea(); 31401 return this; 31402 }, 31403 31404 /** 31405 * Returns selected text 31406 * @return {String} 31407 */ 31408 getSelectedText: function () { 31409 return this._text 31410 .slice(this.selectionStart, this.selectionEnd) 31411 .join(""); 31412 }, 31413 31414 /** 31415 * Find new selection index representing start of current word according to current selection index 31416 * @param {Number} startFrom Current selection index 31417 * @return {Number} New selection index 31418 */ 31419 findWordBoundaryLeft: function (startFrom) { 31420 var offset = 0, 31421 index = startFrom - 1; 31422 31423 // remove space before cursor first 31424 if (this._reSpace.test(this._text[index])) { 31425 while (this._reSpace.test(this._text[index])) { 31426 offset++; 31427 index--; 31428 } 31429 } 31430 while (/\S/.test(this._text[index]) && index > -1) { 31431 offset++; 31432 index--; 31433 } 31434 31435 return startFrom - offset; 31436 }, 31437 31438 /** 31439 * Find new selection index representing end of current word according to current selection index 31440 * @param {Number} startFrom Current selection index 31441 * @return {Number} New selection index 31442 */ 31443 findWordBoundaryRight: function (startFrom) { 31444 var offset = 0, 31445 index = startFrom; 31446 31447 // remove space after cursor first 31448 if (this._reSpace.test(this._text[index])) { 31449 while (this._reSpace.test(this._text[index])) { 31450 offset++; 31451 index++; 31452 } 31453 } 31454 while (/\S/.test(this._text[index]) && index < this._text.length) { 31455 offset++; 31456 index++; 31457 } 31458 31459 return startFrom + offset; 31460 }, 31461 31462 /** 31463 * Find new selection index representing start of current line according to current selection index 31464 * @param {Number} startFrom Current selection index 31465 * @return {Number} New selection index 31466 */ 31467 findLineBoundaryLeft: function (startFrom) { 31468 var offset = 0, 31469 index = startFrom - 1; 31470 31471 while (!/\n/.test(this._text[index]) && index > -1) { 31472 offset++; 31473 index--; 31474 } 31475 31476 return startFrom - offset; 31477 }, 31478 31479 /** 31480 * Find new selection index representing end of current line according to current selection index 31481 * @param {Number} startFrom Current selection index 31482 * @return {Number} New selection index 31483 */ 31484 findLineBoundaryRight: function (startFrom) { 31485 var offset = 0, 31486 index = startFrom; 31487 31488 while (!/\n/.test(this._text[index]) && index < this._text.length) { 31489 offset++; 31490 index++; 31491 } 31492 31493 return startFrom + offset; 31494 }, 31495 31496 /** 31497 * Finds index corresponding to beginning or end of a word 31498 * @param {Number} selectionStart Index of a character 31499 * @param {Number} direction 1 or -1 31500 * @return {Number} Index of the beginning or end of a word 31501 */ 31502 searchWordBoundary: function (selectionStart, direction) { 31503 var text = this._text, 31504 index = this._reSpace.test(text[selectionStart]) 31505 ? selectionStart - 1 31506 : selectionStart, 31507 _char = text[index], 31508 // wrong 31509 reNonWord = fabric.reNonWord; 31510 31511 while (!reNonWord.test(_char) && index > 0 && index < text.length) { 31512 index += direction; 31513 _char = text[index]; 31514 } 31515 if (reNonWord.test(_char)) { 31516 index += direction === 1 ? 0 : 1; 31517 } 31518 return index; 31519 }, 31520 31521 /** 31522 * Selects a word based on the index 31523 * @param {Number} selectionStart Index of a character 31524 */ 31525 selectWord: function (selectionStart) { 31526 selectionStart = selectionStart || this.selectionStart; 31527 var newSelectionStart = this.searchWordBoundary( 31528 selectionStart, 31529 -1, 31530 ) /* search backwards */, 31531 newSelectionEnd = this.searchWordBoundary( 31532 selectionStart, 31533 1, 31534 ); /* search forward */ 31535 31536 this.selectionStart = newSelectionStart; 31537 this.selectionEnd = newSelectionEnd; 31538 this._fireSelectionChanged(); 31539 this._updateTextarea(); 31540 this.renderCursorOrSelection(); 31541 }, 31542 31543 /** 31544 * Selects a line based on the index 31545 * @param {Number} selectionStart Index of a character 31546 * @return {fabric.IText} thisArg 31547 * @chainable 31548 */ 31549 selectLine: function (selectionStart) { 31550 selectionStart = selectionStart || this.selectionStart; 31551 var newSelectionStart = this.findLineBoundaryLeft(selectionStart), 31552 newSelectionEnd = this.findLineBoundaryRight(selectionStart); 31553 31554 this.selectionStart = newSelectionStart; 31555 this.selectionEnd = newSelectionEnd; 31556 this._fireSelectionChanged(); 31557 this._updateTextarea(); 31558 return this; 31559 }, 31560 31561 /** 31562 * Enters editing state 31563 * @return {fabric.IText} thisArg 31564 * @chainable 31565 */ 31566 enterEditing: function (e) { 31567 if (this.isEditing || !this.editable) { 31568 return; 31569 } 31570 31571 if (this.canvas) { 31572 this.canvas.calcOffset(); 31573 this.exitEditingOnOthers(this.canvas); 31574 } 31575 31576 this.isEditing = true; 31577 31578 this.initHiddenTextarea(e); 31579 this.hiddenTextarea.focus(); 31580 this.hiddenTextarea.value = this.text; 31581 this._updateTextarea(); 31582 this._saveEditingProps(); 31583 this._setEditingProps(); 31584 this._textBeforeEdit = this.text; 31585 31586 this._tick(); 31587 this.fire("editing:entered"); 31588 this._fireSelectionChanged(); 31589 if (!this.canvas) { 31590 return this; 31591 } 31592 this.canvas.fire("text:editing:entered", { target: this }); 31593 this.initMouseMoveHandler(); 31594 this.canvas.requestRenderAll(); 31595 return this; 31596 }, 31597 31598 exitEditingOnOthers: function (canvas) { 31599 if (canvas._iTextInstances) {
31600 canvas._iTextInstances.forEach(function (obj) { 31601 obj.selected = false; 31602 if (obj.isEditing) { 31603 obj.exitEditing(); 31604 } 31605 }); 31606 } 31607 }, 31608 31609 /** 31610 * Initializes "mousemove" event handler 31611 */ 31612 initMouseMoveHandler: function () { 31613 this.canvas.on("mouse:move", this.mouseMoveHandler); 31614 }, 31615 31616 /** 31617 * @private 31618 */ 31619 mouseMoveHandler: function (options) { 31620 if (!this.__isMousedown || !this.isEditing) { 31621 return; 31622 } 31623 31624 // regain focus 31625 document.activeElement !== this.hiddenTextarea && 31626 this.hiddenTextarea.focus(); 31627 31628 var newSelectionStart = this.getSelectionStartFromPointer(options.e), 31629 currentStart = this.selectionStart, 31630 currentEnd = this.selectionEnd; 31631 if ( 31632 (newSelectionStart !== this.__selectionStartOnMouseDown || 31633 currentStart === currentEnd) && 31634 (currentStart === newSelectionStart || 31635 currentEnd === newSelectionStart) 31636 ) { 31637 return; 31638 } 31639 if (newSelectionStart > this.__selectionStartOnMouseDown) { 31640 this.selectionStart = this.__selectionStartOnMouseDown; 31641 this.selectionEnd = newSelectionStart; 31642 } else { 31643 this.selectionStart = newSelectionStart; 31644 this.selectionEnd = this.__selectionStartOnMouseDown; 31645 } 31646 if ( 31647 this.selectionStart !== currentStart || 31648 this.selectionEnd !== currentEnd 31649 ) { 31650 this.restartCursorIfNeeded(); 31651 this._fireSelectionChanged(); 31652 this._updateTextarea(); 31653 this.renderCursorOrSelection(); 31654 } 31655 }, 31656 31657 /** 31658 * @private 31659 */ 31660 _setEditingProps: function () { 31661 this.hoverCursor = "text"; 31662 31663 if (this.canvas) { 31664 this.canvas.defaultCursor = this.canvas.moveCursor = "text"; 31665 } 31666 31667 this.borderColor = this.editingBorderColor; 31668 this.hasControls = this.selectable = false; 31669 this.lockMovementX = this.lockMovementY = true; 31670 }, 31671 31672 /** 31673 * convert from textarea to grapheme indexes 31674 */ 31675 fromStringToGraphemeSelection: function (start, end, text) { 31676 var smallerTextStart = text.slice(0, start), 31677 graphemeStart = 31678 fabric.util.string.graphemeSplit(smallerTextStart).length; 31679 if (start === end) { 31680 return { selectionStart: graphemeStart, selectionEnd: graphemeStart }; 31681 } 31682 var smallerTextEnd = text.slice(start, end), 31683 graphemeEnd = fabric.util.string.graphemeSplit(smallerTextEnd).length; 31684 return { 31685 selectionStart: graphemeStart, 31686 selectionEnd: graphemeStart + graphemeEnd, 31687 }; 31688 }, 31689 31690 /** 31691 * convert from fabric to textarea values 31692 */ 31693 fromGraphemeToStringSelection: function (start, end, _text) { 31694 var smallerTextStart = _text.slice(0, start), 31695 graphemeStart = smallerTextStart.join("").length; 31696 if (start === end) { 31697 return { selectionStart: graphemeStart, selectionEnd: graphemeStart }; 31698 } 31699 var smallerTextEnd = _text.slice(start, end), 31700 graphemeEnd = smallerTextEnd.join("").length; 31701 return { 31702 selectionStart: graphemeStart, 31703 selectionEnd: graphemeStart + graphemeEnd, 31704 }; 31705 }, 31706 31707 /** 31708 * @private 31709 */ 31710 _updateTextarea: function () { 31711 this.cursorOffsetCache = {}; 31712 if (!this.hiddenTextarea) { 31713 return; 31714 } 31715 if (!this.inCompositionMode) { 31716 var newSelection = this.fromGraphemeToStringSelection( 31717 this.selectionStart, 31718 this.selectionEnd, 31719 this._text, 31720 ); 31721 this.hiddenTextarea.selectionStart = newSelection.selectionStart; 31722 this.hiddenTextarea.selectionEnd = newSelection.selectionEnd; 31723 } 31724 this.updateTextareaPosition(); 31725 }, 31726 31727 /** 31728 * @private 31729 */ 31730 updateFromTextArea: function () { 31731 if (!this.hiddenTextarea) { 31732 return; 31733 }
31734 this.cursorOffsetCache = {}; 31735 this.text = this.hiddenTextarea.value; 31736 if (this._shouldClearDimensionCache()) { 31737 this.initDimensions(); 31738 this.setCoords(); 31739 } 31740 var newSelection = this.fromStringToGraphemeSelection( 31741 this.hiddenTextarea.selectionStart, 31742 this.hiddenTextarea.selectionEnd, 31743 this.hiddenTextarea.value, 31744 ); 31745 this.selectionEnd = this.selectionStart = newSelection.selectionEnd; 31746 if (!this.inCompositionMode) { 31747 this.selectionStart = newSelection.selectionStart; 31748 } 31749 this.updateTextareaPosition(); 31750 }, 31751 31752 /** 31753 * @private 31754 */ 31755 updateTextareaPosition: function () { 31756 if (this.selectionStart === this.selectionEnd) { 31757 var style = this._calcTextareaPosition(); 31758 this.hiddenTextarea.style.left = style.left; 31759 this.hiddenTextarea.style.top = style.top; 31760 } 31761 }, 31762 31763 /** 31764 * @private 31765 * @return {Object} style contains style for hiddenTextarea 31766 */ 31767 _calcTextareaPosition: function () { 31768 if (!this.canvas) { 31769 return { x: 1, y: 1 }; 31770 } 31771 var desiredPosition = this.inCompositionMode 31772 ? this.compositionStart 31773 : this.selectionStart, 31774 boundaries = this._getCursorBoundaries(desiredPosition), 31775 cursorLocation = this.get2DCursorLocation(desiredPosition), 31776 lineIndex = cursorLocation.lineIndex, 31777 charIndex = cursorLocation.charIndex, 31778 charHeight = 31779 this.getValueOfPropertyAt(lineIndex, charIndex, "fontSize") * 31780 this.lineHeight, 31781 leftOffset = boundaries.leftOffset, 31782 m = this.calcTransformMatrix(), 31783 p = { 31784 x: boundaries.left + leftOffset, 31785 y: boundaries.top + boundaries.topOffset + charHeight, 31786 }, 31787 retinaScaling = this.canvas.getRetinaScaling(), 31788 upperCanvas = this.canvas.upperCanvasEl, 31789 upperCanvasWidth = upperCanvas.width / retinaScaling, 31790 upperCanvasHeight = upperCanvas.height / retinaScaling, 31791 maxWidth = upperCanvasWidth - charHeight, 31792 maxHeight = upperCanvasHeight - charHeight, 31793 scaleX = upperCanvas.clientWidth / upperCanvasWidth, 31794 scaleY = upperCanvas.clientHeight / upperCanvasHeight; 31795 31796 p = fabric.util.transformPoint(p, m); 31797 p = fabric.util.transformPoint(p, this.canvas.viewportTransform); 31798 p.x *= scaleX; 31799 p.y *= scaleY; 31800 if (p.x < 0) { 31801 p.x = 0; 31802 } 31803 if (p.x > maxWidth) { 31804 p.x = maxWidth; 31805 } 31806 if (p.y < 0) { 31807 p.y = 0; 31808 } 31809 if (p.y > maxHeight) { 31810 p.y = maxHeight; 31811 } 31812 31813 // add canvas offset on document 31814 p.x += this.canvas._offset.left; 31815 p.y += this.canvas._offset.top; 31816 31817 return { 31818 left: p.x + "px", 31819 top: p.y + "px", 31820 fontSize: charHeight + "px", 31821 charHeight: charHeight, 31822 }; 31823 }, 31824 31825 /** 31826 * @private 31827 */ 31828 _saveEditingProps: function () { 31829 this._savedProps = { 31830 hasControls: this.hasControls, 31831 borderColor: this.borderColor, 31832 lockMovementX: this.lockMovementX, 31833 lockMovementY: this.lockMovementY, 31834 hoverCursor: this.hoverCursor, 31835 selectable: this.selectable, 31836 defaultCursor: this.canvas && this.canvas.defaultCursor, 31837 moveCursor: this.canvas && this.canvas.moveCursor, 31838 }; 31839 }, 31840 31841 /** 31842 * @private 31843 */ 31844 _restoreEditingProps: function () { 31845 if (!this._savedProps) { 31846 return; 31847 } 31848 31849 this.hoverCursor = this._savedProps.hoverCursor; 31850 this.hasControls = this._savedProps.hasControls; 31851 this.borderColor = this._savedProps.borderColor; 31852 this.selectable = this._savedProps.selectable; 31853 this.lockMovementX = this._savedProps.lockMovementX; 31854 this.lockMovementY = this._savedProps.lockMovementY; 31855 31856 if (this.canvas) { 31857 this.canvas.defaultCursor = this._savedProps.defaultCursor; 31858 this.canvas.moveCursor = this._savedProps.moveCursor; 31859 } 31860 }, 31861 31862 /** 31863 * Exits from editing state 31864 * @return {fabric.IText} thisArg 31865 * @chainable 31866 */ 31867 exitEditing: function () { 31868 var isTextChanged = this._textBeforeEdit !== this.text; 31869 var hiddenTextarea = this.hiddenTextarea; 31870 this.selected = false;
vendor: 10,609 bytes, lines 31871-32166
31871 this.isEditing = false; 31872 31873 this.selectionEnd = this.selectionStart; 31874 31875 if (hiddenTextarea) { 31876 hiddenTextarea.blur && hiddenTextarea.blur(); 31877 hiddenTextarea.parentNode && 31878 hiddenTextarea.parentNode.removeChild(hiddenTextarea); 31879 } 31880 this.hiddenTextarea = null; 31881 this.abortCursorAnimation(); 31882 this._restoreEditingProps(); 31883 this._currentCursorOpacity = 0; 31884 if (this._shouldClearDimensionCache()) { 31885 this.initDimensions(); 31886 this.setCoords(); 31887 } 31888 this.fire("editing:exited"); 31889 isTextChanged && this.fire("modified"); 31890 if (this.canvas) { 31891 this.canvas.off("mouse:move", this.mouseMoveHandler); 31892 this.canvas.fire("text:editing:exited", { target: this }); 31893 isTextChanged && 31894 this.canvas.fire("object:modified", { target: this }); 31895 } 31896 return this; 31897 }, 31898 31899 /** 31900 * @private 31901 */ 31902 _removeExtraneousStyles: function () { 31903 for (var prop in this.styles) { 31904 if (!this._textLines[prop]) { 31905 delete this.styles[prop]; 31906 } 31907 } 31908 }, 31909 31910 /** 31911 * remove and reflow a style block from start to end. 31912 * @param {Number} start linear start position for removal (included in removal) 31913 * @param {Number} end linear end position for removal ( excluded from removal ) 31914 */ 31915 removeStyleFromTo: function (start, end) { 31916 var cursorStart = this.get2DCursorLocation(start, true), 31917 cursorEnd = this.get2DCursorLocation(end, true), 31918 lineStart = cursorStart.lineIndex, 31919 charStart = cursorStart.charIndex, 31920 lineEnd = cursorEnd.lineIndex, 31921 charEnd = cursorEnd.charIndex, 31922 i, 31923 styleObj; 31924 if (lineStart !== lineEnd) { 31925 // step1 remove the trailing of lineStart 31926 if (this.styles[lineStart]) { 31927 for ( 31928 i = charStart; 31929 i < this._unwrappedTextLines[lineStart].length; 31930 i++ 31931 ) { 31932 delete this.styles[lineStart][i]; 31933 } 31934 } 31935 // step2 move the trailing of lineEnd to lineStart if needed 31936 if (this.styles[lineEnd]) { 31937 for ( 31938 i = charEnd; 31939 i < this._unwrappedTextLines[lineEnd].length; 31940 i++ 31941 ) { 31942 styleObj = this.styles[lineEnd][i]; 31943 if (styleObj) { 31944 this.styles[lineStart] || (this.styles[lineStart] = {}); 31945 this.styles[lineStart][charStart + i - charEnd] = styleObj; 31946 } 31947 } 31948 } 31949 // step3 detects lines will be completely removed. 31950 for (i = lineStart + 1; i <= lineEnd; i++) { 31951 delete this.styles[i]; 31952 } 31953 // step4 shift remaining lines. 31954 this.shiftLineStyles(lineEnd, lineStart - lineEnd); 31955 } else { 31956 // remove and shift left on the same line 31957 if (this.styles[lineStart]) { 31958 styleObj = this.styles[lineStart]; 31959 var diff = charEnd - charStart, 31960 numericChar, 31961 _char; 31962 for (i = charStart; i < charEnd; i++) { 31963 delete styleObj[i]; 31964 } 31965 for (_char in this.styles[lineStart]) { 31966 numericChar = parseInt(_char, 10); 31967 if (numericChar >= charEnd) { 31968 styleObj[numericChar - diff] = styleObj[_char]; 31969 delete styleObj[_char]; 31970 } 31971 } 31972 } 31973 } 31974 }, 31975 31976 /** 31977 * Shifts line styles up or down 31978 * @param {Number} lineIndex Index of a line 31979 * @param {Number} offset Can any number? 31980 */ 31981 shiftLineStyles: function (lineIndex, offset) { 31982 // shift all line styles by offset upward or downward 31983 // do not clone deep. we need new array, not new style objects 31984 var clonedStyles = clone(this.styles); 31985 for (var line in this.styles) { 31986 var numericLine = parseInt(line, 10); 31987 if (numericLine > lineIndex) { 31988 this.styles[numericLine + offset] = clonedStyles[numericLine]; 31989 if (!clonedStyles[numericLine - offset]) { 31990 delete this.styles[numericLine]; 31991 } 31992 } 31993 } 31994 }, 31995 31996 restartCursorIfNeeded: function () { 31997 if ( 31998 !this._currentTickState || 31999 this._currentTickState.isAborted || 32000 !this._currentTickCompleteState || 32001 this._currentTickCompleteState.isAborted 32002 ) { 32003 this.initDelayedCursor(); 32004 } 32005 }, 32006 32007 /** 32008 * Handle insertion of more consecutive style lines for when one or more 32009 * newlines gets added to the text. Since current style needs to be shifted 32010 * first we shift the current style of the number lines needed, then we add 32011 * new lines from the last to the first. 32012 * @param {Number} lineIndex Index of a line 32013 * @param {Number} charIndex Index of a char 32014 * @param {Number} qty number of lines to add 32015 * @param {Array} copiedStyle Array of objects styles 32016 */ 32017 insertNewlineStyleObject: function ( 32018 lineIndex, 32019 charIndex, 32020 qty, 32021 copiedStyle, 32022 ) { 32023 var currentCharStyle, 32024 newLineStyles = {}, 32025 somethingAdded = false, 32026 isEndOfLine = 32027 this._unwrappedTextLines[lineIndex].length === charIndex; 32028 32029 qty || (qty = 1); 32030 this.shiftLineStyles(lineIndex, qty); 32031 if (this.styles[lineIndex]) { 32032 currentCharStyle = 32033 this.styles[lineIndex][charIndex === 0 ? charIndex : charIndex - 1]; 32034 } 32035 // we clone styles of all chars 32036 // after cursor onto the current line 32037 for (var index in this.styles[lineIndex]) { 32038 var numIndex = parseInt(index, 10); 32039 if (numIndex >= charIndex) { 32040 somethingAdded = true; 32041 newLineStyles[numIndex - charIndex] = this.styles[lineIndex][index]; 32042 // remove lines from the previous line since they're on a new line now 32043 if (!(isEndOfLine && charIndex === 0)) { 32044 delete this.styles[lineIndex][index]; 32045 } 32046 } 32047 } 32048 var styleCarriedOver = false; 32049 if (somethingAdded && !isEndOfLine) { 32050 // if is end of line, the extra style we copied 32051 // is probably not something we want 32052 this.styles[lineIndex + qty] = newLineStyles; 32053 styleCarriedOver = true; 32054 } 32055 if (styleCarriedOver) { 32056 // skip the last line of since we already prepared it. 32057 qty--; 32058 } 32059 // for the all the lines or all the other lines 32060 // we clone current char style onto the next (otherwise empty) line 32061 while (qty > 0) { 32062 if (copiedStyle && copiedStyle[qty - 1]) { 32063 this.styles[lineIndex + qty] = { 0: clone(copiedStyle[qty - 1]) }; 32064 } else if (currentCharStyle) { 32065 this.styles[lineIndex + qty] = { 0: clone(currentCharStyle) }; 32066 } else { 32067 delete this.styles[lineIndex + qty]; 32068 } 32069 qty--; 32070 } 32071 this._forceClearCache = true; 32072 }, 32073 32074 /** 32075 * Inserts style object for a given line/char index 32076 * @param {Number} lineIndex Index of a line 32077 * @param {Number} charIndex Index of a char 32078 * @param {Number} quantity number Style object to insert, if given 32079 * @param {Array} copiedStyle array of style objects 32080 */ 32081 insertCharStyleObject: function ( 32082 lineIndex, 32083 charIndex, 32084 quantity, 32085 copiedStyle, 32086 ) { 32087 if (!this.styles) { 32088 this.styles = {}; 32089 } 32090 var currentLineStyles = this.styles[lineIndex], 32091 currentLineStylesCloned = currentLineStyles 32092 ? clone(currentLineStyles) 32093 : {}; 32094 32095 quantity || (quantity = 1); 32096 // shift all char styles by quantity forward 32097 // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4 32098 for (var index in currentLineStylesCloned) { 32099 var numericIndex = parseInt(index, 10); 32100 if (numericIndex >= charIndex) { 32101 currentLineStyles[numericIndex + quantity] = 32102 currentLineStylesCloned[numericIndex]; 32103 // only delete the style if there was nothing moved there 32104 if (!currentLineStylesCloned[numericIndex - quantity]) { 32105 delete currentLineStyles[numericIndex]; 32106 } 32107 } 32108 } 32109 this._forceClearCache = true; 32110 if (copiedStyle) { 32111 while (quantity--) { 32112 if (!Object.keys(copiedStyle[quantity]).length) { 32113 continue; 32114 } 32115 if (!this.styles[lineIndex]) { 32116 this.styles[lineIndex] = {}; 32117 } 32118 this.styles[lineIndex][charIndex + quantity] = clone( 32119 copiedStyle[quantity], 32120 ); 32121 } 32122 return; 32123 } 32124 if (!currentLineStyles) { 32125 return; 32126 } 32127 var newStyle = currentLineStyles[charIndex ? charIndex - 1 : 1]; 32128 while (newStyle && quantity--) { 32129 this.styles[lineIndex][charIndex + quantity] = clone(newStyle); 32130 } 32131 }, 32132 32133 /** 32134 * Inserts style object(s) 32135 * @param {Array} insertedText Characters at the location where style is inserted 32136 * @param {Number} start cursor index for inserting style 32137 * @param {Array} [copiedStyle] array of style objects to insert. 32138 */ 32139 insertNewStyleBlock: function (insertedText, start, copiedStyle) { 32140 var cursorLoc = this.get2DCursorLocation(start, true), 32141 addedLines = [0], 32142 linesLength = 0; 32143 // get an array of how many char per lines are being added. 32144 for (var i = 0; i < insertedText.length; i++) { 32145 if (insertedText[i] === "\n") { 32146 linesLength++; 32147 addedLines[linesLength] = 0; 32148 } else { 32149 addedLines[linesLength]++; 32150 } 32151 } 32152 // for the first line copy the style from the current char position. 32153 if (addedLines[0] > 0) { 32154 this.insertCharStyleObject( 32155 cursorLoc.lineIndex, 32156 cursorLoc.charIndex, 32157 addedLines[0], 32158 copiedStyle, 32159 ); 32160 copiedStyle = copiedStyle && copiedStyle.slice(addedLines[0] + 1); 32161 } 32162 linesLength && 32163 this.insertNewlineStyleObject( 32164 cursorLoc.lineIndex, 32165 cursorLoc.charIndex + addedLines[0], 32166 linesLength,
32167 ); 32168 for (var i = 1; i < linesLength; i++) { 32169 if (addedLines[i] > 0) { 32170 this.insertCharStyleObject( 32171 cursorLoc.lineIndex + i, 32172 0, 32173 addedLines[i], 32174 copiedStyle, 32175 ); 32176 } else if (copiedStyle) { 32177 // this test is required in order to close #6841 32178 // when a pasted buffer begins with a newline then 32179 // this.styles[cursorLoc.lineIndex + i] and copiedStyle[0] 32180 // may be undefined for some reason 32181 if (this.styles[cursorLoc.lineIndex + i] && copiedStyle[0]) { 32182 this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0]; 32183 } 32184 } 32185 copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1); 32186 } 32187 // we use i outside the loop to get it like linesLength 32188 if (addedLines[i] > 0) { 32189 this.insertCharStyleObject( 32190 cursorLoc.lineIndex + i, 32191 0, 32192 addedLines[i], 32193 copiedStyle, 32194 ); 32195 } 32196 }, 32197 32198 /** 32199 * Set the selectionStart and selectionEnd according to the new position of cursor 32200 * mimic the key - mouse navigation when shift is pressed. 32201 */ 32202 setSelectionStartEndWithShift: function (start, end, newSelection) { 32203 if (newSelection <= start) { 32204 if (end === start) { 32205 this._selectionDirection = "left"; 32206 } else if (this._selectionDirection === "right") { 32207 this._selectionDirection = "left"; 32208 this.selectionEnd = start; 32209 } 32210 this.selectionStart = newSelection; 32211 } else if (newSelection > start && newSelection < end) { 32212 if (this._selectionDirection === "right") { 32213 this.selectionEnd = newSelection; 32214 } else { 32215 this.selectionStart = newSelection; 32216 } 32217 } else { 32218 // newSelection is > selection start and end 32219 if (end === start) { 32220 this._selectionDirection = "right"; 32221 } else if (this._selectionDirection === "left") { 32222 this._selectionDirection = "right"; 32223 this.selectionStart = end; 32224 } 32225 this.selectionEnd = newSelection; 32226 } 32227 }, 32228 32229 setSelectionInBoundaries: function () { 32230 var length = this.text.length; 32231 if (this.selectionStart > length) { 32232 this.selectionStart = length; 32233 } else if (this.selectionStart < 0) { 32234 this.selectionStart = 0; 32235 } 32236 if (this.selectionEnd > length) { 32237 this.selectionEnd = length; 32238 } else if (this.selectionEnd < 0) { 32239 this.selectionEnd = 0; 32240 } 32241 }, 32242 }, 32243 ); 32244})(); 32245 32246fabric.util.object.extend( 32247 fabric.IText.prototype, 32248 /** @lends fabric.IText.prototype */ { 32249 /** 32250 * Initializes "dbclick" event handler 32251 */ 32252 initDoubleClickSimulation: function () { 32253 // for double click 32254 this.__lastClickTime = +new Date(); 32255 32256 // for triple click 32257 this.__lastLastClickTime = +new Date(); 32258 32259 this.__lastPointer = {}; 32260 32261 this.on("mousedown", this.onMouseDown); 32262 }, 32263 32264 /** 32265 * Default event handler to simulate triple click 32266 * @private 32267 */ 32268 onMouseDown: function (options) { 32269 if (!this.canvas) { 32270 return; 32271 } 32272 this.__newClickTime = +new Date(); 32273 var newPointer = options.pointer; 32274 if (this.isTripleClick(newPointer)) { 32275 this.fire("tripleclick", options); 32276 this._stopEvent(options.e); 32277 } 32278 this.__lastLastClickTime = this.__lastClickTime; 32279 this.__lastClickTime = this.__newClickTime; 32280 this.__lastPointer = newPointer; 32281 this.__lastIsEditing = this.isEditing; 32282 this.__lastSelected = this.selected; 32283 }, 32284 32285 isTripleClick: function (newPointer) { 32286 return ( 32287 this.__newClickTime - this.__lastClickTime < 500 && 32288 this.__lastClickTime - this.__lastLastClickTime < 500 && 32289 this.__lastPointer.x === newPointer.x && 32290 this.__lastPointer.y === newPointer.y 32291 ); 32292 }, 32293 32294 /** 32295 * @private 32296 */ 32297 _stopEvent: function (e) { 32298 e.preventDefault && e.preventDefault(); 32299 e.stopPropagation && e.stopPropagation(); 32300 }, 32301 32302 /** 32303 * Initializes event handlers related to cursor or selection 32304 */
vendor: 4,107 bytes, lines 32305-32444
32305 initCursorSelectionHandlers: function () { 32306 this.initMousedownHandler(); 32307 this.initMouseupHandler(); 32308 this.initClicks(); 32309 }, 32310 32311 /** 32312 * Default handler for double click, select a word 32313 */ 32314 doubleClickHandler: function (options) { 32315 if (!this.isEditing) { 32316 return; 32317 } 32318 this.selectWord(this.getSelectionStartFromPointer(options.e)); 32319 }, 32320 32321 /** 32322 * Default handler for triple click, select a line 32323 */ 32324 tripleClickHandler: function (options) { 32325 if (!this.isEditing) { 32326 return; 32327 } 32328 this.selectLine(this.getSelectionStartFromPointer(options.e)); 32329 }, 32330 32331 /** 32332 * Initializes double and triple click event handlers 32333 */ 32334 initClicks: function () { 32335 this.on("mousedblclick", this.doubleClickHandler); 32336 this.on("tripleclick", this.tripleClickHandler); 32337 }, 32338 32339 /** 32340 * Default event handler for the basic functionalities needed on _mouseDown 32341 * can be overridden to do something different. 32342 * Scope of this implementation is: find the click position, set selectionStart 32343 * find selectionEnd, initialize the drawing of either cursor or selection area 32344 * initializing a mousedDown on a text area will cancel fabricjs knowledge of 32345 * current compositionMode. It will be set to false. 32346 */ 32347 _mouseDownHandler: function (options) { 32348 if ( 32349 !this.canvas || 32350 !this.editable || 32351 (options.e.button && options.e.button !== 1) 32352 ) { 32353 return; 32354 } 32355 32356 this.__isMousedown = true; 32357 32358 if (this.selected) { 32359 this.inCompositionMode = false; 32360 this.setCursorByClick(options.e); 32361 } 32362 32363 if (this.isEditing) { 32364 this.__selectionStartOnMouseDown = this.selectionStart; 32365 if (this.selectionStart === this.selectionEnd) { 32366 this.abortCursorAnimation(); 32367 } 32368 this.renderCursorOrSelection(); 32369 } 32370 }, 32371 32372 /** 32373 * Default event handler for the basic functionalities needed on mousedown:before 32374 * can be overridden to do something different. 32375 * Scope of this implementation is: verify the object is already selected when mousing down 32376 */ 32377 _mouseDownHandlerBefore: function (options) { 32378 if ( 32379 !this.canvas || 32380 !this.editable || 32381 (options.e.button && options.e.button !== 1) 32382 ) { 32383 return; 32384 } 32385 // we want to avoid that an object that was selected and then becomes unselectable, 32386 // may trigger editing mode in some way. 32387 this.selected = this === this.canvas._activeObject; 32388 }, 32389 32390 /** 32391 * Initializes "mousedown" event handler 32392 */ 32393 initMousedownHandler: function () { 32394 this.on("mousedown", this._mouseDownHandler); 32395 this.on("mousedown:before", this._mouseDownHandlerBefore); 32396 }, 32397 32398 /** 32399 * Initializes "mouseup" event handler 32400 */ 32401 initMouseupHandler: function () { 32402 this.on("mouseup", this.mouseUpHandler); 32403 }, 32404 32405 /** 32406 * standard handler for mouse up, overridable 32407 * @private 32408 */ 32409 mouseUpHandler: function (options) { 32410 this.__isMousedown = false; 32411 if ( 32412 !this.editable || 32413 this.group || 32414 (options.transform && options.transform.actionPerformed) || 32415 (options.e.button && options.e.button !== 1) 32416 ) { 32417 return; 32418 } 32419 32420 if (this.canvas) { 32421 var currentActive = this.canvas._activeObject; 32422 if (currentActive && currentActive !== this) { 32423 // avoid running this logic when there is an active object 32424 // this because is possible with shift click and fast clicks, 32425 // to rapidly deselect and reselect this object and trigger an enterEdit 32426 return; 32427 } 32428 } 32429 32430 if (this.__lastSelected && !this.__corner) { 32431 this.selected = false; 32432 this.__lastSelected = false; 32433 this.enterEditing(options.e); 32434 if (this.selectionStart === this.selectionEnd) { 32435 this.initDelayedCursor(true); 32436 } else { 32437 this.renderCursorOrSelection(); 32438 } 32439 } else { 32440 this.selected = true; 32441 } 32442 }, 32443 32444 /**
32445 * Changes cursor location in a text depending on passed pointer (x/y) object 32446 * @param {Event} e Event object 32447 */ 32448 setCursorByClick: function (e) { 32449 var newSelection = this.getSelectionStartFromPointer(e), 32450 start = this.selectionStart, 32451 end = this.selectionEnd; 32452 if (e.shiftKey) { 32453 this.setSelectionStartEndWithShift(start, end, newSelection); 32454 } else { 32455 this.selectionStart = newSelection; 32456 this.selectionEnd = newSelection; 32457 } 32458 if (this.isEditing) { 32459 this._fireSelectionChanged(); 32460 this._updateTextarea(); 32461 } 32462 }, 32463 32464 /** 32465 * Returns index of a character corresponding to where an object was clicked 32466 * @param {Event} e Event object 32467 * @return {Number} Index of a character 32468 */ 32469 getSelectionStartFromPointer: function (e) { 32470 var mouseOffset = this.getLocalPointer(e), 32471 prevWidth = 0, 32472 width = 0, 32473 height = 0, 32474 charIndex = 0, 32475 lineIndex = 0, 32476 lineLeftOffset, 32477 line; 32478 for (var i = 0, len = this._textLines.length; i < len; i++) { 32479 if (height <= mouseOffset.y) { 32480 height += this.getHeightOfLine(i) * this.scaleY; 32481 lineIndex = i; 32482 if (i > 0) { 32483 charIndex += 32484 this._textLines[i - 1].length + this.missingNewlineOffset(i - 1); 32485 } 32486 } else { 32487 break; 32488 } 32489 } 32490 lineLeftOffset = this._getLineLeftOffset(lineIndex); 32491 width = lineLeftOffset * this.scaleX; 32492 line = this._textLines[lineIndex]; 32493 // handling of RTL: in order to get things work correctly, 32494 // we assume RTL writing is mirrored compared to LTR writing. 32495 // so in position detection we mirror the X offset, and when is time 32496 // of rendering it, we mirror it again. 32497 if (this.direction === "rtl") { 32498 mouseOffset.x = this.width * this.scaleX - mouseOffset.x + width; 32499 } 32500 for (var j = 0, jlen = line.length; j < jlen; j++) { 32501 prevWidth = width; 32502 // i removed something about flipX here, check. 32503 width += this.__charBounds[lineIndex][j].kernedWidth * this.scaleX; 32504 if (width <= mouseOffset.x) { 32505 charIndex++; 32506 } else { 32507 break; 32508 } 32509 } 32510 return this._getNewSelectionStartFromOffset( 32511 mouseOffset, 32512 prevWidth, 32513 width, 32514 charIndex, 32515 jlen, 32516 ); 32517 }, 32518 32519 /** 32520 * @private 32521 */ 32522 _getNewSelectionStartFromOffset: function ( 32523 mouseOffset, 32524 prevWidth, 32525 width, 32526 index, 32527 jlen, 32528 ) { 32529 // we need Math.abs because when width is after the last char, the offset is given as 1, while is 0 32530 var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, 32531 distanceBtwNextCharAndCursor = width - mouseOffset.x, 32532 offset = 32533 distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor || 32534 distanceBtwNextCharAndCursor < 0 32535 ? 0 32536 : 1, 32537 newSelectionStart = index + offset; 32538 // if object is horizontally flipped, mirror cursor location from the end 32539 if (this.flipX) { 32540 newSelectionStart = jlen - newSelectionStart; 32541 } 32542 32543 if (newSelectionStart > this._text.length) { 32544 newSelectionStart = this._text.length; 32545 } 32546 32547 return newSelectionStart; 32548 }, 32549 }, 32550); 32551 32552fabric.util.object.extend( 32553 fabric.IText.prototype, 32554 /** @lends fabric.IText.prototype */ { 32555 /** 32556 * Initializes hidden textarea (needed to bring up keyboard in iOS) 32557 */ 32558 initHiddenTextarea: function () { 32559 this.hiddenTextarea = fabric.document.createElement("textarea"); 32560 this.hiddenTextarea.setAttribute("autocapitalize", "off"); 32561 this.hiddenTextarea.setAttribute("autocorrect", "off"); 32562 this.hiddenTextarea.setAttribute("autocomplete", "off"); 32563 this.hiddenTextarea.setAttribute("spellcheck", "false"); 32564 this.hiddenTextarea.setAttribute("data-fabric-hiddentextarea", ""); 32565 this.hiddenTextarea.setAttribute("wrap", "off"); 32566 var style = this._calcTextareaPosition(); 32567 // line-height: 1px; was removed from the style to fix this: 32568 // https://bugs.chromium.org/p/chromium/issues/detail?id=870966 32569 this.hiddenTextarea.style.cssText = 32570 "position: absolute; top: " + 32571 style.top + 32572 ";
32572 left: " + 32573 style.left + 32574 "; z-index: -999; opacity: 0; width: 1px; height: 1px; font-size: 1px;" + 32575 " padding-top: " + 32576 style.fontSize + 32577 ";"; 32578 32579 if (this.hiddenTextareaContainer) { 32580 this.hiddenTextareaContainer.appendChild(this.hiddenTextarea); 32581 } else { 32582 fabric.document.body.appendChild(this.hiddenTextarea); 32583 } 32584 32585 fabric.util.addListener( 32586 this.hiddenTextarea, 32587 "keydown", 32588 this.onKeyDown.bind(this), 32589 ); 32590 fabric.util.addListener( 32591 this.hiddenTextarea, 32592 "keyup", 32593 this.onKeyUp.bind(this), 32594 ); 32595 fabric.util.addListener( 32596 this.hiddenTextarea, 32597 "input", 32598 this.onInput.bind(this), 32599 ); 32600 fabric.util.addListener( 32601 this.hiddenTextarea, 32602 "copy", 32603 this.copy.bind(this), 32604 ); 32605 fabric.util.addListener(this.hiddenTextarea, "cut", this.copy.bind(this)); 32606 fabric.util.addListener( 32607 this.hiddenTextarea, 32608 "paste", 32609 this.paste.bind(this), 32610 ); 32611 fabric.util.addListener( 32612 this.hiddenTextarea, 32613 "compositionstart", 32614 this.onCompositionStart.bind(this), 32615 ); 32616 fabric.util.addListener( 32617 this.hiddenTextarea, 32618 "compositionupdate", 32619 this.onCompositionUpdate.bind(this), 32620 ); 32621 fabric.util.addListener( 32622 this.hiddenTextarea, 32623 "compositionend", 32624 this.onCompositionEnd.bind(this), 32625 ); 32626 32627 if (!this._clickHandlerInitialized && this.canvas) { 32628 fabric.util.addListener( 32629 this.canvas.upperCanvasEl, 32630 "click", 32631 this.onClick.bind(this), 32632 ); 32633 this._clickHandlerInitialized = true; 32634 } 32635 }, 32636 32637 /** 32638 * For functionalities on keyDown 32639 * Map a special key to a function of the instance/prototype 32640 * If you need different behaviour for ESC or TAB or arrows, you have to change 32641 * this map setting the name of a function that you build on the fabric.Itext or 32642 * your prototype. 32643 * the map change will affect all Instances unless you need for only some text Instances 32644 * in that case you have to clone this object and assign your Instance. 32645 * this.keysMap = fabric.util.object.clone(this.keysMap); 32646 * The function must be in fabric.Itext.prototype.myFunction And will receive event as args[0] 32647 */ 32648 keysMap: { 32649 9: "exitEditing", 32650 27: "exitEditing", 32651 33: "moveCursorUp", 32652 34: "moveCursorDown", 32653 35: "moveCursorRight", 32654 36: "moveCursorLeft", 32655 37: "moveCursorLeft", 32656 38: "moveCursorUp", 32657 39: "moveCursorRight", 32658 40: "moveCursorDown", 32659 }, 32660 32661 keysMapRtl: { 32662 9: "exitEditing", 32663 27: "exitEditing", 32664 33: "moveCursorUp", 32665 34: "moveCursorDown", 32666 35: "moveCursorLeft", 32667 36: "moveCursorRight", 32668 37: "moveCursorRight", 32669 38: "moveCursorUp", 32670 39: "moveCursorLeft", 32671 40: "moveCursorDown", 32672 }, 32673 32674 /** 32675 * For functionalities on keyUp + ctrl || cmd 32676 */ 32677 ctrlKeysMapUp: { 32678 67: "copy", 32679 88: "cut", 32680 }, 32681 32682 /** 32683 * For functionalities on keyDown + ctrl || cmd 32684 */ 32685 ctrlKeysMapDown: { 32686 65: "selectAll", 32687 }, 32688 32689 onClick: function () { 32690 // No need to trigger click event here, focus is enough to have the keyboard appear on Android 32691 this.hiddenTextarea && this.hiddenTextarea.focus(); 32692 }, 32693 32694 /** 32695 * Handles keydown event 32696 * only used for arrows and combination of modifier keys. 32697 * @param {Event} e Event object 32698 */ 32699 onKeyDown: function (e) { 32700 if (!this.isEditing) { 32701 return; 32702 } 32703 var keyMap = this.direction === "rtl" ? this.keysMapRtl : this.keysMap; 32704 if (e.keyCode in keyMap) { 32705 this[keyMap[e.keyCode]](e); 32706 } else if ( 32707 e.keyCode in this.ctrlKeysMapDown && 32708 (e.ctrlKey || e.metaKey) 32709 ) { 32710 this[this.ctrlKeysMapDown[e.keyCode]](e); 32711 } else { 32712 return; 32713 } 32714 e.stopImmediatePropagation(); 32715 e.preventDefault(); 32716 if (e.keyCode >= 33 && e.keyCode <= 40) { 32717 // if i press an arrow key just update selection 32718 this.inCompositionMode = false;
32719 this.clearContextTop(); 32720 this.renderCursorOrSelection(); 32721 } else { 32722 this.canvas && this.canvas.requestRenderAll(); 32723 } 32724 }, 32725 32726 /** 32727 * Handles keyup event 32728 * We handle KeyUp because ie11 and edge have difficulties copy/pasting 32729 * if a copy/cut event fired, keyup is dismissed 32730 * @param {Event} e Event object 32731 */ 32732 onKeyUp: function (e) { 32733 if (!this.isEditing || this._copyDone || this.inCompositionMode) { 32734 this._copyDone = false; 32735 return; 32736 } 32737 if (e.keyCode in this.ctrlKeysMapUp && (e.ctrlKey || e.metaKey)) { 32738 this[this.ctrlKeysMapUp[e.keyCode]](e); 32739 } else { 32740 return; 32741 } 32742 e.stopImmediatePropagation(); 32743 e.preventDefault(); 32744 this.canvas && this.canvas.requestRenderAll(); 32745 }, 32746 32747 /** 32748 * Handles onInput event 32749 * @param {Event} e Event object 32750 */ 32751 onInput: function (e) { 32752 var fromPaste = this.fromPaste; 32753 this.fromPaste = false; 32754 e && e.stopPropagation(); 32755 if (!this.isEditing) { 32756 return; 32757 } 32758 // decisions about style changes. 32759 var nextText = this._splitTextIntoLines( 32760 this.hiddenTextarea.value, 32761 ).graphemeText, 32762 charCount = this._text.length, 32763 nextCharCount = nextText.length, 32764 removedText, 32765 insertedText, 32766 charDiff = nextCharCount - charCount, 32767 selectionStart = this.selectionStart, 32768 selectionEnd = this.selectionEnd, 32769 selection = selectionStart !== selectionEnd, 32770 copiedStyle, 32771 removeFrom, 32772 removeTo; 32773 if (this.hiddenTextarea.value === "") { 32774 this.styles = {}; 32775 this.updateFromTextArea(); 32776 this.fire("changed"); 32777 if (this.canvas) { 32778 this.canvas.fire("text:changed", { target: this }); 32779 this.canvas.requestRenderAll(); 32780 } 32781 return; 32782 } 32783 32784 var textareaSelection = this.fromStringToGraphemeSelection( 32785 this.hiddenTextarea.selectionStart, 32786 this.hiddenTextarea.selectionEnd, 32787 this.hiddenTextarea.value, 32788 ); 32789 var backDelete = selectionStart > textareaSelection.selectionStart; 32790 32791 if (selection) { 32792 removedText = this._text.slice(selectionStart, selectionEnd); 32793 charDiff += selectionEnd - selectionStart; 32794 } else if (nextCharCount < charCount) { 32795 if (backDelete) { 32796 removedText = this._text.slice(selectionEnd + charDiff, selectionEnd); 32797 } else { 32798 removedText = this._text.slice( 32799 selectionStart, 32800 selectionStart - charDiff, 32801 ); 32802 } 32803 } 32804 insertedText = nextText.slice( 32805 textareaSelection.selectionEnd - charDiff, 32806 textareaSelection.selectionEnd, 32807 ); 32808 if (removedText && removedText.length) { 32809 if (insertedText.length) { 32810 // let's copy some style before deleting. 32811 // we want to copy the style before the cursor OR the style at the cursor if selection 32812 // is bigger than 0. 32813 copiedStyle = this.getSelectionStyles( 32814 selectionStart, 32815 selectionStart + 1, 32816 false, 32817 ); 32818 // now duplicate the style one for each inserted text. 32819 copiedStyle = insertedText.map(function () { 32820 // this return an array of references, but that is fine since we are 32821 // copying the style later. 32822 return copiedStyle[0]; 32823 }); 32824 } 32825 if (selection) { 32826 removeFrom = selectionStart; 32827 removeTo = selectionEnd; 32828 } else if (backDelete) { 32829 // detect differences between forwardDelete and backDelete 32830 removeFrom = selectionEnd - removedText.length; 32831 removeTo = selectionEnd; 32832 } else { 32833 removeFrom = selectionEnd; 32834 removeTo = selectionEnd + removedText.length; 32835 } 32836 this.removeStyleFromTo(removeFrom, removeTo); 32837 } 32838 if (insertedText.length) { 32839 if ( 32840 fromPaste && 32841 insertedText.join("") === fabric.copiedText && 32842 !fabric.disableStyleCopyPaste 32843 ) { 32844 copiedStyle = fabric.copiedTextStyle; 32845 } 32846 this.insertNewStyleBlock(insertedText, selectionStart, copiedStyle); 32847 } 32848 this.updateFromTextArea(); 32849 this.fire("changed"); 32850 if (this.canvas) { 32851 this.canvas.fire("text:changed", { target: this }); 32852 this.canvas.requestRenderAll(); 32853 } 32854 }, 32855 /** 32856 * Composition start 32857 */ 32858 onCompositionStart: function () { 32859 this.inCompositionMode = true; 32860 }, 32861 32862 /** 32863 * Composition end 32864 */ 32865 onCompositionEnd: function () { 32866 this.inCompositionMode = false;
vendor: 11,958 bytes, lines 32867-33293
32867 }, 32868 32869 // /** 32870 // * Composition update 32871 // */ 32872 onCompositionUpdate: function (e) { 32873 this.compositionStart = e.target.selectionStart; 32874 this.compositionEnd = e.target.selectionEnd; 32875 this.updateTextareaPosition(); 32876 }, 32877 32878 /** 32879 * Copies selected text 32880 * @param {Event} e Event object 32881 */ 32882 copy: function () { 32883 if (this.selectionStart === this.selectionEnd) { 32884 //do not cut-copy if no selection 32885 return; 32886 } 32887 32888 fabric.copiedText = this.getSelectedText(); 32889 if (!fabric.disableStyleCopyPaste) { 32890 fabric.copiedTextStyle = this.getSelectionStyles( 32891 this.selectionStart, 32892 this.selectionEnd, 32893 true, 32894 ); 32895 } else { 32896 fabric.copiedTextStyle = null; 32897 } 32898 this._copyDone = true; 32899 }, 32900 32901 /** 32902 * Pastes text 32903 * @param {Event} e Event object 32904 */ 32905 paste: function () { 32906 this.fromPaste = true; 32907 }, 32908 32909 /** 32910 * @private 32911 * @param {Event} e Event object 32912 * @return {Object} Clipboard data object 32913 */ 32914 _getClipboardData: function (e) { 32915 return (e && e.clipboardData) || fabric.window.clipboardData; 32916 }, 32917 32918 /** 32919 * Finds the width in pixels before the cursor on the same line 32920 * @private 32921 * @param {Number} lineIndex 32922 * @param {Number} charIndex 32923 * @return {Number} widthBeforeCursor width before cursor 32924 */ 32925 _getWidthBeforeCursor: function (lineIndex, charIndex) { 32926 var widthBeforeCursor = this._getLineLeftOffset(lineIndex), 32927 bound; 32928 32929 if (charIndex > 0) { 32930 bound = this.__charBounds[lineIndex][charIndex - 1]; 32931 widthBeforeCursor += bound.left + bound.width; 32932 } 32933 return widthBeforeCursor; 32934 }, 32935 32936 /** 32937 * Gets start offset of a selection 32938 * @param {Event} e Event object 32939 * @param {Boolean} isRight 32940 * @return {Number} 32941 */ 32942 getDownCursorOffset: function (e, isRight) { 32943 var selectionProp = this._getSelectionForOffset(e, isRight), 32944 cursorLocation = this.get2DCursorLocation(selectionProp), 32945 lineIndex = cursorLocation.lineIndex; 32946 // if on last line, down cursor goes to end of line 32947 if ( 32948 lineIndex === this._textLines.length - 1 || 32949 e.metaKey || 32950 e.keyCode === 34 32951 ) { 32952 // move to the end of a text 32953 return this._text.length - selectionProp; 32954 } 32955 var charIndex = cursorLocation.charIndex, 32956 widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), 32957 indexOnOtherLine = this._getIndexOnLine( 32958 lineIndex + 1, 32959 widthBeforeCursor, 32960 ), 32961 textAfterCursor = this._textLines[lineIndex].slice(charIndex); 32962 return ( 32963 textAfterCursor.length + 32964 indexOnOtherLine + 32965 1 + 32966 this.missingNewlineOffset(lineIndex) 32967 ); 32968 }, 32969 32970 /** 32971 * private 32972 * Helps finding if the offset should be counted from Start or End 32973 * @param {Event} e Event object 32974 * @param {Boolean} isRight 32975 * @return {Number} 32976 */ 32977 _getSelectionForOffset: function (e, isRight) { 32978 if (e.shiftKey && this.selectionStart !== this.selectionEnd && isRight) { 32979 return this.selectionEnd; 32980 } else { 32981 return this.selectionStart; 32982 } 32983 }, 32984 32985 /** 32986 * @param {Event} e Event object 32987 * @param {Boolean} isRight 32988 * @return {Number} 32989 */ 32990 getUpCursorOffset: function (e, isRight) { 32991 var selectionProp = this._getSelectionForOffset(e, isRight), 32992 cursorLocation = this.get2DCursorLocation(selectionProp), 32993 lineIndex = cursorLocation.lineIndex; 32994 if (lineIndex === 0 || e.metaKey || e.keyCode === 33) { 32995 // if on first line, up cursor goes to start of line 32996 return -selectionProp; 32997 } 32998 var charIndex = cursorLocation.charIndex, 32999 widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), 33000 indexOnOtherLine = this._getIndexOnLine( 33001 lineIndex - 1, 33002 widthBeforeCursor, 33003 ), 33004 textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex), 33005 missingNewlineOffset = this.missingNewlineOffset(lineIndex - 1); 33006 // return a negative offset 33007 return ( 33008 -this._textLines[lineIndex - 1].length + 33009 indexOnOtherLine - 33010 textBeforeCursor.length + 33011 (1 - missingNewlineOffset) 33012 ); 33013 }, 33014 33015 /** 33016 * for a given width it founds the matching character. 33017 * @private 33018 */ 33019 _getIndexOnLine: function (lineIndex, width) { 33020 var line = this._textLines[lineIndex], 33021 lineLeftOffset = this._getLineLeftOffset(lineIndex), 33022 widthOfCharsOnLine = lineLeftOffset, 33023 indexOnLine = 0, 33024 charWidth, 33025 foundMatch; 33026 33027 for (var j = 0, jlen = line.length; j < jlen; j++) { 33028 charWidth = this.__charBounds[lineIndex][j].width; 33029 widthOfCharsOnLine += charWidth; 33030 if (widthOfCharsOnLine > width) { 33031 foundMatch = true; 33032 var leftEdge = widthOfCharsOnLine - charWidth, 33033 rightEdge = widthOfCharsOnLine, 33034 offsetFromLeftEdge = Math.abs(leftEdge - width), 33035 offsetFromRightEdge = Math.abs(rightEdge - width); 33036 33037 indexOnLine = offsetFromRightEdge < offsetFromLeftEdge ? j : j - 1; 33038 break; 33039 } 33040 } 33041 33042 // reached end 33043 if (!foundMatch) { 33044 indexOnLine = line.length - 1; 33045 } 33046 33047 return indexOnLine; 33048 }, 33049 33050 /** 33051 * Moves cursor down 33052 * @param {Event} e Event object 33053 */ 33054 moveCursorDown: function (e) { 33055 if ( 33056 this.selectionStart >= this._text.length && 33057 this.selectionEnd >= this._text.length 33058 ) { 33059 return; 33060 } 33061 this._moveCursorUpOrDown("Down", e); 33062 }, 33063 33064 /** 33065 * Moves cursor up 33066 * @param {Event} e Event object 33067 */ 33068 moveCursorUp: function (e) { 33069 if (this.selectionStart === 0 && this.selectionEnd === 0) { 33070 return; 33071 } 33072 this._moveCursorUpOrDown("Up", e); 33073 }, 33074 33075 /** 33076 * Moves cursor up or down, fires the events 33077 * @param {String} direction 'Up' or 'Down' 33078 * @param {Event} e Event object 33079 */ 33080 _moveCursorUpOrDown: function (direction, e) { 33081 // getUpCursorOffset 33082 // getDownCursorOffset 33083 var action = "get" + direction + "CursorOffset", 33084 offset = this[action](e, this._selectionDirection === "right"); 33085 if (e.shiftKey) { 33086 this.moveCursorWithShift(offset); 33087 } else { 33088 this.moveCursorWithoutShift(offset); 33089 } 33090 if (offset !== 0) { 33091 this.setSelectionInBoundaries(); 33092 this.abortCursorAnimation(); 33093 this._currentCursorOpacity = 1; 33094 this.initDelayedCursor(); 33095 this._fireSelectionChanged(); 33096 this._updateTextarea(); 33097 } 33098 }, 33099 33100 /** 33101 * Moves cursor with shift 33102 * @param {Number} offset 33103 */ 33104 moveCursorWithShift: function (offset) { 33105 var newSelection = 33106 this._selectionDirection === "left" 33107 ? this.selectionStart + offset 33108 : this.selectionEnd + offset; 33109 this.setSelectionStartEndWithShift( 33110 this.selectionStart, 33111 this.selectionEnd, 33112 newSelection, 33113 ); 33114 return offset !== 0; 33115 }, 33116 33117 /** 33118 * Moves cursor up without shift 33119 * @param {Number} offset 33120 */ 33121 moveCursorWithoutShift: function (offset) { 33122 if (offset < 0) { 33123 this.selectionStart += offset; 33124 this.selectionEnd = this.selectionStart; 33125 } else { 33126 this.selectionEnd += offset; 33127 this.selectionStart = this.selectionEnd; 33128 } 33129 return offset !== 0; 33130 }, 33131 33132 /** 33133 * Moves cursor left 33134 * @param {Event} e Event object 33135 */ 33136 moveCursorLeft: function (e) { 33137 if (this.selectionStart === 0 && this.selectionEnd === 0) { 33138 return; 33139 } 33140 this._moveCursorLeftOrRight("Left", e); 33141 }, 33142 33143 /** 33144 * @private 33145 * @return {Boolean} true if a change happened 33146 */ 33147 _move: function (e, prop, direction) { 33148 var newValue; 33149 if (e.altKey) { 33150 newValue = this["findWordBoundary" + direction](this[prop]); 33151 } else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36) { 33152 newValue = this["findLineBoundary" + direction](this[prop]); 33153 } else { 33154 this[prop] += direction === "Left" ? -1 : 1; 33155 return true; 33156 } 33157 if (typeof newValue !== "undefined" && this[prop] !== newValue) { 33158 this[prop] = newValue; 33159 return true; 33160 } 33161 }, 33162 33163 /** 33164 * @private 33165 */ 33166 _moveLeft: function (e, prop) { 33167 return this._move(e, prop, "Left"); 33168 }, 33169 33170 /** 33171 * @private 33172 */ 33173 _moveRight: function (e, prop) { 33174 return this._move(e, prop, "Right"); 33175 }, 33176 33177 /** 33178 * Moves cursor left without keeping selection 33179 * @param {Event} e 33180 */ 33181 moveCursorLeftWithoutShift: function (e) { 33182 var change = true; 33183 this._selectionDirection = "left"; 33184 33185 // only move cursor when there is no selection, 33186 // otherwise we discard it, and leave cursor on same place 33187 if ( 33188 this.selectionEnd === this.selectionStart && 33189 this.selectionStart !== 0 33190 ) { 33191 change = this._moveLeft(e, "selectionStart"); 33192 } 33193 this.selectionEnd = this.selectionStart; 33194 return change; 33195 }, 33196 33197 /** 33198 * Moves cursor left while keeping selection 33199 * @param {Event} e 33200 */ 33201 moveCursorLeftWithShift: function (e) { 33202 if ( 33203 this._selectionDirection === "right" && 33204 this.selectionStart !== this.selectionEnd 33205 ) { 33206 return this._moveLeft(e, "selectionEnd"); 33207 } else if (this.selectionStart !== 0) { 33208 this._selectionDirection = "left"; 33209 return this._moveLeft(e, "selectionStart"); 33210 } 33211 }, 33212 33213 /** 33214 * Moves cursor right 33215 * @param {Event} e Event object 33216 */ 33217 moveCursorRight: function (e) { 33218 if ( 33219 this.selectionStart >= this._text.length && 33220 this.selectionEnd >= this._text.length 33221 ) { 33222 return; 33223 } 33224 this._moveCursorLeftOrRight("Right", e); 33225 }, 33226 33227 /** 33228 * Moves cursor right or Left, fires event 33229 * @param {String} direction 'Left', 'Right' 33230 * @param {Event} e Event object 33231 */ 33232 _moveCursorLeftOrRight: function (direction, e) { 33233 var actionName = "moveCursor" + direction + "With"; 33234 this._currentCursorOpacity = 1; 33235 33236 if (e.shiftKey) { 33237 actionName += "Shift"; 33238 } else { 33239 actionName += "outShift"; 33240 } 33241 if (this[actionName](e)) { 33242 this.abortCursorAnimation(); 33243 this.initDelayedCursor(); 33244 this._fireSelectionChanged(); 33245 this._updateTextarea(); 33246 } 33247 }, 33248 33249 /** 33250 * Moves cursor right while keeping selection 33251 * @param {Event} e 33252 */ 33253 moveCursorRightWithShift: function (e) { 33254 if ( 33255 this._selectionDirection === "left" && 33256 this.selectionStart !== this.selectionEnd 33257 ) { 33258 return this._moveRight(e, "selectionStart"); 33259 } else if (this.selectionEnd !== this._text.length) { 33260 this._selectionDirection = "right"; 33261 return this._moveRight(e, "selectionEnd"); 33262 } 33263 }, 33264 33265 /** 33266 * Moves cursor right without keeping selection 33267 * @param {Event} e Event object 33268 */ 33269 moveCursorRightWithoutShift: function (e) { 33270 var changed = true; 33271 this._selectionDirection = "right"; 33272 33273 if (this.selectionStart === this.selectionEnd) { 33274 changed = this._moveRight(e, "selectionStart"); 33275 this.selectionEnd = this.selectionStart; 33276 } else { 33277 this.selectionStart = this.selectionEnd; 33278 } 33279 return changed; 33280 }, 33281 33282 /** 33283 * Removes characters from start/end 33284 * start/end ar per grapheme position in _text array. 33285 * 33286 * @param {Number} start 33287 * @param {Number} end default to start + 1 33288 */ 33289 removeChars: function (start, end) { 33290 if (typeof end === "undefined") { 33291 end = start + 1; 33292 } 33293 this.removeStyleFromTo(start, end);
33294 this._text.splice(start, end - start); 33295 this.text = this._text.join(""); 33296 this.set("dirty", true); 33297 if (this._shouldClearDimensionCache()) { 33298 this.initDimensions(); 33299 this.setCoords(); 33300 } 33301 this._removeExtraneousStyles(); 33302 }, 33303 33304 /** 33305 * insert characters at start position, before start position. 33306 * start equal 1 it means the text get inserted between actual grapheme 0 and 1 33307 * if style array is provided, it must be as the same length of text in graphemes 33308 * if end is provided and is bigger than start, old text is replaced. 33309 * start/end ar per grapheme position in _text array. 33310 * 33311 * @param {String} text text to insert 33312 * @param {Array} style array of style objects 33313 * @param {Number} start 33314 * @param {Number} end default to start + 1 33315 */ 33316 insertChars: function (text, style, start, end) { 33317 if (typeof end === "undefined") { 33318 end = start; 33319 } 33320 if (end > start) { 33321 this.removeStyleFromTo(start, end); 33322 } 33323 var graphemes = fabric.util.string.graphemeSplit(text); 33324 this.insertNewStyleBlock(graphemes, start, style); 33325 this._text = [].concat( 33326 this._text.slice(0, start), 33327 graphemes, 33328 this._text.slice(end), 33329 ); 33330 this.text = this._text.join(""); 33331 this.set("dirty", true); 33332 if (this._shouldClearDimensionCache()) { 33333 this.initDimensions(); 33334 this.setCoords(); 33335 } 33336 this._removeExtraneousStyles(); 33337 }, 33338 }, 33339); 33340 33341/* _TO_SVG_START_ */ 33342(function () { 33343 var toFixed = fabric.util.toFixed, 33344 multipleSpacesRegex = / +/g; 33345 33346 fabric.util.object.extend( 33347 fabric.Text.prototype, 33348 /** @lends fabric.Text.prototype */ { 33349 /** 33350 * Returns SVG representation of an instance 33351 * @param {Function} [reviver] Method for further parsing of svg representation. 33352 * @return {String} svg representation of an instance 33353 */ 33354 _toSVG: function () { 33355 var offsets = this._getSVGLeftTopOffsets(), 33356 textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); 33357 return this._wrapSVGTextAndBg(textAndBg); 33358 }, 33359 33360 /** 33361 * Returns svg representation of an instance 33362 * @param {Function} [reviver] Method for further parsing of svg representation. 33363 * @return {String} svg representation of an instance 33364 */ 33365 toSVG: function (reviver) { 33366 return this._createBaseSVGMarkup(this._toSVG(), { 33367 reviver: reviver, 33368 noStyle: true, 33369 withShadow: true, 33370 }); 33371 }, 33372 33373 /** 33374 * @private 33375 */ 33376 _getSVGLeftTopOffsets: function () { 33377 return { 33378 textLeft: -this.width / 2, 33379 textTop: -this.height / 2, 33380 lineTop: this.getHeightOfLine(0), 33381 }; 33382 }, 33383 33384 /** 33385 * @private 33386 */ 33387 _wrapSVGTextAndBg: function (textAndBg) { 33388 var noShadow = true, 33389 textDecoration = this.getSvgTextDecoration(this); 33390 return [ 33391 textAndBg.textBgRects.join(""), 33392 '\t\t<text xml:space="preserve" ', 33393 this.fontFamily 33394 ? 'font-family="' + this.fontFamily.replace(/"/g, "'") + '" ' 33395 : "", 33396 this.fontSize ? 'font-size="' + this.fontSize + '" ' : "", 33397 this.fontStyle ? 'font-style="' + this.fontStyle + '" ' : "", 33398 this.fontWeight ? 'font-weight="' + this.fontWeight + '" ' : "", 33399 textDecoration ? 'text-decoration="' + textDecoration + '" ' : "", 33400 'style="',
33401 this.getSvgStyles(noShadow), 33402 '"', 33403 this.addPaintOrder(), 33404 " >", 33405 textAndBg.textSpans.join(""), 33406 "</text>\n", 33407 ]; 33408 }, 33409 33410 /** 33411 * @private 33412 * @param {Number} textTopOffset Text top offset 33413 * @param {Number} textLeftOffset Text left offset 33414 * @return {Object} 33415 */ 33416 _getSVGTextAndBg: function (textTopOffset, textLeftOffset) { 33417 var textSpans = [], 33418 textBgRects = [], 33419 height = textTopOffset, 33420 lineOffset; 33421 // bounding-box background 33422 this._setSVGBg(textBgRects); 33423 33424 // text and text-background 33425 for (var i = 0, len = this._textLines.length; i < len; i++) { 33426 lineOffset = this._getLineLeftOffset(i); 33427 if ( 33428 this.textBackgroundColor || 33429 this.styleHas("textBackgroundColor", i) 33430 ) { 33431 this._setSVGTextLineBg( 33432 textBgRects, 33433 i, 33434 textLeftOffset + lineOffset, 33435 height, 33436 ); 33437 } 33438 this._setSVGTextLineText( 33439 textSpans, 33440 i, 33441 textLeftOffset + lineOffset, 33442 height, 33443 ); 33444 height += this.getHeightOfLine(i); 33445 } 33446 33447 return { 33448 textSpans: textSpans, 33449 textBgRects: textBgRects, 33450 }; 33451 }, 33452 33453 /** 33454 * @private 33455 */ 33456 _createTextCharSpan: function (_char, styleDecl, left, top) { 33457 var shouldUseWhitespace = 33458 _char !== _char.trim() || _char.match(multipleSpacesRegex), 33459 styleProps = this.getSvgSpanStyles(styleDecl, shouldUseWhitespace), 33460 fillStyles = styleProps ? 'style="' + styleProps + '"' : "", 33461 dy = styleDecl.deltaY, 33462 dySpan = "", 33463 NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; 33464 if (dy) { 33465 dySpan = ' dy="' + toFixed(dy, NUM_FRACTION_DIGITS) + '" '; 33466 } 33467 return [ 33468 '<tspan x="', 33469 toFixed(left, NUM_FRACTION_DIGITS), 33470 '" y="', 33471 toFixed(top, NUM_FRACTION_DIGITS), 33472 '" ', 33473 dySpan, 33474 fillStyles, 33475 ">", 33476 fabric.util.string.escapeXml(_char), 33477 "</tspan>", 33478 ].join(""); 33479 }, 33480 33481 _setSVGTextLineText: function ( 33482 textSpans, 33483 lineIndex, 33484 textLeftOffset, 33485 textTopOffset, 33486 ) { 33487 // set proper line offset 33488 var lineHeight = this.getHeightOfLine(lineIndex), 33489 isJustify = this.textAlign.indexOf("justify") !== -1, 33490 actualStyle, 33491 nextStyle, 33492 charsToRender = "", 33493 charBox, 33494 style, 33495 boxWidth = 0, 33496 line = this._textLines[lineIndex], 33497 timeToRender; 33498 33499 textTopOffset += 33500 (lineHeight * (1 - this._fontSizeFraction)) / this.lineHeight; 33501 for (var i = 0, len = line.length - 1; i <= len; i++) { 33502 timeToRender = i === len || this.charSpacing; 33503 charsToRender += line[i]; 33504 charBox = this.__charBounds[lineIndex][i]; 33505 if (boxWidth === 0) { 33506 textLeftOffset += charBox.kernedWidth - charBox.width; 33507 boxWidth += charBox.width; 33508 } else { 33509 boxWidth += charBox.kernedWidth; 33510 } 33511 if (isJustify && !timeToRender) { 33512 if (this._reSpaceAndTab.test(line[i])) { 33513 timeToRender = true; 33514 } 33515 } 33516 if (!timeToRender) { 33517 // if we have charSpacing, we render char by char 33518 actualStyle = 33519 actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); 33520 nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); 33521 timeToRender = fabric.util.hasStyleChanged( 33522 actualStyle, 33523 nextStyle, 33524 true, 33525 ); 33526 } 33527 if (timeToRender) { 33528 style = this._getStyleDeclaration(lineIndex, i) || {}; 33529 textSpans.push( 33530 this._createTextCharSpan( 33531 charsToRender, 33532 style, 33533 textLeftOffset, 33534 textTopOffset, 33535 ), 33536 ); 33537 charsToRender = ""; 33538 actualStyle = nextStyle; 33539 textLeftOffset += boxWidth; 33540 boxWidth = 0; 33541 } 33542 } 33543 }, 33544 33545 _pushTextBgRect: function (textBgRects, color, left, top, width, height) { 33546 var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; 33547 textBgRects.push( 33548 "\t\t<rect ", 33549 this._getFillAttributes(color), 33550 ' x="', 33551 toFixed(left, NUM_FRACTION_DIGITS), 33552 '" y="', 33553 toFixed(top, NUM_FRACTION_DIGITS), 33554 '" width="', 33555 toFixed(width, NUM_FRACTION_DIGITS), 33556 '" height="', 33557 toFixed(height, NUM_FRACTION_DIGITS), 33558 '"></rect>\n', 33559 ); 33560 }, 33561 33562 _setSVGTextLineBg: function (textBgRects, i, leftOffset, textTopOffset) { 33563 var line = this._textLines[i], 33564 heightOfLine = this.getHeightOfLine(i) / this.lineHeight, 33565 boxWidth = 0, 33566 boxStart = 0, 33567 charBox, 33568 currentColor, 33569 lastColor = this.getValueOfPropertyAt(i, 0, "textBackgroun
33569dColor"); 33570 for (var j = 0, jlen = line.length; j < jlen; j++) { 33571 charBox = this.__charBounds[i][j]; 33572 currentColor = this.getValueOfPropertyAt(i, j, "textBackgroundColor"); 33573 if (currentColor !== lastColor) { 33574 lastColor && 33575 this._pushTextBgRect( 33576 textBgRects, 33577 lastColor, 33578 leftOffset + boxStart, 33579 textTopOffset, 33580 boxWidth, 33581 heightOfLine, 33582 ); 33583 boxStart = charBox.left; 33584 boxWidth = charBox.width; 33585 lastColor = currentColor; 33586 } else { 33587 boxWidth += charBox.kernedWidth; 33588 } 33589 } 33590 currentColor && 33591 this._pushTextBgRect( 33592 textBgRects, 33593 currentColor, 33594 leftOffset + boxStart, 33595 textTopOffset, 33596 boxWidth, 33597 heightOfLine, 33598 ); 33599 }, 33600 33601 /** 33602 * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values 33603 * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 33604 * 33605 * @private 33606 * @param {*} value 33607 * @return {String} 33608 */ 33609 _getFillAttributes: function (value) { 33610 var fillColor = 33611 value && typeof value === "string" ? new fabric.Color(value) : ""; 33612 if ( 33613 !fillColor || 33614 !fillColor.getSource() || 33615 fillColor.getAlpha() === 1 33616 ) { 33617 return 'fill="' + value + '"'; 33618 } 33619 return ( 33620 'opacity="' + 33621 fillColor.getAlpha() + 33622 '" fill="' + 33623 fillColor.setAlpha(1).toRgb() + 33624 '"' 33625 ); 33626 }, 33627 33628 /** 33629 * @private 33630 */ 33631 _getSVGLineTopOffset: function (lineIndex) { 33632 var lineTopOffset = 0, 33633 lastHeight = 0; 33634 for (var j = 0; j < lineIndex; j++) { 33635 lineTopOffset += this.getHeightOfLine(j); 33636 } 33637 lastHeight = this.getHeightOfLine(j); 33638 return { 33639 lineTop: lineTopOffset, 33640 offset: 33641 ((this._fontSizeMult - this._fontSizeFraction) * lastHeight) / 33642 (this.lineHeight * this._fontSizeMult), 33643 }; 33644 }, 33645 33646 /** 33647 * Returns styles-string for svg-export 33648 * @param {Boolean} skipShadow a boolean to skip shadow filter output 33649 * @return {String} 33650 */ 33651 getSvgStyles: function (skipShadow) { 33652 var svgStyle = fabric.Object.prototype.getSvgStyles.call( 33653 this, 33654 skipShadow, 33655 ); 33656 return svgStyle + " white-space: pre;"; 33657 }, 33658 }, 33659 ); 33660})(); 33661/* _TO_SVG_END_ */ 33662 33663(function (global) { 33664 "use strict"; 33665 33666 var fabric = global.fabric || (global.fabric = {}); 33667 33668 /** 33669 * Textbox class, based on IText, allows the user to resize the text rectangle 33670 * and wraps lines automatically. Textboxes have their Y scaling locked, the 33671 * user can only change width. Height is adjusted automatically based on the 33672 * wrapping of lines. 33673 * @class fabric.Textbox 33674 * @extends fabric.IText 33675 * @mixes fabric.Observable 33676 * @return {fabric.Textbox} thisArg 33677 * @see {@link fabric.Textbox#initialize} for constructor definition 33678 */ 33679 fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, { 33680 /** 33681 * Type of an object
vendor: 10,876 bytes, lines 33682-34041
33682 * @type String 33683 * @default 33684 */ 33685 type: "textbox", 33686 33687 /** 33688 * Minimum width of textbox, in pixels. 33689 * @type Number 33690 * @default 33691 */ 33692 minWidth: 20, 33693 33694 /** 33695 * Minimum calculated width of a textbox, in pixels. 33696 * fixed to 2 so that an empty textbox cannot go to 0 33697 * and is still selectable without text. 33698 * @type Number 33699 * @default 33700 */ 33701 dynamicMinWidth: 2, 33702 33703 /** 33704 * Cached array of text wrapping. 33705 * @type Array 33706 */ 33707 __cachedLines: null, 33708 33709 /** 33710 * Override standard Object class values 33711 */ 33712 lockScalingFlip: true, 33713 33714 /** 33715 * Override standard Object class values 33716 * Textbox needs this on false 33717 */ 33718 noScaleCache: false, 33719 33720 /** 33721 * Properties which when set cause object to change dimensions 33722 * @type Object 33723 * @private 33724 */ 33725 _dimensionAffectingProps: 33726 fabric.Text.prototype._dimensionAffectingProps.concat("width"), 33727 33728 /** 33729 * Use this regular expression to split strings in breakable lines 33730 * @private 33731 */ 33732 _wordJoiners: /[ \t\r]/, 33733 33734 /** 33735 * Use this boolean property in order to split strings that have no white space concept. 33736 * this is a cheap way to help with chinese/japanese 33737 * @type Boolean 33738 * @since 2.6.0 33739 */ 33740 splitByGrapheme: false, 33741 33742 /** 33743 * Unlike superclass's version of this function, Textbox does not update 33744 * its width. 33745 * @private 33746 * @override 33747 */ 33748 initDimensions: function () { 33749 if (this.__skipDimension) { 33750 return; 33751 } 33752 this.isEditing && this.initDelayedCursor(); 33753 this.clearContextTop(); 33754 this._clearCache(); 33755 // clear dynamicMinWidth as it will be different after we re-wrap line 33756 this.dynamicMinWidth = 0; 33757 // wrap lines 33758 this._styleMap = this._generateStyleMap(this._splitText()); 33759 // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap 33760 if (this.dynamicMinWidth > this.width) { 33761 this._set("width", this.dynamicMinWidth); 33762 } 33763 if (this.textAlign.indexOf("justify") !== -1) { 33764 // once text is measured we need to make space fatter to make justified text. 33765 this.enlargeSpaces(); 33766 } 33767 // clear cache and re-calculate height 33768 this.height = this.calcTextHeight(); 33769 this.saveState({ propertySet: "_dimensionAffectingProps" }); 33770 }, 33771 33772 /** 33773 * Generate an object that translates the style object so that it is 33774 * broken up by visual lines (new lines and automatic wrapping). 33775 * The original text styles object is broken up by actual lines (new lines only), 33776 * which is only sufficient for Text / IText 33777 * @private 33778 */ 33779 _generateStyleMap: function (textInfo) { 33780 var realLineCount = 0, 33781 realLineCharCount = 0, 33782 charCount = 0, 33783 map = {}; 33784 33785 for (var i = 0; i < textInfo.graphemeLines.length; i++) { 33786 if (textInfo.graphemeText[charCount] === "\n" && i > 0) { 33787 realLineCharCount = 0; 33788 charCount++; 33789 realLineCount++; 33790 } else if ( 33791 !this.splitByGrapheme && 33792 this._reSpaceAndTab.test(textInfo.graphemeText[charCount]) && 33793 i > 0 33794 ) { 33795 // this case deals with space's that are removed from end of lines when wrapping 33796 realLineCharCount++; 33797 charCount++; 33798 } 33799 33800 map[i] = { line: realLineCount, offset: realLineCharCount }; 33801 33802 charCount += textInfo.graphemeLines[i].length; 33803 realLineCharCount += textInfo.graphemeLines[i].length; 33804 } 33805 33806 return map; 33807 }, 33808 33809 /** 33810 * Returns true if object has a style property or has it on a specified line 33811 * @param {Number} lineIndex 33812 * @return {Boolean} 33813 */ 33814 styleHas: function (property, lineIndex) { 33815 if (this._styleMap && !this.isWrapping) { 33816 var map = this._styleMap[lineIndex]; 33817 if (map) { 33818 lineIndex = map.line; 33819 } 33820 } 33821 return fabric.Text.prototype.styleHas.call(this, property, lineIndex); 33822 }, 33823 33824 /** 33825 * Returns true if object has no styling or no styling in a line 33826 * @param {Number} lineIndex , lineIndex is on wrapped lines. 33827 * @return {Boolean} 33828 */ 33829 isEmptyStyles: function (lineIndex) { 33830 if (!this.styles) { 33831 return true; 33832 } 33833 var offset = 0, 33834 nextLineIndex = lineIndex + 1, 33835 nextOffset, 33836 obj, 33837 shouldLimit = false, 33838 map = this._styleMap[lineIndex], 33839 mapNextLine = this._styleMap[lineIndex + 1]; 33840 if (map) { 33841 lineIndex = map.line; 33842 offset = map.offset; 33843 } 33844 if (mapNextLine) { 33845 nextLineIndex = mapNextLine.line; 33846 shouldLimit = nextLineIndex === lineIndex; 33847 nextOffset = mapNextLine.offset; 33848 } 33849 obj = 33850 typeof lineIndex === "undefined" 33851 ? this.styles 33852 : { line: this.styles[lineIndex] }; 33853 for (var p1 in obj) { 33854 for (var p2 in obj[p1]) { 33855 if (p2 >= offset && (!shouldLimit || p2 < nextOffset)) { 33856 // eslint-disable-next-line no-unused-vars 33857 for (var p3 in obj[p1][p2]) { 33858 return false; 33859 } 33860 } 33861 } 33862 } 33863 return true; 33864 }, 33865 33866 /** 33867 * @param {Number} lineIndex 33868 * @param {Number} charIndex 33869 * @private 33870 */ 33871 _getStyleDeclaration: function (lineIndex, charIndex) { 33872 if (this._styleMap && !this.isWrapping) { 33873 var map = this._styleMap[lineIndex]; 33874 if (!map) { 33875 return null; 33876 } 33877 lineIndex = map.line; 33878 charIndex = map.offset + charIndex; 33879 } 33880 return this.callSuper("_getStyleDeclaration", lineIndex, charIndex); 33881 }, 33882 33883 /** 33884 * @param {Number} lineIndex 33885 * @param {Number} charIndex 33886 * @param {Object} style 33887 * @private 33888 */ 33889 _setStyleDeclaration: function (lineIndex, charIndex, style) { 33890 var map = this._styleMap[lineIndex]; 33891 lineIndex = map.line; 33892 charIndex = map.offset + charIndex; 33893 33894 this.styles[lineIndex][charIndex] = style; 33895 }, 33896 33897 /** 33898 * @param {Number} lineIndex 33899 * @param {Number} charIndex 33900 * @private 33901 */ 33902 _deleteStyleDeclaration: function (lineIndex, charIndex) { 33903 var map = this._styleMap[lineIndex]; 33904 lineIndex = map.line; 33905 charIndex = map.offset + charIndex; 33906 delete this.styles[lineIndex][charIndex]; 33907 }, 33908 33909 /** 33910 * probably broken need a fix 33911 * Returns the real style line that correspond to the wrapped lineIndex line 33912 * Used just to verify if the line does exist or not. 33913 * @param {Number} lineIndex 33914 * @returns {Boolean} if the line exists or not 33915 * @private 33916 */ 33917 _getLineStyle: function (lineIndex) { 33918 var map = this._styleMap[lineIndex]; 33919 return !!this.styles[map.line]; 33920 }, 33921 33922 /** 33923 * Set the line style to an empty object so that is initialized 33924 * @param {Number} lineIndex 33925 * @param {Object} style 33926 * @private 33927 */ 33928 _setLineStyle: function (lineIndex) { 33929 var map = this._styleMap[lineIndex]; 33930 this.styles[map.line] = {}; 33931 }, 33932 33933 /** 33934 * Wraps text using the 'width' property of Textbox. First this function 33935 * splits text on newlines, so we preserve newlines entered by the user. 33936 * Then it wraps each line using the width of the Textbox by calling 33937 * _wrapLine(). 33938 * @param {Array} lines The string array of text that is split into lines 33939 * @param {Number} desiredWidth width you want to wrap to 33940 * @returns {Array} Array of lines 33941 */ 33942 _wrapText: function (lines, desiredWidth) { 33943 var wrapped = [], 33944 i; 33945 this.isWrapping = true; 33946 for (i = 0; i < lines.length; i++) { 33947 wrapped = wrapped.concat(this._wrapLine(lines[i], i, desiredWidth)); 33948 } 33949 this.isWrapping = false; 33950 return wrapped; 33951 }, 33952 33953 /** 33954 * Helper function to measure a string of text, given its lineIndex and charIndex offset 33955 * it gets called when charBounds are not available yet. 33956 * @param {CanvasRenderingContext2D} ctx 33957 * @param {String} text 33958 * @param {number} lineIndex 33959 * @param {number} charOffset 33960 * @returns {number} 33961 * @private 33962 */ 33963 _measureWord: function (word, lineIndex, charOffset) { 33964 var width = 0, 33965 prevGrapheme, 33966 skipLeft = true; 33967 charOffset = charOffset || 0; 33968 for (var i = 0, len = word.length; i < len; i++) { 33969 var box = this._getGraphemeBox( 33970 word[i], 33971 lineIndex, 33972 i + charOffset, 33973 prevGrapheme, 33974 skipLeft, 33975 ); 33976 width += box.kernedWidth; 33977 prevGrapheme = word[i]; 33978 } 33979 return width; 33980 }, 33981 33982 /** 33983 * Wraps a line of text using the width of the Textbox and a context. 33984 * @param {Array} line The grapheme array that represent the line 33985 * @param {Number} lineIndex 33986 * @param {Number} desiredWidth width you want to wrap the line to 33987 * @param {Number} reservedSpace space to remove from wrapping for custom functionalities 33988 * @returns {Array} Array of line(s) into which the given text is wrapped 33989 * to. 33990 */ 33991 _wrapLine: function (_line, lineIndex, desiredWidth, reservedSpace) { 33992 var lineWidth = 0, 33993 splitByGrapheme = this.splitByGrapheme, 33994 graphemeLines = [], 33995 line = [], 33996 // spaces in different languages? 33997 words = splitByGrapheme 33998 ? fabric.util.string.graphemeSplit(_line) 33999 : _line.split(this._wordJoiners), 34000 word = "", 34001 offset = 0, 34002 infix = splitByGrapheme ? "" : " ", 34003 wordWidth = 0, 34004 infixWidth = 0, 34005 largestWordWidth = 0, 34006 lineJustStarted = true, 34007 additionalSpace = this._getWidthOfCharSpacing(), 34008 reservedSpace = reservedSpace || 0; 34009 // fix a difference between split and graphemeSplit 34010 if (words.length === 0) { 34011 words.push([]); 34012 } 34013 desiredWidth -= reservedSpace; 34014 for (var i = 0; i < words.length; i++) { 34015 // if using splitByGrapheme words are already in graphemes. 34016 word = splitByGrapheme 34017 ? words[i] 34018 : fabric.util.string.graphemeSplit(words[i]); 34019 wordWidth = this._measureWord(word, lineIndex, offset); 34020 offset += word.length; 34021 34022 lineWidth += infixWidth + wordWidth - additionalSpace; 34023 if (lineWidth > desiredWidth && !lineJustStarted) { 34024 graphemeLines.push(line); 34025 line = []; 34026 lineWidth = wordWidth; 34027 lineJustStarted = true; 34028 } else { 34029 lineWidth += additionalSpace; 34030 } 34031 34032 if (!lineJustStarted && !splitByGrapheme) { 34033 line.push(infix); 34034 } 34035 line = line.concat(word); 34036 34037 infixWidth = splitByGrapheme 34038 ? 0 34039 : this._measureWord([infix], lineIndex, offset); 34040 offset++; 34041 lineJustStarted = false;
34042 // keep track of largest word 34043 if (wordWidth > largestWordWidth) { 34044 largestWordWidth = wordWidth; 34045 } 34046 } 34047 34048 i && graphemeLines.push(line); 34049 34050 if (largestWordWidth + reservedSpace > this.dynamicMinWidth) { 34051 this.dynamicMinWidth = 34052 largestWordWidth - additionalSpace + reservedSpace; 34053 } 34054 return graphemeLines; 34055 }, 34056 34057 /** 34058 * Detect if the text line is ended with an hard break 34059 * text and itext do not have wrapping, return false 34060 * @param {Number} lineIndex text to split 34061 * @return {Boolean} 34062 */ 34063 isEndOfWrapping: function (lineIndex) { 34064 if (!this._styleMap[lineIndex + 1]) { 34065 // is last line, return true; 34066 return true; 34067 } 34068 if ( 34069 this._styleMap[lineIndex + 1].line !== this._styleMap[lineIndex].line 34070 ) { 34071 // this is last line before a line break, return true; 34072 return true; 34073 } 34074 return false; 34075 }, 34076 34077 /** 34078 * Detect if a line has a linebreak and so we need to account for it when moving 34079 * and counting style. 34080 * @return Number 34081 */ 34082 missingNewlineOffset: function (lineIndex) { 34083 if (this.splitByGrapheme) { 34084 return this.isEndOfWrapping(lineIndex) ? 1 : 0; 34085 } 34086 return 1; 34087 }, 34088 34089 /** 34090 * Gets lines of text to render in the Textbox. This function calculates 34091 * text wrapping on the fly every time it is called. 34092 * @param {String} text text to split 34093 * @returns {Array} Array of lines in the Textbox. 34094 * @override 34095 */ 34096 _splitTextIntoLines: function (text) { 34097 var newText = fabric.Text.prototype._splitTextIntoLines.call(this, text), 34098 graphemeLines = this._wrapText(newText.lines, this.width), 34099 lines = new Array(graphemeLines.length); 34100 for (var i = 0; i < graphemeLines.length; i++) { 34101 lines[i] = graphemeLines[i].join(""); 34102 } 34103 newText.lines = lines; 34104 newText.graphemeLines = graphemeLines; 34105 return newText; 34106 }, 34107 34108 getMinWidth: function () { 34109 return Math.max(this.minWidth, this.dynamicMinWidth); 34110 }, 34111 34112 _removeExtraneousStyles: function () { 34113 var linesToKeep = {}; 34114 for (var prop in this._styleMap) { 34115 if (this._textLines[prop]) { 34116 linesToKeep[this._styleMap[prop].line] = 1; 34117 } 34118 } 34119 for (var prop in this.styles) { 34120 if (!linesToKeep[prop]) { 34121 delete this.styles[prop]; 34122 } 34123 } 34124 }, 34125 34126 /** 34127 * Returns object representation of an instance 34128 * @method toObject 34129 * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output 34130 * @return {Object} object representation of an instance 34131 */ 34132 toObject: function (propertiesToInclude) { 34133 return this.callSuper( 34134 "toObject", 34135 ["minWidth", "splitByGrapheme"].concat(propertiesToInclude), 34136 ); 34137 }, 34138 }); 34139 34140 /** 34141 * Returns fabric.Textbox instance from an object representation 34142 * @static 34143 * @memberOf fabric.Textbox 34144 * @param {Object} object Object to create an instance from 34145 * @param {Function} [callback] Callback to invoke when an fabric.Textbox instance is created 34146 */ 34147 fabric.Textbox.fromObject = function (object, callback) { 34148 var styles = fabric.util.stylesFromArray(object.styles, object.text); 34149 //copy object to prevent mutation 34150 let objCopy = Object.assign({}, object, { styles: styles }); 34151 return fabric.Object._fromObject("Textbox", objCopy, callback, "text"); 34152 }; 34153 34154 /* Custom expanded patrh-group code : Suraj */ 34155 fabric.PathGroup = {}; 34156 fabric.PathGroup.fromObject = function (object, callback) { 34157 let originalPaths = object.paths || object.src; 34158 delete object.paths; 34159 if (typeof originalPaths === "string") { 34160 fabric.loadSVGFromURL(originalPaths, (elements) => { 34161 let pathUrl = originalPaths; 34162 if ( 34163 object.objType == "shape" && 34164 object.fill != "rgb(0,0,0)" && 34165 (elements ? elements.length : 0) > 1 && 34166 (object.fill || (object.fill = "rgb(0,0,0)")) && 34167 (object.opacity || (object.opacity = 1)) 34168 ) {
34169 elements.forEach((value) => { 34170 value.set({ 34171 fill: object.fill, 34172 pastOpacity: value.opacity, 34173 opacity: value.opacity * object.opacity, 34174 }); 34175 }); 34176 } else if (object.objType == "background" && object.opacity == null) { 34177 object.opacity = 1; 34178 } 34179 if (object.typecast && elements != undefined && elements.length >= 1) { 34180 let max_width = elements[0].width, 34181 max_height = elements[0].height, 34182 deMatrix = [], 34183 scaleX, 34184 scaleY; 34185 (scaleX = elements[0].scaleX), (scaleY = elements[0].scaleY); 34186 if ( 34187 elements[0].type == "image" && 34188 elements[0].transformMatrix && 34189 elements[0].transformMatrix != "" 34190 ) { 34191 deMatrix = e.util.qrDecompose(elements[0].transformMatrix); 34192 scaleX = deMatrix && deMatrix.scaleX && deMatrix.scaleX; 34193 scaleY = deMatrix && deMatrix.scaleY && deMatrix.scaleY; 34194 } 34195 max_width = 34196 elements[0] && 34197 elements[0]["xlink:href"] && 34198 elements[0]["xlink:href"].search("data:image/") == 0 34199 ? object.width * object.scaleX 34200 : elements[0].width * scaleX; 34201 max_height = 34202 elements[0] && 34203 elements[0]["xlink:href"] && 34204 elements[0]["xlink:href"].search("data:image/") == 0 34205 ? object.height * object.scaleY 34206 : elements[0].height * scaleY; 34207 34208 elements.forEach((v) => { 34209 (scaleX = v.scaleX), (scaleY = v.scaleY); 34210 if ( 34211 v.type == "image" && 34212 v.transformMatrix && 34213 v.transformMatrix != "" 34214 ) { 34215 deMatrix = e.util.qrDecompose(v.transformMatrix); 34216 (scaleX = deMatrix && deMatrix.scaleX && deMatrix.scaleX),
34217 (scaleY = deMatrix && deMatrix.scaleY && deMatrix.scaleY); 34218 } 34219 // max_width = ((v.width * scaleX)+v.left>max_width)?(v.width * scaleX)+v.left:max_width, 34220 // max_height= ((v.height * scaleY)+v.top>max_height)?(v.height * scaleY)+v.top:max_height; 34221 /* start: Added to solve template background scalling issue (e.g. Halloween templates) */ 34222 max_width = 34223 object.objType == "background" 34224 ? object.width > max_width 34225 ? object.width 34226 : max_width 34227 : v.width * scaleX + v.left > max_width 34228 ? v.width * scaleX + v.left 34229 : max_width; 34230 max_height = 34231 object.objType == "background" 34232 ? object.height > max_height 34233 ? object.height 34234 : max_height 34235 : v.height * scaleY + v.top > max_height 34236 ? v.height * scaleY + v.top 34237 : max_height; 34238 /* end: Added to solve template background scalling issue (e.g. Halloween templates) */ 34239 }); 34240 object["scaleX"] = (object.width * object.scaleX) / max_width; 34241 object["scaleY"] = (object.height * object.scaleY) / max_height; 34242 (object["width"] = max_width), (object["height"] = max_height); 34243 } 34244 let group = fabric.util.groupSVGElements(elements, object, pathUrl); 34245 object.objType != "shape" && (group.type = "group"); 34246 group.objType = object.objType; 34247 object.objects = originalPaths; 34248 group.objects = group.paths = group.sourcePath = originalPaths; 34249 if ( 34250 object.typecast && 34251 (object.objType == "clipart" || object.objType == "background") 34252 ) { 34253 if ( 34254 elements && 34255 elements.length == 1 && 34256 elements[0] && 34257 elements[0]["xlink:href"] && 34258 elements[0]["xlink:href"].search("data:image/") == 0 34259 ) { 34260 elements[0].scaleY = 34261 (object.height * object.scaleY) / elements[0].height; 34262 elements[0].scaleX = 34263 (object.width * object.scaleX) / elements[0].width; 34264 group.left = 34265 elements && elements.length > 1 34266 ? group.left 34267 : object.left - (object.width * object.scaleX) / 2; 34268 group.top = 34269 elements && elements.length > 1 34270 ? group.top 34271 : object.top - (object.height * object.scaleY) / 2; 34272 } else if (elements && elements.length == 1 && elements[0]) { 34273 object.objType == "clipart" && (group.angle = object.angle); 34274 (group.left = object.left), (group.top = object.top); 34275 (group.scaleX = object.scaleX), (group.scaleY = object.scaleY); 34276 (group.originX = object.originX), (group.originY = object.originY); 34277 } else { 34278 /* start: Added to solve template background scalling issue (e.g. Halloween templates) */ 34279 object.objType == "background" && 34280 (group.left = object.left) && 34281 (group.top = object.top); 34282 } 34283 } 34284 // object.typecast && object.objType == 'shape' && (group.scaleY = object.scaleY, group.scaleX = object.scaleX, group.fill = object.fill?object.fill:group.fill,group.left=(elements && elements.length>1?group.left:object.left - (object.width*object.scaleX/2)),group.top=(elements && elements.length>1?group.top:object.top - (object.height*object.scaleY/2))); 34285 if ( 34286 object.typecast && 34287 (object.objType == "uploaded" || object.objType == "shape") 34288 ) { 34289 group.scaleY = 34290 group.height && (object.scaleY * object.height) / group.height; 34291 group.scaleX = 34292 group.width && (object.scaleX * object.width) / group.width; 34293 group.fill = object.fill ? object.fill : group.fill; 34294 group.left = 34295 elements && elements.length > 1 ? group.left : object.left; 34296 group.top = elements && elements.length > 1 ? group.top : object.top; 34297 group.angle = object.angle; 34298 group.originX = object.originX; 34299 group.originY = object.originY; 34300 group.fill = group.fill && group.fill.replace("##", "#"); 34301 } 34302 delete object.typecast; 34303 callback(group); 34304 }); 34305 } else { 34306 fabric.util.enlivenObjects(originalPaths, (enlivenedObjects) => {
34307 enlivenedObjects.forEach((obj) => { 34308 obj._removeTransformMatrix(); 34309 }); 34310 let group = new fabric.Group(enlivenedObjects, object); 34311 group.type = "group"; 34312 object.paths = originalPaths; 34313 callback(group); 34314 }); 34315 } 34316 }; 34317})(typeof exports !== "undefined" ? exports : this); 34318 34319(function () { 34320 var controlsUtils = fabric.controlsUtils, 34321 scaleSkewStyleHandler = controlsUtils.scaleSkewCursorStyleHandler, 34322 scaleStyleHandler = controlsUtils.scaleCursorStyleHandler, 34323 scalingEqually = controlsUtils.scalingEqually, 34324 scalingYOrSkewingX = controlsUtils.scalingYOrSkewingX, 34325 scalingXOrSkewingY = controlsUtils.scalingXOrSkewingY, 34326 scaleOrSkewActionName = controlsUtils.scaleOrSkewActionName, 34327 objectControls = fabric.Object.prototype.controls; 34328 34329 objectControls.ml = new fabric.Control({ 34330 x: -0.5, 34331 y: 0, 34332 cursorStyleHandler: scaleSkewStyleHandler, 34333 actionHandler: scalingXOrSkewingY, 34334 getActionName: scaleOrSkewActionName, 34335 }); 34336 34337 objectControls.mr = new fabric.Control({ 34338 x: 0.5, 34339 y: 0, 34340 cursorStyleHandler: scaleSkewStyleHandler, 34341 actionHandler: scalingXOrSkewingY, 34342 getActionName: scaleOrSkewActionName, 34343 }); 34344 34345 objectControls.mb = new fabric.Control({ 34346 x: 0, 34347 y: 0.5, 34348 cursorStyleHandler: scaleSkewStyleHandler, 34349 actionHandler: scalingYOrSkewingX, 34350 getActionName: scaleOrSkewActionName, 34351 }); 34352 34353 objectControls.mt = new fabric.Control({ 34354 x: 0, 34355 y: -0.5, 34356 cursorStyleHandler: scaleSkewStyleHandler, 34357 actionHandler: scalingYOrSkewingX, 34358 getActionName: scaleOrSkewActionName, 34359 }); 34360 34361 objectControls.tl = new fabric.Control({ 34362 x: -0.5, 34363 y: -0.5, 34364 cursorStyleHandler: scaleStyleHandler, 34365 actionHandler: scalingEqually, 34366 }); 34367 34368 objectControls.tr = new fabric.Control({ 34369 x: 0.5, 34370 y: -0.5, 34371 cursorStyleHandler: scaleStyleHandler, 34372 actionHandler: scalingEqually, 34373 }); 34374 34375 objectControls.bl = new fabric.Control({ 34376 x: -0.5, 34377 y: 0.5, 34378 cursorStyleHandler: scaleStyleHandler, 34379 actionHandler: scalingEqually, 34380 }); 34381 34382 objectControls.br = new fabric.Control({ 34383 x: 0.5, 34384 y: 0.5, 34385 cursorStyleHandler: scaleStyleHandler, 34386 actionHandler: scalingEqually, 34387 }); 34388 34389 objectControls.mtr = new fabric.Control({ 34390 x: 0, 34391 y: -0.5, 34392 actionHandler: controlsUtils.rotationWithSnapping, 34393 cursorStyleHandler: controlsUtils.rotationStyleHandler, 34394 offsetY: -40, 34395 withConnection: true, 34396 actionName: "rotate", 34397 }); 34398 34399 if (fabric.Textbox) { 34400 // this is breaking the prototype inheritance, no time / ideas to fix it. 34401 // is important to document that if you want to have all objects to have a 34402 // specific custom control, you have to add it to Object prototype and to Textbox 34403 // prototype. The controls are shared as references. So changes to control `tr` 34404 // can still apply to all objects if needed. 34405 var textBoxControls = (fabric.Textbox.prototype.controls = {}); 34406 34407 textBoxControls.mtr = objectControls.mtr; 34408 textBoxControls.tr = objectControls.tr; 34409 textBoxControls.br = objectControls.br; 34410 textBoxControls.tl = objectControls.tl; 34411 textBoxControls.bl = objectControls.bl; 34412 textBoxControls.mt = objectControls.mt; 34413 textBoxControls.mb = objectControls.mb; 34414 34415 textBoxControls.mr = new fabric.Control({ 34416 x: 0.5, 34417 y: 0, 34418 actionHandler: controlsUtils.changeWidth, 34419 cursorStyleHandler: scaleSkewStyleHandler, 34420 actionName: "resizing", 34421 }); 34422 34423 textBoxControls.ml = new fabric.Control({ 34424 x: -0.5, 34425 y: 0, 34426 actionHandler: controlsUtils.changeWidth, 34427 cursorStyleHandler: scaleSkewStyleHandler, 34428 actionName: "resizing", 34429 }); 34430 } 34431 /* 34432 const deleteIcon = "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Ccircle style='fill:%23F44336;' cx='299.76' cy='439.067' r='218.516'/%3E%3Cg%3E%3Crect x='267.162' y='307.978' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -222.6202 340.6915)' style='fill:white;' width='65.545' height='262.18'/%3E%3Crect x='266.988' y='308.153' transform='matrix(0.7071 0.7071 -0.7071 0.7071 398.3889 -83.3116)' style='fill:white;' width='65.544' height='262.179'/%3E%3C/g%3E%3C/svg%3E"; 34433 const img = document.createElement('img'); 34434 img.src = deleteIcon; 34435 function deleteObject(eventData, transform) { 34436 var target = transform.target; 34437 var canvas = target.canvas; 34438 canvas.remove(target); 34439 canvas.requestRenderAll(); 34440 } 34441 function renderIcon(ctx, left, top, styleOverride, fabricObject) { 34442 var size = this.cornerSize; 34443 ctx.save(); 34444 ctx.translate(left, top); 34445 ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); 34446 ctx.drawImage(img, -size / 2, -size / 2, size, size);
34447 ctx.restore(); 34448 } 34449 fabric.Object.prototype.controls.deleteControl = new fabric.Control({ 34450 x: 0.5, 34451 y: -0.5, 34452 offsetY: 16, 34453 cursorStyle: 'pointer', 34454 mouseUpHandler: deleteObject, 34455 render: renderIcon, 34456 cornerSize: 24 34457 }); 34458 */ 34459})(); 34460 34461// URL normalize logic here : Suraj-24/4/2025 34462(function () { 34463 function replaceCdnUrl(url) { 34464 const cdnUrl = window.NEXT_PUBLIC_CDN_URL; 34465 if (url.startsWith("data:")) { 34466 return url; 34467 } 34468 const existingCdnUrls = [ 34469 "https://d1owifjwev9mvs.cloudfront.net/", 34470 "https://d3h0plqn962z36.cloudfront.net/", 34471 "https://d3gd4qdmy5zr4x.cloudfront.net/", 34472 "https://dkpzugxxi7ybi.cloudfront.net/", 34473 "https://duusl9bkd291v.cloudfront.net/", 34474 "https://dgbijfdsapvz3.cloudfront.net/", 34475 "https://www.customdesigntool.com/", 34476 "https://customdesigntool.com/", 34477 ]; 34478 const match = existingCdnUrls.find((cdn) => url.startsWith(cdn)); 34479 if (match) { 34480 return url.replace(match, cdnUrl + "/"); 34481 } 34482 if (!url.startsWith("http://") && !url.startsWith("https://")) { 34483 return `${cdnUrl}/${url.replace(/^\/+/, "")}`; 34484 } 34485 return url; 34486 } 34487 window.replaceCdnUrl = replaceCdnUrl; 34488})(); 34489 34490/* 34491(function () { 34492 fabric.Object.prototype.toSVG = (function (toSVG) { 34493 return function (reviver) { 34494 let svgString = toSVG.call(this, reviver); 34495 if(this && this.codeImage){ 34496 const imageSrc = this.codeImage; 34497 const imageBase64 = imageSrc.startsWith('data:image/') ? imageSrc : ''; 34498 // Get the current style of the object (if any) 34499 const style = this.get('style') || ''; 34500 const x = this.get('left') || 0; 34501 const y = this.get('top') || 0; 34502 const width = this.get('width') || 0; 34503 const height = this.get('height') || 0; 34504 svgString = svgString.replace( 34505 /<g([^>]*)>/, // Match the opening <g> tag with its attributes 34506 (match, group) => { 34507 return `<g${group}> <image xmlns="http://www.w3.org/2000/svg" style="${style}" xlink:href="${imageBase64}" x="${x}" y="${y}" width="${width}" height="${height}" ></image>`; 34508 } 34509 ); 34510 } 34511 return svgString; 34512 }; 34513})(fabric.Object.prototype.toSVG); 34514})(); 34515*/
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.