PageSourceSearch

https://cdn.bestofsigns.com/fabric/fabric5_3.js

js bestofsigns.com collected 2026-09-26 00:21:52 UTC 1,095,201 bytes, 34,515 lines download raw bytes

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, "&amp;")
3257      .replace(/"/g, "&quot;")
3258      .replace(/'/g, "&apos;")
3259      .replace(/</g, "&lt;")
3260      .replace(/>/g, "&gt;");
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 &lt;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 &lt;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 &lt;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 &lt;?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 &lt;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 &lt;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.