// source --> https://www.ppag.at/wp-content/themes/_tk-master/includes/js/fabric.js 
/* build: `node build.js modules=ALL exclude=gestures,cufon,json minifier=uglifyjs` */
/*! Fabric.js Copyright 2008-2014, Printio (Juriy Zaytsev, Maxim Chernyak) */

var fabric = fabric || { version: "1.4.9" };
if (typeof exports !== "undefined") {
  exports.fabric = fabric;
}

if (typeof document !== "undefined" && typeof window !== "undefined") {
  fabric.document = document;
  fabric.window = window;
} else {
  // assume we're running under node.js when document/window are not present
  fabric.document = require("jsdom").jsdom(
    "<!DOCTYPE html><html><head></head><body></body></html>"
  );

  fabric.window = fabric.document.createWindow();
}

/**
 * True when in environment that supports touch events
 * @type boolean
 */
fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;

/**
 * True when in environment that's probably Node.js
 * @type boolean
 */
fabric.isLikelyNode =
  typeof Buffer !== "undefined" && typeof window === "undefined";

/**
 * Attributes parsed from all SVG elements
 * @type array
 */
fabric.SHARED_ATTRIBUTES = [
  "display",
  "transform",
  "fill",
  "fill-opacity",
  "fill-rule",
  "opacity",
  "stroke",
  "stroke-dasharray",
  "stroke-linecap",
  "stroke-linejoin",
  "stroke-miterlimit",
  "stroke-opacity",
  "stroke-width"
];

/**
 * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.
 */
fabric.DPI = 96;

(function () {
  /**
   * @private
   * @param {String} eventName
   * @param {Function} handler
   */
  function _removeEventListener(eventName, handler) {
    if (!this.__eventListeners[eventName]) return;

    if (handler) {
      fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
    } else {
      this.__eventListeners[eventName].length = 0;
    }
  }

  /**
   * Observes specified event
   * @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
   * @memberOf fabric.Observable
   * @alias on
   * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
   * @param {Function} handler Function that receives a notification when an event of the specified type occurs
   * @return {Self} thisArg
   * @chainable
   */
  function observe(eventName, handler) {
    if (!this.__eventListeners) {
      this.__eventListeners = {};
    }
    // one object with key/value pairs was passed
    if (arguments.length === 1) {
      for (var prop in eventName) {
        this.on(prop, eventName[prop]);
      }
    } else {
      if (!this.__eventListeners[eventName]) {
        this.__eventListeners[eventName] = [];
      }
      this.__eventListeners[eventName].push(handler);
    }
    return this;
  }

  /**
   * Stops event observing for a particular event handler. Calling this method
   * without arguments removes all handlers for all events
   * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead)
   * @memberOf fabric.Observable
   * @alias off
   * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
   * @param {Function} handler Function to be deleted from EventListeners
   * @return {Self} thisArg
   * @chainable
   */
  function stopObserving(eventName, handler) {
    if (!this.__eventListeners) return;

    // remove all key/value pairs (event name -> event handler)
    if (arguments.length === 0) {
      this.__eventListeners = {};
    }
    // one object with key/value pairs was passed
    else if (arguments.length === 1 && typeof arguments[0] === "object") {
      for (var prop in eventName) {
        _removeEventListener.call(this, prop, eventName[prop]);
      }
    } else {
      _removeEventListener.call(this, eventName, handler);
    }
    return this;
  }

  /**
   * Fires event with an optional options object
   * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead)
   * @memberOf fabric.Observable
   * @alias trigger
   * @param {String} eventName Event name to fire
   * @param {Object} [options] Options object
   * @return {Self} thisArg
   * @chainable
   */
  function fire(eventName, options) {
    if (!this.__eventListeners) return;

    var listenersForEvent = this.__eventListeners[eventName];
    if (!listenersForEvent) return;
    for (var i = 0, len = listenersForEvent.length; i < len; i++) {
      // avoiding try/catch for perf. reasons
      listenersForEvent[i].call(this, options || {});
    }
    return this;
  }

  /**
   * @namespace fabric.Observable
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events}
   * @see {@link http://fabricjs.com/events/|Events demo}
   */
  fabric.Observable = {
    observe: observe,
    stopObserving: stopObserving,
    fire: fire,

    on: observe,
    off: stopObserving,
    trigger: fire
  };
})();

/**
 * @namespace fabric.Collection
 */
fabric.Collection = {
  /**
   * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`)
   * Objects should be instances of (or inherit from) fabric.Object
   * @param {...fabric.Object} object Zero or more fabric instances
   * @return {Self} thisArg
   */
  add: function () {
    this._objects.push.apply(this._objects, arguments);
    for (var i = 0, length = arguments.length; i < length; i++) {
      this._onObjectAdded(arguments[i]);
    }
    this.renderOnAddRemove && this.renderAll();
    return this;
  },

  /**
   * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`)
   * An object should be an instance of (or inherit from) fabric.Object
   * @param {Object} object Object to insert
   * @param {Number} index Index to insert object at
   * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs
   * @return {Self} thisArg
   * @chainable
   */
  insertAt: function (object, index, nonSplicing) {
    var objects = this.getObjects();
    if (nonSplicing) {
      objects[index] = object;
    } else {
      objects.splice(index, 0, object);
    }
    this._onObjectAdded(object);
    this.renderOnAddRemove && this.renderAll();
    return this;
  },

  /**
   * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`)
   * @param {...fabric.Object} object Zero or more fabric instances
   * @return {Self} thisArg
   * @chainable
   */
  remove: function () {
    var objects = this.getObjects(),
      index;

    for (var i = 0, length = arguments.length; i < length; i++) {
      index = objects.indexOf(arguments[i]);

      // only call onObjectRemoved if an object was actually removed
      if (index !== -1) {
        objects.splice(index, 1);
        this._onObjectRemoved(arguments[i]);
      }
    }

    this.renderOnAddRemove && this.renderAll();
    return this;
  },

  /**
   * Executes given function for each object in this group
   * @param {Function} callback
   *                   Callback invoked with current object as first argument,
   *                   index - as second and an array of all objects - as third.
   *                   Iteration happens in reverse order (for performance reasons).
   *                   Callback is invoked in a context of Global Object (e.g. `window`)
   *                   when no `context` argument is given
   *
   * @param {Object} context Context (aka thisObject)
   * @return {Self} thisArg
   */
  forEachObject: function (callback, context) {
    var objects = this.getObjects(),
      i = objects.length;
    while (i--) {
      callback.call(context, objects[i], i, objects);
    }
    return this;
  },

  /**
   * Returns an array of children objects of this instance
   * Type parameter introduced in 1.3.10
   * @param {String} [type] When specified, only objects of this type are returned
   * @return {Array}
   */
  getObjects: function (type) {
    if (typeof type === "undefined") {
      return this._objects;
    }
    return this._objects.filter(function (o) {
      return o.type === type;
    });
  },

  /**
   * Returns object at specified index
   * @param {Number} index
   * @return {Self} thisArg
   */
  item: function (index) {
    return this.getObjects()[index];
  },

  /**
   * Returns true if collection contains no objects
   * @return {Boolean} true if collection is empty
   */
  isEmpty: function () {
    return this.getObjects().length === 0;
  },

  /**
   * Returns a size of a collection (i.e: length of an array containing its objects)
   * @return {Number} Collection size
   */
  size: function () {
    return this.getObjects().length;
  },

  /**
   * Returns true if collection contains an object
   * @param {Object} object Object to check against
   * @return {Boolean} `true` if collection contains an object
   */
  contains: function (object) {
    return this.getObjects().indexOf(object) > -1;
  },

  /**
   * Returns number representation of a collection complexity
   * @return {Number} complexity
   */
  complexity: function () {
    return this.getObjects().reduce(function (memo, current) {
      memo += current.complexity ? current.complexity() : 0;
      return memo;
    }, 0);
  }
};

(function (global) {
  var sqrt = Math.sqrt,
    atan2 = Math.atan2,
    PiBy180 = Math.PI / 180;

  /**
   * @namespace fabric.util
   */
  fabric.util = {
    /**
     * Removes value from an array.
     * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
     * @static
     * @memberOf fabric.util
     * @param {Array} array
     * @param {Any} value
     * @return {Array} original array
     */
    removeFromArray: function (array, value) {
      var idx = array.indexOf(value);
      if (idx !== -1) {
        array.splice(idx, 1);
      }
      return array;
    },

    /**
     * Returns random number between 2 specified ones.
     * @static
     * @memberOf fabric.util
     * @param {Number} min lower limit
     * @param {Number} max upper limit
     * @return {Number} random value (between min and max)
     */
    getRandomInt: function (min, max) {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    },

    /**
     * Transforms degrees to radians.
     * @static
     * @memberOf fabric.util
     * @param {Number} degrees value in degrees
     * @return {Number} value in radians
     */
    degreesToRadians: function (degrees) {
      return degrees * PiBy180;
    },

    /**
     * Transforms radians to degrees.
     * @static
     * @memberOf fabric.util
     * @param {Number} radians value in radians
     * @return {Number} value in degrees
     */
    radiansToDegrees: function (radians) {
      return radians / PiBy180;
    },

    /**
     * Rotates `point` around `origin` with `radians`
     * @static
     * @memberOf fabric.util
     * @param {fabric.Point} point The point to rotate
     * @param {fabric.Point} origin The origin of the rotation
     * @param {Number} radians The radians of the angle for the rotation
     * @return {fabric.Point} The new rotated point
     */
    rotatePoint: function (point, origin, radians) {
      var sin = Math.sin(radians),
        cos = Math.cos(radians);

      point.subtractEquals(origin);

      var rx = point.x * cos - point.y * sin,
        ry = point.x * sin + point.y * cos;

      return new fabric.Point(rx, ry).addEquals(origin);
    },

    /**
     * Apply transform t to point p
     * @static
     * @memberOf fabric.util
     * @param  {fabric.Point} p The point to transform
     * @param  {Array} t The transform
     * @param  {Boolean} [ignoreOffset] Indicates that the offset should not be applied
     * @return {fabric.Point} The transformed point
     */
    transformPoint: function (p, t, ignoreOffset) {
      if (ignoreOffset) {
        return new fabric.Point(
          t[0] * p.x + t[1] * p.y,
          t[2] * p.x + t[3] * p.y
        );
      }
      return new fabric.Point(
        t[0] * p.x + t[1] * p.y + t[4],
        t[2] * p.x + t[3] * p.y + t[5]
      );
    },

    /**
     * Invert transformation t
     * @static
     * @memberOf fabric.util
     * @param {Array} t The transform
     * @return {Array} The inverted transform
     */
    invertTransform: function (t) {
      var r = t.slice(),
        a = 1 / (t[0] * t[3] - t[1] * t[2]);
      r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0];
      var o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r);
      r[4] = -o.x;
      r[5] = -o.y;
      return r;
    },

    /**
     * A wrapper around Number#toFixed, which contrary to native method returns number, not string.
     * @static
     * @memberOf fabric.util
     * @param {Number|String} number number to operate on
     * @param {Number} fractionDigits number of fraction digits to "leave"
     * @return {Number}
     */
    toFixed: function (number, fractionDigits) {
      return parseFloat(Number(number).toFixed(fractionDigits));
    },

    /**
     * Converts from attribute value to pixel value if applicable.
     * Returns converted pixels or original value not converted.
     * @param {Number|String} value number to operate on
     * @return {Number|String}
     */
    parseUnit: function (value) {
      var unit = /\D{0,2}$/.exec(value),
        number = parseFloat(value);

      switch (unit[0]) {
        case "mm":
          return (number * fabric.DPI) / 25.4;

        case "cm":
          return (number * fabric.DPI) / 2.54;

        case "in":
          return number * fabric.DPI;

        case "pt":
          return (number * fabric.DPI) / 72; // or * 4 / 3

        case "pc":
          return ((number * fabric.DPI) / 72) * 12; // or * 16

        default:
          return number;
      }
    },

    /**
     * Function which always returns `false`.
     * @static
     * @memberOf fabric.util
     * @return {Boolean}
     */
    falseFunction: function () {
      return false;
    },

    /**
     * Returns klass "Class" object of given namespace
     * @memberOf fabric.util
     * @param {String} type Type of object (eg. 'circle')
     * @param {String} namespace Namespace to get klass "Class" object from
     * @return {Object} klass "Class"
     */
    getKlass: function (type, namespace) {
      // capitalize first letter only
      type = fabric.util.string.camelize(
        type.charAt(0).toUpperCase() + type.slice(1)
      );
      return fabric.util.resolveNamespace(namespace)[type];
    },

    /**
     * Returns object of given namespace
     * @memberOf fabric.util
     * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric'
     * @return {Object} Object for given namespace (default fabric)
     */
    resolveNamespace: function (namespace) {
      if (!namespace) {
        return fabric;
      }

      var parts = namespace.split("."),
        len = parts.length,
        obj = global || fabric.window;

      for (var i = 0; i < len; ++i) {
        obj = obj[parts[i]];
      }

      return obj;
    },

    /**
     * Loads image element from given url and passes it to a callback
     * @memberOf fabric.util
     * @param {String} url URL representing an image
     * @param {Function} callback Callback; invoked with loaded image
     * @param {Any} [context] Context to invoke callback in
     * @param {Object} [crossOrigin] crossOrigin value to set image element to
     */
    loadImage: function (url, callback, context, crossOrigin) {
      if (!url) {
        callback && callback.call(context, url);
        return;
      }

      var img = fabric.util.createImage();

      /** @ignore */
      img.onload = function () {
        callback && callback.call(context, img);
        img = img.onload = img.onerror = null;
      };

      /** @ignore */
      img.onerror = function () {
        fabric.log("Error loading " + img.src);
        callback && callback.call(context, null, true);
        img = img.onload = img.onerror = null;
      };

      // data-urls appear to be buggy with crossOrigin
      // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767
      // see https://code.google.com/p/chromium/issues/detail?id=315152
      //     https://bugzilla.mozilla.org/show_bug.cgi?id=935069
      if (url.indexOf("data") !== 0 && typeof crossOrigin !== "undefined") {
        img.crossOrigin = crossOrigin;
      }

      img.src = url;
    },

    /**
     * Creates corresponding fabric instances from their object representations
     * @static
     * @memberOf fabric.util
     * @param {Array} objects Objects to enliven
     * @param {Function} callback Callback to invoke when all objects are created
     * @param {String} namespace Namespace to get klass "Class" object from
     * @param {Function} reviver Method for further parsing of object elements,
     * called after each fabric object created.
     */
    enlivenObjects: function (objects, callback, namespace, reviver) {
      objects = objects || [];

      function onLoaded() {
        if (++numLoadedObjects === numTotalObjects) {
          callback && callback(enlivenedObjects);
        }
      }

      var enlivenedObjects = [],
        numLoadedObjects = 0,
        numTotalObjects = objects.length;

      if (!numTotalObjects) {
        callback && callback(enlivenedObjects);
        return;
      }

      objects.forEach(function (o, index) {
        // if sparse array
        if (!o || !o.type) {
          onLoaded();
          return;
        }
        var klass = fabric.util.getKlass(o.type, namespace);
        if (klass.async) {
          klass.fromObject(o, function (obj, error) {
            if (!error) {
              enlivenedObjects[index] = obj;
              reviver && reviver(o, enlivenedObjects[index]);
            }
            onLoaded();
          });
        } else {
          enlivenedObjects[index] = klass.fromObject(o);
          reviver && reviver(o, enlivenedObjects[index]);
          onLoaded();
        }
      });
    },

    /**
     * Groups SVG elements (usually those retrieved from SVG document)
     * @static
     * @memberOf fabric.util
     * @param {Array} elements SVG elements to group
     * @param {Object} [options] Options object
     * @return {fabric.Object|fabric.PathGroup}
     */
    groupSVGElements: function (elements, options, path) {
      var object;

      object = new fabric.PathGroup(elements, options);

      if (typeof path !== "undefined") {
        object.setSourcePath(path);
      }
      return object;
    },

    /**
     * Populates an object with properties of another object
     * @static
     * @memberOf fabric.util
     * @param {Object} source Source object
     * @param {Object} destination Destination object
     * @return {Array} properties Propertie names to include
     */
    populateWithProperties: function (source, destination, properties) {
      if (
        properties &&
        Object.prototype.toString.call(properties) === "[object Array]"
      ) {
        for (var i = 0, len = properties.length; i < len; i++) {
          if (properties[i] in source) {
            destination[properties[i]] = source[properties[i]];
          }
        }
      }
    },

    /**
     * Draws a dashed line between two points
     *
     * This method is used to draw dashed line around selection area.
     * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
     *
     * @param {CanvasRenderingContext2D} ctx context
     * @param {Number} x  start x coordinate
     * @param {Number} y start y coordinate
     * @param {Number} x2 end x coordinate
     * @param {Number} y2 end y coordinate
     * @param {Array} da dash array pattern
     */
    drawDashedLine: function (ctx, x, y, x2, y2, da) {
      var dx = x2 - x,
        dy = y2 - y,
        len = sqrt(dx * dx + dy * dy),
        rot = atan2(dy, dx),
        dc = da.length,
        di = 0,
        draw = true;

      ctx.save();
      ctx.translate(x, y);
      ctx.moveTo(0, 0);
      ctx.rotate(rot);

      x = 0;
      while (len > x) {
        x += da[di++ % dc];
        if (x > len) {
          x = len;
        }
        ctx[draw ? "lineTo" : "moveTo"](x, 0);
        draw = !draw;
      }

      ctx.restore();
    },

    /**
     * Creates canvas element and initializes it via excanvas if necessary
     * @static
     * @memberOf fabric.util
     * @param {CanvasElement} [canvasEl] optional canvas element to initialize;
     * when not given, element is created implicitly
     * @return {CanvasElement} initialized canvas element
     */
    createCanvasElement: function (canvasEl) {
      canvasEl || (canvasEl = fabric.document.createElement("canvas"));
      if (!canvasEl.getContext && typeof G_vmlCanvasManager !== "undefined") {
        G_vmlCanvasManager.initElement(canvasEl);
      }
      return canvasEl;
    },

    /**
     * Creates image element (works on client and node)
     * @static
     * @memberOf fabric.util
     * @return {HTMLImageElement} HTML image element
     */
    createImage: function () {
      return fabric.isLikelyNode
        ? new (require("canvas").Image)()
        : fabric.document.createElement("img");
    },

    /**
     * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array
     * @static
     * @memberOf fabric.util
     * @param {Object} klass "Class" to create accessors for
     */
    createAccessors: function (klass) {
      var proto = klass.prototype;

      for (var i = proto.stateProperties.length; i--; ) {
        var propName = proto.stateProperties[i],
          capitalizedPropName =
            propName.charAt(0).toUpperCase() + propName.slice(1),
          setterName = "set" + capitalizedPropName,
          getterName = "get" + capitalizedPropName;

        // using `new Function` for better introspection
        if (!proto[getterName]) {
          proto[getterName] = (function (property) {
            return new Function('return this.get("' + property + '")');
          })(propName);
        }
        if (!proto[setterName]) {
          proto[setterName] = (function (property) {
            return new Function(
              "value",
              'return this.set("' + property + '", value)'
            );
          })(propName);
        }
      }
    },

    /**
     * @static
     * @memberOf fabric.util
     * @param {fabric.Object} receiver Object implementing `clipTo` method
     * @param {CanvasRenderingContext2D} ctx Context to clip
     */
    clipContext: function (receiver, ctx) {
      ctx.save();
      ctx.beginPath();
      receiver.clipTo(ctx);
      ctx.clip();
    },

    /**
     * Multiply matrix A by matrix B to nest transformations
     * @static
     * @memberOf fabric.util
     * @param  {Array} matrixA First transformMatrix
     * @param  {Array} matrixB Second transformMatrix
     * @return {Array} The product of the two transform matrices
     */
    multiplyTransformMatrices: function (matrixA, matrixB) {
      // Matrix multiply matrixA * matrixB
      var a = [
          [matrixA[0], matrixA[2], matrixA[4]],
          [matrixA[1], matrixA[3], matrixA[5]],
          [0, 0, 1]
        ],
        b = [
          [matrixB[0], matrixB[2], matrixB[4]],
          [matrixB[1], matrixB[3], matrixB[5]],
          [0, 0, 1]
        ],
        result = [];

      for (var r = 0; r < 3; r++) {
        result[r] = [];
        for (var c = 0; c < 3; c++) {
          var sum = 0;
          for (var k = 0; k < 3; k++) {
            sum += a[r][k] * b[k][c];
          }

          result[r][c] = sum;
        }
      }

      return [
        result[0][0],
        result[1][0],
        result[0][1],
        result[1][1],
        result[0][2],
        result[1][2]
      ];
    },

    /**
     * Returns string representation of function body
     * @param {Function} fn Function to get body of
     * @return {String} Function body
     */
    getFunctionBody: function (fn) {
      return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];
    },

    /**
     * Normalizes polygon/polyline points according to their dimensions
     * @param {Array} points
     * @param {Object} options
     */
    normalizePoints: function (points, options) {
      var minX = fabric.util.array.min(points, "x"),
        minY = fabric.util.array.min(points, "y");

      minX = minX < 0 ? minX : 0;
      minY = minX < 0 ? minY : 0;

      for (var i = 0, len = points.length; i < len; i++) {
        // normalize coordinates, according to containing box
        // (dimensions of which are passed via `options`)
        points[i].x -= options.width / 2 + minX || 0;
        points[i].y -= options.height / 2 + minY || 0;
      }
    },

    /**
     * Returns true if context has transparent pixel
     * at specified location (taking tolerance into account)
     * @param {CanvasRenderingContext2D} ctx context
     * @param {Number} x x coordinate
     * @param {Number} y y coordinate
     * @param {Number} tolerance Tolerance
     */
    isTransparent: function (ctx, x, y, tolerance) {
      // If tolerance is > 0 adjust start coords to take into account.
      // If moves off Canvas fix to 0
      if (tolerance > 0) {
        if (x > tolerance) {
          x -= tolerance;
        } else {
          x = 0;
        }
        if (y > tolerance) {
          y -= tolerance;
        } else {
          y = 0;
        }
      }

      var _isTransparent = true,
        imageData = ctx.getImageData(
          x,
          y,
          tolerance * 2 || 1,
          tolerance * 2 || 1
        );

      // Split image data - for tolerance > 1, pixelDataSize = 4;
      for (var i = 3, l = imageData.data.length; i < l; i += 4) {
        var temp = imageData.data[i];
        _isTransparent = temp <= 0;
        if (_isTransparent === false) break; // Stop if colour found
      }

      imageData = null;

      return _isTransparent;
    }
  };
})(typeof exports !== "undefined" ? exports : this);

(function () {
  var arcToSegmentsCache = {},
    segmentToBezierCache = {},
    _join = Array.prototype.join,
    argsString;

  // Generous contribution by Raph Levien, from libsvg-0.1.0.tar.gz
  function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
    argsString = _join.call(arguments);

    if (arcToSegmentsCache[argsString]) {
      return arcToSegmentsCache[argsString];
    }

    var coords = getXYCoords(rotateX, rx, ry, ox, oy, x, y),
      d =
        (coords.x1 - coords.x0) * (coords.x1 - coords.x0) +
        (coords.y1 - coords.y0) * (coords.y1 - coords.y0),
      sfactorSq = 1 / d - 0.25;

    if (sfactorSq < 0) {
      sfactorSq = 0;
    }

    var sfactor = Math.sqrt(sfactorSq);
    if (sweep === large) {
      sfactor = -sfactor;
    }

    var xc = 0.5 * (coords.x0 + coords.x1) - sfactor * (coords.y1 - coords.y0),
      yc = 0.5 * (coords.y0 + coords.y1) + sfactor * (coords.x1 - coords.x0),
      th0 = Math.atan2(coords.y0 - yc, coords.x0 - xc),
      th1 = Math.atan2(coords.y1 - yc, coords.x1 - xc),
      thArc = th1 - th0;

    if (thArc < 0 && sweep === 1) {
      thArc += 2 * Math.PI;
    } else if (thArc > 0 && sweep === 0) {
      thArc -= 2 * Math.PI;
    }

    var segments = Math.ceil(Math.abs(thArc / (Math.PI * 0.5 + 0.001))),
      result = [];

    for (var i = 0; i < segments; i++) {
      var th2 = th0 + (i * thArc) / segments,
        th3 = th0 + ((i + 1) * thArc) / segments;

      result[i] = [xc, yc, th2, th3, rx, ry, coords.sinTh, coords.cosTh];
    }

    arcToSegmentsCache[argsString] = result;
    return result;
  }

  function getXYCoords(rotateX, rx, ry, ox, oy, x, y) {
    var th = rotateX * (Math.PI / 180),
      sinTh = Math.sin(th),
      cosTh = Math.cos(th);

    rx = Math.abs(rx);
    ry = Math.abs(ry);

    var px = cosTh * (ox - x) + sinTh * (oy - y),
      py = cosTh * (oy - y) - sinTh * (ox - x),
      pl = (px * px) / (rx * rx) + (py * py) / (ry * ry);

    pl *= 0.25;

    if (pl > 1) {
      pl = Math.sqrt(pl);
      rx *= pl;
      ry *= pl;
    }

    var a00 = cosTh / rx,
      a01 = sinTh / rx,
      a10 = -sinTh / ry,
      a11 = cosTh / ry;

    return {
      x0: a00 * ox + a01 * oy,
      y0: a10 * ox + a11 * oy,
      x1: a00 * x + a01 * y,
      y1: a10 * x + a11 * y,
      sinTh: sinTh,
      cosTh: cosTh
    };
  }

  function segmentToBezier(cx, cy, th0, th1, rx, ry, sinTh, cosTh) {
    argsString = _join.call(arguments);

    if (segmentToBezierCache[argsString]) {
      return segmentToBezierCache[argsString];
    }

    var sinTh0 = Math.sin(th0),
      cosTh0 = Math.cos(th0),
      sinTh1 = Math.sin(th1),
      cosTh1 = Math.cos(th1),
      a00 = cosTh * rx,
      a01 = -sinTh * ry,
      a10 = sinTh * rx,
      a11 = cosTh * ry,
      thHalf = 0.25 * (th1 - th0),
      t =
        ((8 / 3) * Math.sin(thHalf) * Math.sin(thHalf)) / Math.sin(thHalf * 2),
      x1 = cx + cosTh0 - t * sinTh0,
      y1 = cy + sinTh0 + t * cosTh0,
      x3 = cx + cosTh1,
      y3 = cy + sinTh1,
      x2 = x3 + t * sinTh1,
      y2 = y3 - t * cosTh1;

    segmentToBezierCache[argsString] = [
      a00 * x1 + a01 * y1,
      a10 * x1 + a11 * y1,
      a00 * x2 + a01 * y2,
      a10 * x2 + a11 * y2,
      a00 * x3 + a01 * y3,
      a10 * x3 + a11 * y3
    ];

    return segmentToBezierCache[argsString];
  }

  /**
   * Draws arc
   * @param {CanvasRenderingContext2D} ctx
   * @param {Number} x
   * @param {Number} y
   * @param {Array} coords
   */
  fabric.util.drawArc = function (ctx, x, y, coords) {
    var rx = coords[0],
      ry = coords[1],
      rot = coords[2],
      large = coords[3],
      sweep = coords[4],
      ex = coords[5],
      ey = coords[6],
      segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
    for (var i = 0; i < segs.length; i++) {
      var bez = segmentToBezier.apply(this, segs[i]);
      ctx.bezierCurveTo.apply(ctx, bez);
    }
  };
})();

(function () {
  var slice = Array.prototype.slice;

  /* _ES5_COMPAT_START_ */

  if (!Array.prototype.indexOf) {
    /**
     * Finds index of an element in an array
     * @param {Any} searchElement
     * @param {Number} [fromIndex]
     * @return {Number}
     */
    Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
      if (this === void 0 || this === null) {
        throw new TypeError();
      }
      var t = Object(this),
        len = t.length >>> 0;
      if (len === 0) {
        return -1;
      }
      var n = 0;
      if (arguments.length > 0) {
        n = Number(arguments[1]);
        if (n !== n) {
          // shortcut for verifying if it's NaN
          n = 0;
        } else if (
          n !== 0 &&
          n !== Number.POSITIVE_INFINITY &&
          n !== Number.NEGATIVE_INFINITY
        ) {
          n = (n > 0 || -1) * Math.floor(Math.abs(n));
        }
      }
      if (n >= len) {
        return -1;
      }
      var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
      for (; k < len; k++) {
        if (k in t && t[k] === searchElement) {
          return k;
        }
      }
      return -1;
    };
  }

  if (!Array.prototype.forEach) {
    /**
     * Iterates an array, invoking callback for each element
     * @param {Function} fn Callback to invoke for each element
     * @param {Object} [context] Context to invoke callback in
     * @return {Array}
     */
    Array.prototype.forEach = function (fn, context) {
      for (var i = 0, len = this.length >>> 0; i < len; i++) {
        if (i in this) {
          fn.call(context, this[i], i, this);
        }
      }
    };
  }

  if (!Array.prototype.map) {
    /**
     * Returns a result of iterating over an array, invoking callback for each element
     * @param {Function} fn Callback to invoke for each element
     * @param {Object} [context] Context to invoke callback in
     * @return {Array}
     */
    Array.prototype.map = function (fn, context) {
      var result = [];
      for (var i = 0, len = this.length >>> 0; i < len; i++) {
        if (i in this) {
          result[i] = fn.call(context, this[i], i, this);
        }
      }
      return result;
    };
  }

  if (!Array.prototype.every) {
    /**
     * Returns true if a callback returns truthy value for all elements in an array
     * @param {Function} fn Callback to invoke for each element
     * @param {Object} [context] Context to invoke callback in
     * @return {Boolean}
     */
    Array.prototype.every = function (fn, context) {
      for (var i = 0, len = this.length >>> 0; i < len; i++) {
        if (i in this && !fn.call(context, this[i], i, this)) {
          return false;
        }
      }
      return true;
    };
  }

  if (!Array.prototype.some) {
    /**
     * Returns true if a callback returns truthy value for at least one element in an array
     * @param {Function} fn Callback to invoke for each element
     * @param {Object} [context] Context to invoke callback in
     * @return {Boolean}
     */
    Array.prototype.some = function (fn, context) {
      for (var i = 0, len = this.length >>> 0; i < len; i++) {
        if (i in this && fn.call(context, this[i], i, this)) {
          return true;
        }
      }
      return false;
    };
  }

  if (!Array.prototype.filter) {
    /**
     * Returns the result of iterating over elements in an array
     * @param {Function} fn Callback to invoke for each element
     * @param {Object} [context] Context to invoke callback in
     * @return {Array}
     */
    Array.prototype.filter = function (fn, context) {
      var result = [],
        val;
      for (var i = 0, len = this.length >>> 0; i < len; i++) {
        if (i in this) {
          val = this[i]; // in case fn mutates this
          if (fn.call(context, val, i, this)) {
            result.push(val);
          }
        }
      }
      return result;
    };
  }

  if (!Array.prototype.reduce) {
    /**
     * Returns "folded" (reduced) result of iterating over elements in an array
     * @param {Function} fn Callback to invoke for each element
     * @param {Object} [initial] Object to use as the first argument to the first call of the callback
     * @return {Any}
     */
    Array.prototype.reduce = function (fn /*, initial*/) {
      var len = this.length >>> 0,
        i = 0,
        rv;

      if (arguments.length > 1) {
        rv = arguments[1];
      } else {
        do {
          if (i in this) {
            rv = this[i++];
            break;
          }
          // if array contains no values, no initial value to return
          if (++i >= len) {
            throw new TypeError();
          }
        } while (true);
      }
      for (; i < len; i++) {
        if (i in this) {
          rv = fn.call(null, rv, this[i], i, this);
        }
      }
      return rv;
    };
  }

  /* _ES5_COMPAT_END_ */

  /**
   * Invokes method on all items in a given array
   * @memberOf fabric.util.array
   * @param {Array} array Array to iterate over
   * @param {String} method Name of a method to invoke
   * @return {Array}
   */
  function invoke(array, method) {
    var args = slice.call(arguments, 2),
      result = [];
    for (var i = 0, len = array.length; i < len; i++) {
      result[i] = args.length
        ? array[i][method].apply(array[i], args)
        : array[i][method].call(array[i]);
    }
    return result;
  }

  /**
   * Finds maximum value in array (not necessarily "first" one)
   * @memberOf fabric.util.array
   * @param {Array} array Array to iterate over
   * @param {String} byProperty
   * @return {Any}
   */
  function max(array, byProperty) {
    return find(array, byProperty, function (value1, value2) {
      return value1 >= value2;
    });
  }

  /**
   * Finds minimum value in array (not necessarily "first" one)
   * @memberOf fabric.util.array
   * @param {Array} array Array to iterate over
   * @param {String} byProperty
   * @return {Any}
   */
  function min(array, byProperty) {
    return find(array, byProperty, function (value1, value2) {
      return value1 < value2;
    });
  }

  /**
   * @private
   */
  function find(array, byProperty, condition) {
    if (!array || array.length === 0) return;

    var i = array.length - 1,
      result = byProperty ? array[i][byProperty] : array[i];
    if (byProperty) {
      while (i--) {
        if (condition(array[i][byProperty], result)) {
          result = array[i][byProperty];
        }
      }
    } else {
      while (i--) {
        if (condition(array[i], result)) {
          result = array[i];
        }
      }
    }
    return result;
  }

  /**
   * @namespace fabric.util.array
   */
  fabric.util.array = {
    invoke: invoke,
    min: min,
    max: max
  };
})();

(function () {
  /**
   * Copies all enumerable properties of one object to another
   * @memberOf fabric.util.object
   * @param {Object} destination Where to copy to
   * @param {Object} source Where to copy from
   * @return {Object}
   */
  function extend(destination, source) {
    // JScript DontEnum bug is not taken care of
    for (var property in source) {
      destination[property] = source[property];
    }
    return destination;
  }

  /**
   * Creates an empty object and copies all enumerable properties of another object to it
   * @memberOf fabric.util.object
   * @param {Object} object Object to clone
   * @return {Object}
   */
  function clone(object) {
    return extend({}, object);
  }

  /** @namespace fabric.util.object */
  fabric.util.object = {
    extend: extend,
    clone: clone
  };
})();

(function () {
  /* _ES5_COMPAT_START_ */
  if (!String.prototype.trim) {
    /**
     * Trims a string (removing whitespace from the beginning and the end)
     * @function external:String#trim
     * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a>
     */
    String.prototype.trim = function () {
      // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
      return this.replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, "");
    };
  }
  /* _ES5_COMPAT_END_ */

  /**
   * Camelizes a string
   * @memberOf fabric.util.string
   * @param {String} string String to camelize
   * @return {String} Camelized version of a string
   */
  function camelize(string) {
    return string.replace(/-+(.)?/g, function (match, character) {
      return character ? character.toUpperCase() : "";
    });
  }

  /**
   * Capitalizes a string
   * @memberOf fabric.util.string
   * @param {String} string String to capitalize
   * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized
   * and other letters stay untouched, if false first letter is capitalized
   * and other letters are converted to lowercase.
   * @return {String} Capitalized version of a string
   */
  function capitalize(string, firstLetterOnly) {
    return (
      string.charAt(0).toUpperCase() +
      (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase())
    );
  }

  /**
   * Escapes XML in a string
   * @memberOf fabric.util.string
   * @param {String} string String to escape
   * @return {String} Escaped version of a string
   */
  function escapeXml(string) {
    return string
      .replace(/&/g, "&amp;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&apos;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;");
  }

  /**
   * String utilities
   * @namespace fabric.util.string
   */
  fabric.util.string = {
    camelize: camelize,
    capitalize: capitalize,
    escapeXml: escapeXml
  };
})();

/* _ES5_COMPAT_START_ */
(function () {
  var slice = Array.prototype.slice,
    apply = Function.prototype.apply,
    Dummy = function () {};

  if (!Function.prototype.bind) {
    /**
     * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)
     * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function#bind on MDN</a>
     * @param {Object} thisArg Object to bind function to
     * @param {Any[]} [...] Values to pass to a bound function
     * @return {Function}
     */
    Function.prototype.bind = function (thisArg) {
      var _this = this,
        args = slice.call(arguments, 1),
        bound;
      if (args.length) {
        bound = function () {
          return apply.call(
            _this,
            this instanceof Dummy ? this : thisArg,
            args.concat(slice.call(arguments))
          );
        };
      } else {
        /** @ignore */
        bound = function () {
          return apply.call(
            _this,
            this instanceof Dummy ? this : thisArg,
            arguments
          );
        };
      }
      Dummy.prototype = this.prototype;
      bound.prototype = new Dummy();

      return bound;
    };
  }
})();
/* _ES5_COMPAT_END_ */

(function () {
  var slice = Array.prototype.slice,
    emptyFunction = function () {},
    IS_DONTENUM_BUGGY = (function () {
      for (var p in { toString: 1 }) {
        if (p === "toString") {
          return false;
        }
      }
      return true;
    })(),
    /** @ignore */
    addMethods = function (klass, source, parent) {
      for (var property in source) {
        if (
          property in klass.prototype &&
          typeof klass.prototype[property] === "function" &&
          (source[property] + "").indexOf("callSuper") > -1
        ) {
          klass.prototype[property] = (function (property) {
            return function () {
              var superclass = this.constructor.superclass;
              this.constructor.superclass = parent;
              var returnValue = source[property].apply(this, arguments);
              this.constructor.superclass = superclass;

              if (property !== "initialize") {
                return returnValue;
              }
            };
          })(property);
        } else {
          klass.prototype[property] = source[property];
        }

        if (IS_DONTENUM_BUGGY) {
          if (source.toString !== Object.prototype.toString) {
            klass.prototype.toString = source.toString;
          }
          if (source.valueOf !== Object.prototype.valueOf) {
            klass.prototype.valueOf = source.valueOf;
          }
        }
      }
    };

  function Subclass() {}

  function callSuper(methodName) {
    var fn = this.constructor.superclass.prototype[methodName];
    return arguments.length > 1
      ? fn.apply(this, slice.call(arguments, 1))
      : fn.call(this);
  }

  /**
   * Helper for creation of "classes".
   * @memberOf fabric.util
   * @param parent optional "Class" to inherit from
   * @param properties Properties shared by all instances of this class
   *                  (be careful modifying objects defined here as this would affect all instances)
   */
  function createClass() {
    var parent = null,
      properties = slice.call(arguments, 0);

    if (typeof properties[0] === "function") {
      parent = properties.shift();
    }
    function klass() {
      this.initialize.apply(this, arguments);
    }

    klass.superclass = parent;
    klass.subclasses = [];

    if (parent) {
      Subclass.prototype = parent.prototype;
      klass.prototype = new Subclass();
      parent.subclasses.push(klass);
    }
    for (var i = 0, length = properties.length; i < length; i++) {
      addMethods(klass, properties[i], parent);
    }
    if (!klass.prototype.initialize) {
      klass.prototype.initialize = emptyFunction;
    }
    klass.prototype.constructor = klass;
    klass.prototype.callSuper = callSuper;
    return klass;
  }

  fabric.util.createClass = createClass;
})();

(function () {
  var unknown = "unknown";

  /* EVENT HANDLING */

  function areHostMethods(object) {
    var methodNames = Array.prototype.slice.call(arguments, 1),
      t,
      i,
      len = methodNames.length;
    for (i = 0; i < len; i++) {
      t = typeof object[methodNames[i]];
      if (!/^(?:function|object|unknown)$/.test(t)) {
        return false;
      }
    }
    return true;
  }

  /** @ignore */
  var getElement,
    setElement,
    getUniqueId = (function () {
      var uid = 0;
      return function (element) {
        return (
          element.__uniqueID || (element.__uniqueID = "uniqueID__" + uid++)
        );
      };
    })();

  (function () {
    var elements = {};
    /** @ignore */
    getElement = function (uid) {
      return elements[uid];
    };
    /** @ignore */
    setElement = function (uid, element) {
      elements[uid] = element;
    };
  })();

  function createListener(uid, handler) {
    return {
      handler: handler,
      wrappedHandler: createWrappedHandler(uid, handler)
    };
  }

  function createWrappedHandler(uid, handler) {
    return function (e) {
      handler.call(getElement(uid), e || fabric.window.event);
    };
  }

  function createDispatcher(uid, eventName) {
    return function (e) {
      if (handlers[uid] && handlers[uid][eventName]) {
        var handlersForEvent = handlers[uid][eventName];
        for (var i = 0, len = handlersForEvent.length; i < len; i++) {
          handlersForEvent[i].call(this, e || fabric.window.event);
        }
      }
    };
  }

  var shouldUseAddListenerRemoveListener =
      areHostMethods(
        fabric.document.documentElement,
        "addEventListener",
        "removeEventListener"
      ) &&
      areHostMethods(fabric.window, "addEventListener", "removeEventListener"),
    shouldUseAttachEventDetachEvent =
      areHostMethods(
        fabric.document.documentElement,
        "attachEvent",
        "detachEvent"
      ) && areHostMethods(fabric.window, "attachEvent", "detachEvent"),
    // IE branch
    listeners = {},
    // DOM L0 branch
    handlers = {},
    addListener,
    removeListener;

  if (shouldUseAddListenerRemoveListener) {
    /** @ignore */
    addListener = function (element, eventName, handler) {
      element.addEventListener(eventName, handler, false);
    };
    /** @ignore */
    removeListener = function (element, eventName, handler) {
      element.removeEventListener(eventName, handler, false);
    };
  } else if (shouldUseAttachEventDetachEvent) {
    /** @ignore */
    addListener = function (element, eventName, handler) {
      var uid = getUniqueId(element);
      setElement(uid, element);
      if (!listeners[uid]) {
        listeners[uid] = {};
      }
      if (!listeners[uid][eventName]) {
        listeners[uid][eventName] = [];
      }
      var listener = createListener(uid, handler);
      listeners[uid][eventName].push(listener);
      element.attachEvent("on" + eventName, listener.wrappedHandler);
    };
    /** @ignore */
    removeListener = function (element, eventName, handler) {
      var uid = getUniqueId(element),
        listener;
      if (listeners[uid] && listeners[uid][eventName]) {
        for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) {
          listener = listeners[uid][eventName][i];
          if (listener && listener.handler === handler) {
            element.detachEvent("on" + eventName, listener.wrappedHandler);
            listeners[uid][eventName][i] = null;
          }
        }
      }
    };
  } else {
    /** @ignore */
    addListener = function (element, eventName, handler) {
      var uid = getUniqueId(element);
      if (!handlers[uid]) {
        handlers[uid] = {};
      }
      if (!handlers[uid][eventName]) {
        handlers[uid][eventName] = [];
        var existingHandler = element["on" + eventName];
        if (existingHandler) {
          handlers[uid][eventName].push(existingHandler);
        }
        element["on" + eventName] = createDispatcher(uid, eventName);
      }
      handlers[uid][eventName].push(handler);
    };
    /** @ignore */
    removeListener = function (element, eventName, handler) {
      var uid = getUniqueId(element);
      if (handlers[uid] && handlers[uid][eventName]) {
        var handlersForEvent = handlers[uid][eventName];
        for (var i = 0, len = handlersForEvent.length; i < len; i++) {
          if (handlersForEvent[i] === handler) {
            handlersForEvent.splice(i, 1);
          }
        }
      }
    };
  }

  /**
   * Adds an event listener to an element
   * @function
   * @memberOf fabric.util
   * @param {HTMLElement} element
   * @param {String} eventName
   * @param {Function} handler
   */
  fabric.util.addListener = addListener;

  /**
   * Removes an event listener from an element
   * @function
   * @memberOf fabric.util
   * @param {HTMLElement} element
   * @param {String} eventName
   * @param {Function} handler
   */
  fabric.util.removeListener = removeListener;

  /**
   * Cross-browser wrapper for getting event's coordinates
   * @memberOf fabric.util
   * @param {Event} event Event object
   * @param {HTMLCanvasElement} upperCanvasEl &lt;canvas> element on which object selection is drawn
   */
  function getPointer(event, upperCanvasEl) {
    event || (event = fabric.window.event);

    var element =
        event.target ||
        (typeof event.srcElement !== unknown ? event.srcElement : null),
      scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl);

    return {
      x: pointerX(event) + scroll.left,
      y: pointerY(event) + scroll.top
    };
  }

  var pointerX = function (event) {
      // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element)
      // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]]
      // need to investigate later
      return typeof event.clientX !== unknown ? event.clientX : 0;
    },
    pointerY = function (event) {
      return typeof event.clientY !== unknown ? event.clientY : 0;
    };

  function _getPointer(event, pageProp, clientProp) {
    var touchProp = event.type === "touchend" ? "changedTouches" : "touches";

    return event[touchProp] && event[touchProp][0]
      ? event[touchProp][0][pageProp] -
          (event[touchProp][0][pageProp] - event[touchProp][0][clientProp]) ||
          event[clientProp]
      : event[clientProp];
  }

  if (fabric.isTouchSupported) {
    pointerX = function (event) {
      return _getPointer(event, "pageX", "clientX");
    };
    pointerY = function (event) {
      return _getPointer(event, "pageY", "clientY");
    };
  }

  fabric.util.getPointer = getPointer;

  fabric.util.object.extend(fabric.util, fabric.Observable);
})();

(function () {
  /**
   * Cross-browser wrapper for setting element's style
   * @memberOf fabric.util
   * @param {HTMLElement} element
   * @param {Object} styles
   * @return {HTMLElement} Element that was passed as a first argument
   */
  function setStyle(element, styles) {
    var elementStyle = element.style;
    if (!elementStyle) {
      return element;
    }
    if (typeof styles === "string") {
      element.style.cssText += ";" + styles;
      return styles.indexOf("opacity") > -1
        ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1])
        : element;
    }
    for (var property in styles) {
      if (property === "opacity") {
        setOpacity(element, styles[property]);
      } else {
        var normalizedProperty =
          property === "float" || property === "cssFloat"
            ? typeof elementStyle.styleFloat === "undefined"
              ? "cssFloat"
              : "styleFloat"
            : property;
        elementStyle[normalizedProperty] = styles[property];
      }
    }
    return element;
  }

  var parseEl = fabric.document.createElement("div"),
    supportsOpacity = typeof parseEl.style.opacity === "string",
    supportsFilters = typeof parseEl.style.filter === "string",
    reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,
    /** @ignore */
    setOpacity = function (element) {
      return element;
    };

  if (supportsOpacity) {
    /** @ignore */
    setOpacity = function (element, value) {
      element.style.opacity = value;
      return element;
    };
  } else if (supportsFilters) {
    /** @ignore */
    setOpacity = function (element, value) {
      var es = element.style;
      if (element.currentStyle && !element.currentStyle.hasLayout) {
        es.zoom = 1;
      }
      if (reOpacity.test(es.filter)) {
        value = value >= 0.9999 ? "" : "alpha(opacity=" + value * 100 + ")";
        es.filter = es.filter.replace(reOpacity, value);
      } else {
        es.filter += " alpha(opacity=" + value * 100 + ")";
      }
      return element;
    };
  }

  fabric.util.setStyle = setStyle;
})();

(function () {
  var _slice = Array.prototype.slice;

  /**
   * Takes id and returns an element with that id (if one exists in a document)
   * @memberOf fabric.util
   * @param {String|HTMLElement} id
   * @return {HTMLElement|null}
   */
  function getById(id) {
    return typeof id === "string" ? fabric.document.getElementById(id) : id;
  }

  var sliceCanConvertNodelists,
    /**
     * Converts an array-like object (e.g. arguments or NodeList) to an array
     * @memberOf fabric.util
     * @param {Object} arrayLike
     * @return {Array}
     */
    toArray = function (arrayLike) {
      return _slice.call(arrayLike, 0);
    };

  try {
    sliceCanConvertNodelists =
      toArray(fabric.document.childNodes) instanceof Array;
  } catch (err) {}

  if (!sliceCanConvertNodelists) {
    toArray = function (arrayLike) {
      var arr = new Array(arrayLike.length),
        i = arrayLike.length;
      while (i--) {
        arr[i] = arrayLike[i];
      }
      return arr;
    };
  }

  /**
   * Creates specified element with specified attributes
   * @memberOf fabric.util
   * @param {String} tagName Type of an element to create
   * @param {Object} [attributes] Attributes to set on an element
   * @return {HTMLElement} Newly created element
   */
  function makeElement(tagName, attributes) {
    var el = fabric.document.createElement(tagName);
    for (var prop in attributes) {
      if (prop === "class") {
        el.className = attributes[prop];
      } else if (prop === "for") {
        el.htmlFor = attributes[prop];
      } else {
        el.setAttribute(prop, attributes[prop]);
      }
    }
    return el;
  }

  /**
   * Adds class to an element
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to add class to
   * @param {String} className Class to add to an element
   */
  function addClass(element, className) {
    console.log(1910);
    console.log(element);
    console.log(className);
    if (
      element &&
      (" " + element.className + " ").indexOf(" " + className + " ") === -1
    ) {
      element.className += (element.className ? " " : "") + className;
    }
  }

  /**
   * Wraps element with another element
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to wrap
   * @param {HTMLElement|String} wrapper Element to wrap with
   * @param {Object} [attributes] Attributes to set on a wrapper
   * @return {HTMLElement} wrapper
   */
  function wrapElement(element, wrapper, attributes) {
    if (typeof wrapper === "string") {
      wrapper = makeElement(wrapper, attributes);
    }
    if (element.parentNode) {
      element.parentNode.replaceChild(wrapper, element);
    }
    wrapper.appendChild(element);
    return wrapper;
  }

  /**
   * Returns element scroll offsets
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to operate on
   * @param {HTMLElement} upperCanvasEl Upper canvas element
   * @return {Object} Object with left/top values
   */
  function getScrollLeftTop(element, upperCanvasEl) {
    var firstFixedAncestor,
      origElement,
      left = 0,
      top = 0,
      docElement = fabric.document.documentElement,
      body = fabric.document.body || {
        scrollLeft: 0,
        scrollTop: 0
      };

    origElement = element;

    while (element && element.parentNode && !firstFixedAncestor) {
      element = element.parentNode;

      if (
        element !== fabric.document &&
        fabric.util.getElementStyle(element, "position") === "fixed"
      ) {
        firstFixedAncestor = element;
      }

      if (
        element !== fabric.document &&
        origElement !== upperCanvasEl &&
        fabric.util.getElementStyle(element, "position") === "absolute"
      ) {
        left = 0;
        top = 0;
      } else if (element === fabric.document) {
        left = body.scrollLeft || docElement.scrollLeft || 0;
        top = body.scrollTop || docElement.scrollTop || 0;
      } else {
        left += element.scrollLeft || 0;
        top += element.scrollTop || 0;
      }
    }

    return { left: left, top: top };
  }

  /**
   * Returns offset for a given element
   * @function
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to get offset for
   * @return {Object} Object with "left" and "top" properties
   */
  function getElementOffset(element) {
    var docElem,
      doc = element && element.ownerDocument,
      box = { left: 0, top: 0 },
      offset = { left: 0, top: 0 },
      scrollLeftTop,
      offsetAttributes = {
        borderLeftWidth: "left",
        borderTopWidth: "top",
        paddingLeft: "left",
        paddingTop: "top"
      };

    if (!doc) {
      return { left: 0, top: 0 };
    }

    for (var attr in offsetAttributes) {
      offset[offsetAttributes[attr]] +=
        parseInt(getElementStyle(element, attr), 10) || 0;
    }

    docElem = doc.documentElement;
    if (typeof element.getBoundingClientRect !== "undefined") {
      box = element.getBoundingClientRect();
    }

    scrollLeftTop = fabric.util.getScrollLeftTop(element, null);

    return {
      left:
        box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
      top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top
    };
  }

  /**
   * Returns style attribute value of a given element
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to get style attribute for
   * @param {String} attr Style attribute to get for element
   * @return {String} Style attribute value of the given element.
   */
  var getElementStyle;
  if (
    fabric.document.defaultView &&
    fabric.document.defaultView.getComputedStyle
  ) {
    getElementStyle = function (element, attr) {
      return fabric.document.defaultView.getComputedStyle(element, null)[attr];
    };
  } else {
    getElementStyle = function (element, attr) {
      var value = element.style[attr];
      if (!value && element.currentStyle) {
        value = element.currentStyle[attr];
      }
      return value;
    };
  }

  (function () {
    var style = fabric.document.documentElement.style,
      selectProp =
        "userSelect" in style
          ? "userSelect"
          : "MozUserSelect" in style
          ? "MozUserSelect"
          : "WebkitUserSelect" in style
          ? "WebkitUserSelect"
          : "KhtmlUserSelect" in style
          ? "KhtmlUserSelect"
          : "";

    /**
     * Makes element unselectable
     * @memberOf fabric.util
     * @param {HTMLElement} element Element to make unselectable
     * @return {HTMLElement} Element that was passed in
     */
    function makeElementUnselectable(element) {
      if (typeof element.onselectstart !== "undefined") {
        element.onselectstart = fabric.util.falseFunction;
      }
      if (selectProp) {
        element.style[selectProp] = "none";
      } else if (typeof element.unselectable === "string") {
        element.unselectable = "on";
      }
      return element;
    }

    /**
     * Makes element selectable
     * @memberOf fabric.util
     * @param {HTMLElement} element Element to make selectable
     * @return {HTMLElement} Element that was passed in
     */
    function makeElementSelectable(element) {
      if (typeof element.onselectstart !== "undefined") {
        element.onselectstart = null;
      }
      if (selectProp) {
        element.style[selectProp] = "";
      } else if (typeof element.unselectable === "string") {
        element.unselectable = "";
      }
      return element;
    }

    fabric.util.makeElementUnselectable = makeElementUnselectable;
    fabric.util.makeElementSelectable = makeElementSelectable;
  })();

  (function () {
    /**
     * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
     * @memberOf fabric.util
     * @param {String} url URL of a script to load
     * @param {Function} callback Callback to execute when script is finished loading
     */
    function getScript(url, callback) {
      var headEl = fabric.document.getElementsByTagName("head")[0],
        scriptEl = fabric.document.createElement("script"),
        loading = true;

      /** @ignore */
      scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function (
        e
      ) {
        if (loading) {
          if (
            typeof this.readyState === "string" &&
            this.readyState !== "loaded" &&
            this.readyState !== "complete"
          )
            return;
          loading = false;
          callback(e || fabric.window.event);
          scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
        }
      };
      scriptEl.src = url;
      headEl.appendChild(scriptEl);
      // causes issue in Opera
      // headEl.removeChild(scriptEl);
    }

    fabric.util.getScript = getScript;
  })();

  fabric.util.getById = getById;
  fabric.util.toArray = toArray;
  fabric.util.makeElement = makeElement;
  fabric.util.addClass = addClass;
  fabric.util.wrapElement = wrapElement;
  fabric.util.getScrollLeftTop = getScrollLeftTop;
  fabric.util.getElementOffset = getElementOffset;
  fabric.util.getElementStyle = getElementStyle;
})();

(function () {
  function addParamToUrl(url, param) {
    return url + (/\?/.test(url) ? "&" : "?") + param;
  }

  var makeXHR = (function () {
    var factories = [
      function () {
        return new ActiveXObject("Microsoft.XMLHTTP");
      },
      function () {
        return new ActiveXObject("Msxml2.XMLHTTP");
      },
      function () {
        return new ActiveXObject("Msxml2.XMLHTTP.3.0");
      },
      function () {
        return new XMLHttpRequest();
      }
    ];
    for (var i = factories.length; i--; ) {
      try {
        var req = factories[i]();
        if (req) {
          return factories[i];
        }
      } catch (err) {}
    }
  })();

  function emptyFn() {}

  /**
   * Cross-browser abstraction for sending XMLHttpRequest
   * @memberOf fabric.util
   * @param {String} url URL to send XMLHttpRequest to
   * @param {Object} [options] Options object
   * @param {String} [options.method="GET"]
   * @param {Function} options.onComplete Callback to invoke when request is completed
   * @return {XMLHttpRequest} request
   */
  function request(url, options) {
    options || (options = {});

    var method = options.method ? options.method.toUpperCase() : "GET",
      onComplete = options.onComplete || function () {},
      xhr = makeXHR(),
      body;

    /** @ignore */
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        onComplete(xhr);
        xhr.onreadystatechange = emptyFn;
      }
    };

    if (method === "GET") {
      body = null;
      if (typeof options.parameters === "string") {
        url = addParamToUrl(url, options.parameters);
      }
    }

    xhr.open(method, url, true);

    if (method === "POST" || method === "PUT") {
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    }

    xhr.send(body);
    return xhr;
  }

  fabric.util.request = request;
})();

/**
 * Wrapper around `console.log` (when available)
 * @param {Any} values Values to log
 */
fabric.log = function () {};

/**
 * Wrapper around `console.warn` (when available)
 * @param {Any} Values to log as a warning
 */
fabric.warn = function () {};

if (typeof console !== "undefined") {
  ["log", "warn"].forEach(function (methodName) {
    if (
      typeof console[methodName] !== "undefined" &&
      console[methodName].apply
    ) {
      fabric[methodName] = function () {
        return console[methodName].apply(console, arguments);
      };
    }
  });
}

(function () {
  /**
   * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
   * @memberOf fabric.util
   * @param {Object} [options] Animation options
   * @param {Function} [options.onChange] Callback; invoked on every value change
   * @param {Function} [options.onComplete] Callback; invoked when value change is completed
   * @param {Number} [options.startValue=0] Starting value
   * @param {Number} [options.endValue=100] Ending value
   * @param {Number} [options.byValue=100] Value to modify the property by
   * @param {Function} [options.easing] Easing function
   * @param {Number} [options.duration=500] Duration of change (in ms)
   */
  function animate(options) {
    requestAnimFrame(function (timestamp) {
      options || (options = {});

      var start = timestamp || +new Date(),
        duration = options.duration || 500,
        finish = start + duration,
        time,
        onChange = options.onChange || function () {},
        abort =
          options.abort ||
          function () {
            return false;
          },
        easing =
          options.easing ||
          function (t, b, c, d) {
            return -c * Math.cos((t / d) * (Math.PI / 2)) + c + b;
          },
        startValue = "startValue" in options ? options.startValue : 0,
        endValue = "endValue" in options ? options.endValue : 100,
        byValue = options.byValue || endValue - startValue;

      options.onStart && options.onStart();

      (function tick(ticktime) {
        time = ticktime || +new Date();
        var currentTime = time > finish ? duration : time - start;
        if (abort()) {
          options.onComplete && options.onComplete();
          return;
        }
        onChange(easing(currentTime, startValue, byValue, duration));
        if (time > finish) {
          options.onComplete && options.onComplete();
          return;
        }
        requestAnimFrame(tick);
      })(start);
    });
  }

  var _requestAnimFrame =
    fabric.window.requestAnimationFrame ||
    fabric.window.webkitRequestAnimationFrame ||
    fabric.window.mozRequestAnimationFrame ||
    fabric.window.oRequestAnimationFrame ||
    fabric.window.msRequestAnimationFrame ||
    function (callback) {
      fabric.window.setTimeout(callback, 1000 / 60);
    };
  /**
   * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
   * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method
   * @memberOf fabric.util
   * @param {Function} callback Callback to invoke
   * @param {DOMElement} element optional Element to associate with animation
   */
  function requestAnimFrame() {
    return _requestAnimFrame.apply(fabric.window, arguments);
  }

  fabric.util.animate = animate;
  fabric.util.requestAnimFrame = requestAnimFrame;
})();

(function () {
  function normalize(a, c, p, s) {
    if (a < Math.abs(c)) {
      a = c;
      s = p / 4;
    } else {
      s = (p / (2 * Math.PI)) * Math.asin(c / a);
    }
    return { a: a, c: c, p: p, s: s };
  }

  function elastic(opts, t, d) {
    return (
      opts.a *
      Math.pow(2, 10 * (t -= 1)) *
      Math.sin(((t * d - opts.s) * (2 * Math.PI)) / opts.p)
    );
  }

  /**
   * Cubic easing out
   * @memberOf fabric.util.ease
   */
  function easeOutCubic(t, b, c, d) {
    return c * ((t = t / d - 1) * t * t + 1) + b;
  }

  /**
   * Cubic easing in and out
   * @memberOf fabric.util.ease
   */
  function easeInOutCubic(t, b, c, d) {
    t /= d / 2;
    if (t < 1) {
      return (c / 2) * t * t * t + b;
    }
    return (c / 2) * ((t -= 2) * t * t + 2) + b;
  }

  /**
   * Quartic easing in
   * @memberOf fabric.util.ease
   */
  function easeInQuart(t, b, c, d) {
    return c * (t /= d) * t * t * t + b;
  }

  /**
   * Quartic easing out
   * @memberOf fabric.util.ease
   */
  function easeOutQuart(t, b, c, d) {
    return -c * ((t = t / d - 1) * t * t * t - 1) + b;
  }

  /**
   * Quartic easing in and out
   * @memberOf fabric.util.ease
   */
  function easeInOutQuart(t, b, c, d) {
    t /= d / 2;
    if (t < 1) {
      return (c / 2) * t * t * t * t + b;
    }
    return (-c / 2) * ((t -= 2) * t * t * t - 2) + b;
  }

  /**
   * Quintic easing in
   * @memberOf fabric.util.ease
   */
  function easeInQuint(t, b, c, d) {
    return c * (t /= d) * t * t * t * t + b;
  }

  /**
   * Quintic easing out
   * @memberOf fabric.util.ease
   */
  function easeOutQuint(t, b, c, d) {
    return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
  }

  /**
   * Quintic easing in and out
   * @memberOf fabric.util.ease
   */
  function easeInOutQuint(t, b, c, d) {
    t /= d / 2;
    if (t < 1) {
      return (c / 2) * t * t * t * t * t + b;
    }
    return (c / 2) * ((t -= 2) * t * t * t * t + 2) + b;
  }

  /**
   * Sinusoidal easing in
   * @memberOf fabric.util.ease
   */
  function easeInSine(t, b, c, d) {
    return -c * Math.cos((t / d) * (Math.PI / 2)) + c + b;
  }

  /**
   * Sinusoidal easing out
   * @memberOf fabric.util.ease
   */
  function easeOutSine(t, b, c, d) {
    return c * Math.sin((t / d) * (Math.PI / 2)) + b;
  }

  /**
   * Sinusoidal easing in and out
   * @memberOf fabric.util.ease
   */
  function easeInOutSine(t, b, c, d) {
    return (-c / 2) * (Math.cos((Math.PI * t) / d) - 1) + b;
  }

  /**
   * Exponential easing in
   * @memberOf fabric.util.ease
   */
  function easeInExpo(t, b, c, d) {
    return t === 0 ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
  }

  /**
   * Exponential easing out
   * @memberOf fabric.util.ease
   */
  function easeOutExpo(t, b, c, d) {
    return t === d ? b + c : c * (-Math.pow(2, (-10 * t) / d) + 1) + b;
  }

  /**
   * Exponential easing in and out
   * @memberOf fabric.util.ease
   */
  function easeInOutExpo(t, b, c, d) {
    if (t === 0) {
      return b;
    }
    if (t === d) {
      return b + c;
    }
    t /= d / 2;
    if (t < 1) {
      return (c / 2) * Math.pow(2, 10 * (t - 1)) + b;
    }
    return (c / 2) * (-Math.pow(2, -10 * --t) + 2) + b;
  }

  /**
   * Circular easing in
   * @memberOf fabric.util.ease
   */
  function easeInCirc(t, b, c, d) {
    return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
  }

  /**
   * Circular easing out
   * @memberOf fabric.util.ease
   */
  function easeOutCirc(t, b, c, d) {
    return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
  }

  /**
   * Circular easing in and out
   * @memberOf fabric.util.ease
   */
  function easeInOutCirc(t, b, c, d) {
    t /= d / 2;
    if (t < 1) {
      return (-c / 2) * (Math.sqrt(1 - t * t) - 1) + b;
    }
    return (c / 2) * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
  }

  /**
   * Elastic easing in
   * @memberOf fabric.util.ease
   */
  function easeInElastic(t, b, c, d) {
    var s = 1.70158,
      p = 0,
      a = c;
    if (t === 0) {
      return b;
    }
    t /= d;
    if (t === 1) {
      return b + c;
    }
    if (!p) {
      p = d * 0.3;
    }
    var opts = normalize(a, c, p, s);
    return -elastic(opts, t, d) + b;
  }

  /**
   * Elastic easing out
   * @memberOf fabric.util.ease
   */
  function easeOutElastic(t, b, c, d) {
    var s = 1.70158,
      p = 0,
      a = c;
    if (t === 0) {
      return b;
    }
    t /= d;
    if (t === 1) {
      return b + c;
    }
    if (!p) {
      p = d * 0.3;
    }
    var opts = normalize(a, c, p, s);
    return (
      opts.a *
        Math.pow(2, -10 * t) *
        Math.sin(((t * d - opts.s) * (2 * Math.PI)) / opts.p) +
      opts.c +
      b
    );
  }

  /**
   * Elastic easing in and out
   * @memberOf fabric.util.ease
   */
  function easeInOutElastic(t, b, c, d) {
    var s = 1.70158,
      p = 0,
      a = c;
    if (t === 0) {
      return b;
    }
    t /= d / 2;
    if (t === 2) {
      return b + c;
    }
    if (!p) {
      p = d * (0.3 * 1.5);
    }
    var opts = normalize(a, c, p, s);
    if (t < 1) {
      return -0.5 * elastic(opts, t, d) + b;
    }
    return (
      opts.a *
        Math.pow(2, -10 * (t -= 1)) *
        Math.sin(((t * d - opts.s) * (2 * Math.PI)) / opts.p) *
        0.5 +
      opts.c +
      b
    );
  }

  /**
   * Backwards easing in
   * @memberOf fabric.util.ease
   */
  function easeInBack(t, b, c, d, s) {
    if (s === undefined) {
      s = 1.70158;
    }
    return c * (t /= d) * t * ((s + 1) * t - s) + b;
  }

  /**
   * Backwards easing out
   * @memberOf fabric.util.ease
   */
  function easeOutBack(t, b, c, d, s) {
    if (s === undefined) {
      s = 1.70158;
    }
    return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
  }

  /**
   * Backwards easing in and out
   * @memberOf fabric.util.ease
   */
  function easeInOutBack(t, b, c, d, s) {
    if (s === undefined) {
      s = 1.70158;
    }
    t /= d / 2;
    if (t < 1) {
      return (c / 2) * (t * t * (((s *= 1.525) + 1) * t - s)) + b;
    }
    return (c / 2) * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b;
  }

  /**
   * Bouncing easing in
   * @memberOf fabric.util.ease
   */
  function easeInBounce(t, b, c, d) {
    return c - easeOutBounce(d - t, 0, c, d) + b;
  }

  /**
   * Bouncing easing out
   * @memberOf fabric.util.ease
   */
  function easeOutBounce(t, b, c, d) {
    if ((t /= d) < 1 / 2.75) {
      return c * (7.5625 * t * t) + b;
    } else if (t < 2 / 2.75) {
      return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b;
    } else if (t < 2.5 / 2.75) {
      return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b;
    } else {
      return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b;
    }
  }

  /**
   * Bouncing easing in and out
   * @memberOf fabric.util.ease
   */
  function easeInOutBounce(t, b, c, d) {
    if (t < d / 2) {
      return easeInBounce(t * 2, 0, c, d) * 0.5 + b;
    }
    return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
  }

  /**
   * Easing functions
   * See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a>
   * @namespace fabric.util.ease
   */
  fabric.util.ease = {
    /**
     * Quadratic easing in
     * @memberOf fabric.util.ease
     */
    easeInQuad: function (t, b, c, d) {
      return c * (t /= d) * t + b;
    },

    /**
     * Quadratic easing out
     * @memberOf fabric.util.ease
     */
    easeOutQuad: function (t, b, c, d) {
      return -c * (t /= d) * (t - 2) + b;
    },

    /**
     * Quadratic easing in and out
     * @memberOf fabric.util.ease
     */
    easeInOutQuad: function (t, b, c, d) {
      t /= d / 2;
      if (t < 1) {
        return (c / 2) * t * t + b;
      }
      return (-c / 2) * (--t * (t - 2) - 1) + b;
    },

    /**
     * Cubic easing in
     * @memberOf fabric.util.ease
     */
    easeInCubic: function (t, b, c, d) {
      return c * (t /= d) * t * t + b;
    },

    easeOutCubic: easeOutCubic,
    easeInOutCubic: easeInOutCubic,
    easeInQuart: easeInQuart,
    easeOutQuart: easeOutQuart,
    easeInOutQuart: easeInOutQuart,
    easeInQuint: easeInQuint,
    easeOutQuint: easeOutQuint,
    easeInOutQuint: easeInOutQuint,
    easeInSine: easeInSine,
    easeOutSine: easeOutSine,
    easeInOutSine: easeInOutSine,
    easeInExpo: easeInExpo,
    easeOutExpo: easeOutExpo,
    easeInOutExpo: easeInOutExpo,
    easeInCirc: easeInCirc,
    easeOutCirc: easeOutCirc,
    easeInOutCirc: easeInOutCirc,
    easeInElastic: easeInElastic,
    easeOutElastic: easeOutElastic,
    easeInOutElastic: easeInOutElastic,
    easeInBack: easeInBack,
    easeOutBack: easeOutBack,
    easeInOutBack: easeInOutBack,
    easeInBounce: easeInBounce,
    easeOutBounce: easeOutBounce,
    easeInOutBounce: easeInOutBounce
  };
})();

(function (global) {
  "use strict";

  /**
   * @name fabric
   * @namespace
   */

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend,
    capitalize = fabric.util.string.capitalize,
    clone = fabric.util.object.clone,
    toFixed = fabric.util.toFixed,
    parseUnit = fabric.util.parseUnit,
    multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
    attributesMap = {
      cx: "left",
      x: "left",
      r: "radius",
      cy: "top",
      y: "top",
      display: "visible",
      visibility: "visible",
      transform: "transformMatrix",
      "fill-opacity": "fillOpacity",
      "fill-rule": "fillRule",
      "font-family": "fontFamily",
      "font-size": "fontSize",
      "font-style": "fontStyle",
      "font-weight": "fontWeight",
      "stroke-dasharray": "strokeDashArray",
      "stroke-linecap": "strokeLineCap",
      "stroke-linejoin": "strokeLineJoin",
      "stroke-miterlimit": "strokeMiterLimit",
      "stroke-opacity": "strokeOpacity",
      "stroke-width": "strokeWidth",
      "text-decoration": "textDecoration",
      "text-anchor": "originX"
    },
    colorAttributes = {
      stroke: "strokeOpacity",
      fill: "fillOpacity"
    };

  function normalizeAttr(attr) {
    // transform attribute names
    if (attr in attributesMap) {
      return attributesMap[attr];
    }
    return attr;
  }

  function normalizeValue(attr, value, parentAttributes) {
    var isArray = Object.prototype.toString.call(value) === "[object Array]",
      parsed;

    if ((attr === "fill" || attr === "stroke") && value === "none") {
      value = "";
    } else if (attr === "fillRule") {
      value = value === "evenodd" ? "destination-over" : value;
    } else if (attr === "strokeDashArray") {
      value = value
        .replace(/,/g, " ")
        .split(/\s+/)
        .map(function (n) {
          return parseInt(n);
        });
    } else if (attr === "transformMatrix") {
      if (parentAttributes && parentAttributes.transformMatrix) {
        value = multiplyTransformMatrices(
          parentAttributes.transformMatrix,
          fabric.parseTransformAttribute(value)
        );
      } else {
        value = fabric.parseTransformAttribute(value);
      }
    } else if (attr === "visible") {
      value = value === "none" || value === "hidden" ? false : true;
      // display=none on parent element always takes precedence over child element
      if (parentAttributes && parentAttributes.visible === false) {
        value = false;
      }
    } else if (attr === "originX" /* text-anchor */) {
      value = value === "start" ? "left" : value === "end" ? "right" : "center";
    } else {
      parsed = isArray ? value.map(parseUnit) : parseUnit(value);
    }

    return !isArray && isNaN(parsed) ? value : parsed;
  }

  /**
   * @private
   * @param {Object} attributes Array of attributes to parse
   */
  function _setStrokeFillOpacity(attributes) {
    for (var attr in colorAttributes) {
      if (
        !attributes[attr] ||
        typeof attributes[colorAttributes[attr]] === "undefined"
      )
        continue;

      if (attributes[attr].indexOf("url(") === 0) continue;

      var color = new fabric.Color(attributes[attr]);
      attributes[attr] = color
        .setAlpha(
          toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)
        )
        .toRgba();

      delete attributes[colorAttributes[attr]];
    }
    return attributes;
  }

  /**
   * Parses "transform" attribute, returning an array of values
   * @static
   * @function
   * @memberOf fabric
   * @param {String} attributeValue String containing attribute value
   * @return {Array} Array of 6 elements representing transformation matrix
   */
  fabric.parseTransformAttribute = (function () {
    function rotateMatrix(matrix, args) {
      var angle = args[0];

      matrix[0] = Math.cos(angle);
      matrix[1] = Math.sin(angle);
      matrix[2] = -Math.sin(angle);
      matrix[3] = Math.cos(angle);
    }

    function scaleMatrix(matrix, args) {
      var multiplierX = args[0],
        multiplierY = args.length === 2 ? args[1] : args[0];

      matrix[0] = multiplierX;
      matrix[3] = multiplierY;
    }

    function skewXMatrix(matrix, args) {
      matrix[2] = args[0];
    }

    function skewYMatrix(matrix, args) {
      matrix[1] = args[0];
    }

    function translateMatrix(matrix, args) {
      matrix[4] = args[0];
      if (args.length === 2) {
        matrix[5] = args[1];
      }
    }

    // identity matrix
    var iMatrix = [
        1, // a
        0, // b
        0, // c
        1, // d
        0, // e
        0 // f
      ],
      // == begin transform regexp
      number = "(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",
      commaWsp = "(?:\\s+,?\\s*|,\\s*)",
      skewX = "(?:(skewX)\\s*\\(\\s*(" + number + ")\\s*\\))",
      skewY = "(?:(skewY)\\s*\\(\\s*(" + number + ")\\s*\\))",
      rotate =
        "(?:(rotate)\\s*\\(\\s*(" +
        number +
        ")(?:" +
        commaWsp +
        "(" +
        number +
        ")" +
        commaWsp +
        "(" +
        number +
        "))?\\s*\\))",
      scale =
        "(?:(scale)\\s*\\(\\s*(" +
        number +
        ")(?:" +
        commaWsp +
        "(" +
        number +
        "))?\\s*\\))",
      translate =
        "(?:(translate)\\s*\\(\\s*(" +
        number +
        ")(?:" +
        commaWsp +
        "(" +
        number +
        "))?\\s*\\))",
      matrix =
        "(?:(matrix)\\s*\\(\\s*" +
        "(" +
        number +
        ")" +
        commaWsp +
        "(" +
        number +
        ")" +
        commaWsp +
        "(" +
        number +
        ")" +
        commaWsp +
        "(" +
        number +
        ")" +
        commaWsp +
        "(" +
        number +
        ")" +
        commaWsp +
        "(" +
        number +
        ")" +
        "\\s*\\))",
      transform =
        "(?:" +
        matrix +
        "|" +
        translate +
        "|" +
        scale +
        "|" +
        rotate +
        "|" +
        skewX +
        "|" +
        skewY +
        ")",
      transforms =
        "(?:" + transform + "(?:" + commaWsp + transform + ")*" + ")",
      transformList = "^\\s*(?:" + transforms + "?)\\s*$",
      // http://www.w3.org/TR/SVG/coords.html#TransformAttribute
      reTransformList = new RegExp(transformList),
      // == end transform regexp

      reTransform = new RegExp(transform, "g");

    return function (attributeValue) {
      // start with identity matrix
      var matrix = iMatrix.concat(),
        matrices = [];

      // return if no argument was given or
      // an argument does not match transform attribute regexp
      if (
        !attributeValue ||
        (attributeValue && !reTransformList.test(attributeValue))
      ) {
        return matrix;
      }

      attributeValue.replace(reTransform, function (match) {
        var m = new RegExp(transform).exec(match).filter(function (match) {
            return match !== "" && match != null;
          }),
          operation = m[1],
          args = m.slice(2).map(parseFloat);

        switch (operation) {
          case "translate":
            translateMatrix(matrix, args);
            break;
          case "rotate":
            args[0] = fabric.util.degreesToRadians(args[0]);
            rotateMatrix(matrix, args);
            break;
          case "scale":
            scaleMatrix(matrix, args);
            break;
          case "skewX":
            skewXMatrix(matrix, args);
            break;
          case "skewY":
            skewYMatrix(matrix, args);
            break;
          case "matrix":
            matrix = args;
            break;
        }

        // snapshot current matrix into matrices array
        matrices.push(matrix.concat());
        // reset
        matrix = iMatrix.concat();
      });

      var combinedMatrix = matrices[0];
      while (matrices.length > 1) {
        matrices.shift();
        combinedMatrix = fabric.util.multiplyTransformMatrices(
          combinedMatrix,
          matrices[0]
        );
      }
      return combinedMatrix;
    };
  })();

  function parseFontDeclaration(value, oStyle) {
    // TODO: support non-px font size
    var match = value.match(
      /(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/
    );

    if (!match) return;

    var fontStyle = match[1],
      // font variant is not used
      // fontVariant = match[2],
      fontWeight = match[3],
      fontSize = match[4],
      lineHeight = match[5],
      fontFamily = match[6];

    if (fontStyle) {
      oStyle.fontStyle = fontStyle;
    }
    if (fontWeight) {
      oStyle.fontWeight = isNaN(parseFloat(fontWeight))
        ? fontWeight
        : parseFloat(fontWeight);
    }
    if (fontSize) {
      oStyle.fontSize = parseFloat(fontSize);
    }
    if (fontFamily) {
      oStyle.fontFamily = fontFamily;
    }
    if (lineHeight) {
      oStyle.lineHeight = lineHeight === "normal" ? 1 : lineHeight;
    }
  }

  /**
   * @private
   */
  function parseStyleString(style, oStyle) {
    var attr, value;
    style
      .replace(/;$/, "")
      .split(";")
      .forEach(function (chunk) {
        var pair = chunk.split(":");

        attr = normalizeAttr(pair[0].trim().toLowerCase());
        value = normalizeValue(attr, pair[1].trim());

        if (attr === "font") {
          parseFontDeclaration(value, oStyle);
        } else {
          oStyle[attr] = value;
        }
      });
  }

  /**
   * @private
   */
  function parseStyleObject(style, oStyle) {
    var attr, value;
    for (var prop in style) {
      if (typeof style[prop] === "undefined") continue;

      attr = normalizeAttr(prop.toLowerCase());
      value = normalizeValue(attr, style[prop]);

      if (attr === "font") {
        parseFontDeclaration(value, oStyle);
      } else {
        oStyle[attr] = value;
      }
    }
  }

  /**
   * @private
   */
  function getGlobalStylesForElement(element) {
    var nodeName = element.nodeName,
      className = element.getAttribute("class"),
      id = element.getAttribute("id"),
      styles = {};

    for (var rule in fabric.cssRules) {
      var ruleMatchesElement =
        (className && new RegExp("^\\." + className).test(rule)) ||
        (id && new RegExp("^#" + id).test(rule)) ||
        new RegExp("^" + nodeName).test(rule);

      if (ruleMatchesElement) {
        for (var property in fabric.cssRules[rule]) {
          var attr = normalizeAttr(property),
            value = normalizeValue(attr, fabric.cssRules[rule][property]);
          styles[attr] = value;
        }
      }
    }

    return styles;
  }

  /**
   * @private
   */
  function parseUseDirectives(doc) {
    var nodelist = doc.getElementsByTagName("use");
    while (nodelist.length) {
      var el = nodelist[0],
        xlink = el.getAttribute("xlink:href").substr(1),
        x = el.getAttribute("x") || 0,
        y = el.getAttribute("y") || 0,
        el2 = doc.getElementById(xlink).cloneNode(true),
        currentTrans =
          (el.getAttribute("transform") || "") +
          " translate(" +
          x +
          ", " +
          y +
          ")",
        parentNode;

      for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) {
        var attr = attrs.item(j);
        if (
          attr.nodeName === "x" ||
          attr.nodeName === "y" ||
          attr.nodeName === "xlink:href"
        )
          continue;

        if (attr.nodeName === "transform") {
          currentTrans = currentTrans + " " + attr.nodeValue;
        } else {
          el2.setAttribute(attr.nodeName, attr.nodeValue);
        }
      }

      el2.setAttribute("transform", currentTrans);
      el2.removeAttribute("id");
      parentNode = el.parentNode;
      parentNode.replaceChild(el2, el);
    }
  }

  /**
   * Add a <g> element that envelop all SCG elements and makes the viewbox transformMatrix descend on all elements
   */
  function addSvgTransform(doc, matrix) {
    matrix[3] = matrix[0] = matrix[0] > matrix[3] ? matrix[3] : matrix[0];
    if (
      !(
        matrix[0] !== 1 ||
        matrix[3] !== 1 ||
        matrix[4] !== 0 ||
        matrix[5] !== 0
      )
    )
      return;
    // default is to preserve aspect ratio
    // preserveAspectRatio attribute to be implemented
    var el = document.createElement("g");
    while (doc.firstChild != null) {
      var node = doc.firstChild;
      el.appendChild(node);
    }
    el.setAttribute(
      "transform",
      "matrix(" +
        matrix[0] +
        " " +
        matrix[1] +
        " " +
        matrix[2] +
        " " +
        matrix[3] +
        " " +
        matrix[4] +
        " " +
        matrix[5] +
        ")"
    );
    doc.appendChild(el);
  }

  /**
   * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
   * @static
   * @function
   * @memberOf fabric
   * @param {SVGDocument} doc SVG document to parse
   * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document).
   * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
   */
  fabric.parseSVGDocument = (function () {
    var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,
      // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
      // \d doesn't quite cut it (as we need to match an actual float number)

      // matches, e.g.: +14.56e-12, etc.
      reNum = "(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",
      reViewBoxAttrValue = new RegExp(
        "^" +
          "\\s*(" +
          reNum +
          "+)\\s*,?" +
          "\\s*(" +
          reNum +
          "+)\\s*,?" +
          "\\s*(" +
          reNum +
          "+)\\s*,?" +
          "\\s*(" +
          reNum +
          "+)\\s*" +
          "$"
      );

    function hasAncestorWithNodeName(element, nodeName) {
      while (element && (element = element.parentNode)) {
        if (nodeName.test(element.nodeName)) {
          return true;
        }
      }
      return false;
    }

    return function (doc, callback, reviver) {
      if (!doc) return;
      var startTime = new Date();

      parseUseDirectives(doc);

      var viewBoxAttr = doc.getAttribute("viewBox"),
        widthAttr = parseFloat(doc.getAttribute("width")),
        heightAttr = parseFloat(doc.getAttribute("height")),
        viewBoxWidth,
        viewBoxHeight;

      if (
        viewBoxAttr &&
        (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))
      ) {
        var minX = parseFloat(viewBoxAttr[1]),
          minY = parseFloat(viewBoxAttr[2]),
          scaleX = 1,
          scaleY = 1;
        viewBoxWidth = parseFloat(viewBoxAttr[3]);
        viewBoxHeight = parseFloat(viewBoxAttr[4]);
        if (widthAttr && widthAttr !== viewBoxWidth) {
          scaleX = widthAttr / viewBoxWidth;
        }
        if (heightAttr && heightAttr !== viewBoxHeight) {
          scaleY = heightAttr / viewBoxHeight;
        }
        addSvgTransform(doc, [scaleX, 0, 0, scaleY, -minX, -minY]);
      }

      var descendants = fabric.util.toArray(doc.getElementsByTagName("*"));

      if (descendants.length === 0 && fabric.isLikelyNode) {
        // we're likely in node, where "o3-xml" library fails to gEBTN("*")
        // https://github.com/ajaxorg/node-o3-xml/issues/21
        descendants = doc.selectNodes('//*[name(.)!="svg"]');
        var arr = [];
        for (var i = 0, len = descendants.length; i < len; i++) {
          arr[i] = descendants[i];
        }
        descendants = arr;
      }

      var elements = descendants.filter(function (el) {
        return (
          reAllowedSVGTagNames.test(el.tagName) &&
          !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/)
        ); // http://www.w3.org/TR/SVG/struct.html#DefsElement
      });

      if (!elements || (elements && !elements.length)) {
        callback && callback([], {});
        return;
      }

      var options = {
        width: widthAttr ? widthAttr : viewBoxWidth,
        height: heightAttr ? heightAttr : viewBoxHeight,
        widthAttr: widthAttr,
        heightAttr: heightAttr
      };

      fabric.gradientDefs = extend(
        fabric.getGradientDefs(doc),
        fabric.gradientDefs
      );
      fabric.cssRules = extend(fabric.getCSSRules(doc), fabric.cssRules);
      // Precedence of rules:   style > class > attribute

      fabric.parseElements(
        elements,
        function (instances) {
          fabric.documentParsingTime = new Date() - startTime;
          if (callback) {
            callback(instances, options);
          }
        },
        clone(options),
        reviver
      );
    };
  })();

  /**
   * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`)
   * @namespace
   */
  var svgCache = {
    /**
     * @param {String} name
     * @param {Function} callback
     */
    has: function (name, callback) {
      callback(false);
    },

    get: function () {
      /* NOOP */
    },

    set: function () {
      /* NOOP */
    }
  };

  /**
   * @private
   */
  function _enlivenCachedObject(cachedObject) {
    var objects = cachedObject.objects,
      options = cachedObject.options;

    objects = objects.map(function (o) {
      return fabric[capitalize(o.type)].fromObject(o);
    });

    return { objects: objects, options: options };
  }

  /**
   * @private
   */
  function _createSVGPattern(markup, canvas, property) {
    if (canvas[property] && canvas[property].toSVG) {
      markup.push(
        '<pattern x="0" y="0" id="',
        property,
        'Pattern" ',
        'width="',
        canvas[property].source.width,
        '" height="',
        canvas[property].source.height,
        '" patternUnits="userSpaceOnUse">',
        '<image x="0" y="0" ',
        'width="',
        canvas[property].source.width,
        '" height="',
        canvas[property].source.height,
        '" xlink:href="',
        canvas[property].source.src,
        '"></image></pattern>'
      );
    }
  }

  extend(fabric, {
    /**
     * Initializes gradients on instances, according to gradients parsed from a document
     * @param {Array} instances
     */
    resolveGradients: function (instances) {
      for (var i = instances.length; i--; ) {
        var instanceFillValue = instances[i].get("fill");

        if (!/^url\(/.test(instanceFillValue)) continue;

        var gradientId = instanceFillValue.slice(
          5,
          instanceFillValue.length - 1
        );

        if (fabric.gradientDefs[gradientId]) {
          instances[i].set(
            "fill",
            fabric.Gradient.fromElement(
              fabric.gradientDefs[gradientId],
              instances[i]
            )
          );
        }
      }
    },

    /**
     * Parses an SVG document, returning all of the gradient declarations found in it
     * @static
     * @function
     * @memberOf fabric
     * @param {SVGDocument} doc SVG document to parse
     * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
     */
    getGradientDefs: function (doc) {
      var linearGradientEls = doc.getElementsByTagName("linearGradient"),
        radialGradientEls = doc.getElementsByTagName("radialGradient"),
        el,
        i,
        gradientDefs = {};

      i = linearGradientEls.length;
      for (; i--; ) {
        el = linearGradientEls[i];
        gradientDefs[el.getAttribute("id")] = el;
      }

      i = radialGradientEls.length;
      for (; i--; ) {
        el = radialGradientEls[i];
        gradientDefs[el.getAttribute("id")] = el;
      }

      return gradientDefs;
    },

    /**
     * Returns an object of attributes' name/value, given element and an array of attribute names;
     * Parses parent "g" nodes recursively upwards.
     * @static
     * @memberOf fabric
     * @param {DOMElement} element Element to parse
     * @param {Array} attributes Array of attributes to parse
     * @return {Object} object containing parsed attributes' names/values
     */
    parseAttributes: function (element, attributes) {
      if (!element) {
        return;
      }

      var value,
        parentAttributes = {};

      // if there's a parent container (`g` node), parse its attributes recursively upwards
      if (element.parentNode && /^[g|a]$/i.test(element.parentNode.nodeName)) {
        parentAttributes = fabric.parseAttributes(
          element.parentNode,
          attributes
        );
      }

      var ownAttributes = attributes.reduce(function (memo, attr) {
        value = element.getAttribute(attr);
        if (value) {
          attr = normalizeAttr(attr);
          value = normalizeValue(attr, value, parentAttributes);

          memo[attr] = value;
        }
        return memo;
      }, {});

      // add values parsed from style, which take precedence over attributes
      // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
      ownAttributes = extend(
        ownAttributes,
        extend(
          getGlobalStylesForElement(element),
          fabric.parseStyleAttribute(element)
        )
      );

      return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
    },

    /**
     * Transforms an array of svg elements to corresponding fabric.* instances
     * @static
     * @memberOf fabric
     * @param {Array} elements Array of elements to parse
     * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
     * @param {Object} [options] Options object
     * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
     */
    parseElements: function (elements, callback, options, reviver) {
      new fabric.ElementsParser(elements, callback, options, reviver).parse();
    },

    /**
     * Parses "style" attribute, retuning an object with values
     * @static
     * @memberOf fabric
     * @param {SVGElement} element Element to parse
     * @return {Object} Objects with values parsed from style attribute of an element
     */
    parseStyleAttribute: function (element) {
      var oStyle = {},
        style = element.getAttribute("style");

      if (!style) {
        return oStyle;
      }

      if (typeof style === "string") {
        parseStyleString(style, oStyle);
      } else {
        parseStyleObject(style, oStyle);
      }

      return oStyle;
    },

    /**
     * Parses "points" attribute, returning an array of values
     * @static
     * @memberOf fabric
     * @param {String} points points attribute string
     * @return {Array} array of points
     */
    parsePointsAttribute: function (points) {
      // points attribute is required and must not be empty
      if (!points) return null;

      // replace commas with whitespace and remove bookending whitespace
      points = points.replace(/,/g, " ").trim();

      points = points.split(/\s+/);
      var parsedPoints = [],
        i,
        len;

      i = 0;
      len = points.length;
      for (; i < len; i += 2) {
        parsedPoints.push({
          x: parseFloat(points[i]),
          y: parseFloat(points[i + 1])
        });
      }

      // odd number of points is an error
      // if (parsedPoints.length % 2 !== 0) {
      // return null;
      // }

      return parsedPoints;
    },

    /**
     * Returns CSS rules for a given SVG document
     * @static
     * @function
     * @memberOf fabric
     * @param {SVGDocument} doc SVG document to parse
     * @return {Object} CSS rules of this document
     */
    getCSSRules: function (doc) {
      var styles = doc.getElementsByTagName("style"),
        allRules = {},
        rules;

      // very crude parsing of style contents
      for (var i = 0, len = styles.length; i < len; i++) {
        var styleContents = styles[0].textContent;

        // remove comments
        styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, "");

        rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
        rules = rules.map(function (rule) {
          return rule.trim();
        });

        rules.forEach(function (rule) {
          var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/);
          rule = match[1];
          var declaration = match[2].trim(),
            propertyValuePairs = declaration.replace(/;$/, "").split(/\s*;\s*/);

          if (!allRules[rule]) {
            allRules[rule] = {};
          }

          for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
            var pair = propertyValuePairs[i].split(/\s*:\s*/),
              property = pair[0],
              value = pair[1];

            allRules[rule][property] = value;
          }
        });
      }

      return allRules;
    },

    /**
     * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
     * @memberof fabric
     * @param {String} url
     * @param {Function} callback
     * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
     */
    loadSVGFromURL: function (url, callback, reviver) {
      url = url.replace(/^\n\s*/, "").trim();
      svgCache.has(url, function (hasUrl) {
        if (hasUrl) {
          svgCache.get(url, function (value) {
            var enlivedRecord = _enlivenCachedObject(value);
            callback(enlivedRecord.objects, enlivedRecord.options);
          });
        } else {
          new fabric.util.request(url, {
            method: "get",
            onComplete: onComplete
          });
        }
      });

      function onComplete(r) {
        var xml = r.responseXML;
        if (
          xml &&
          !xml.documentElement &&
          fabric.window.ActiveXObject &&
          r.responseText
        ) {
          xml = new ActiveXObject("Microsoft.XMLDOM");
          xml.async = "false";
          //IE chokes on DOCTYPE
          xml.loadXML(
            r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, "")
          );
        }
        if (!xml || !xml.documentElement) return;

        fabric.parseSVGDocument(
          xml.documentElement,
          function (results, options) {
            svgCache.set(url, {
              objects: fabric.util.array.invoke(results, "toObject"),
              options: options
            });
            callback(results, options);
          },
          reviver
        );
      }
    },

    /**
     * Takes string corresponding to an SVG document, and parses it into a set of fabric objects
     * @memberof fabric
     * @param {String} string
     * @param {Function} callback
     * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
     */
    loadSVGFromString: function (string, callback, reviver) {
      string = string.trim();
      var doc;
      if (typeof DOMParser !== "undefined") {
        var parser = new DOMParser();
        if (parser && parser.parseFromString) {
          doc = parser.parseFromString(string, "text/xml");
        }
      } else if (fabric.window.ActiveXObject) {
        doc = new ActiveXObject("Microsoft.XMLDOM");
        doc.async = "false";
        //IE chokes on DOCTYPE
        doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ""));
      }

      fabric.parseSVGDocument(
        doc.documentElement,
        function (results, options) {
          callback(results, options);
        },
        reviver
      );
    },

    /**
     * Creates markup containing SVG font faces
     * @param {Array} objects Array of fabric objects
     * @return {String}
     */
    createSVGFontFacesMarkup: function (objects) {
      var markup = "";

      for (var i = 0, len = objects.length; i < len; i++) {
        if (objects[i].type !== "text" || !objects[i].path) continue;

        markup += [
          "@font-face {",
          "font-family: ",
          objects[i].fontFamily,
          "; ",
          "src: url('",
          objects[i].path,
          "')",
          "}"
        ].join("");
      }

      if (markup) {
        markup = [
          '<style type="text/css">',
          "<![CDATA[",
          markup,
          "]]>",
          "</style>"
        ].join("");
      }

      return markup;
    },

    /**
     * Creates markup containing SVG referenced elements like patterns, gradients etc.
     * @param {fabric.Canvas} canvas instance of fabric.Canvas
     * @return {String}
     */
    createSVGRefElementsMarkup: function (canvas) {
      var markup = [];

      _createSVGPattern(markup, canvas, "backgroundColor");
      _createSVGPattern(markup, canvas, "overlayColor");

      return markup.join("");
    }
  });
})(typeof exports !== "undefined" ? exports : this);

fabric.ElementsParser = function (elements, callback, options, reviver) {
  this.elements = elements;
  this.callback = callback;
  this.options = options;
  this.reviver = reviver;
};

fabric.ElementsParser.prototype.parse = function () {
  this.instances = new Array(this.elements.length);
  this.numElements = this.elements.length;

  this.createObjects();
};

fabric.ElementsParser.prototype.createObjects = function () {
  for (var i = 0, len = this.elements.length; i < len; i++) {
    (function (_this, i) {
      setTimeout(function () {
        _this.createObject(_this.elements[i], i);
      }, 0);
    })(this, i);
  }
};

fabric.ElementsParser.prototype.createObject = function (el, index) {
  var klass = fabric[fabric.util.string.capitalize(el.tagName)];
  if (klass && klass.fromElement) {
    try {
      this._createObject(klass, el, index);
    } catch (err) {
      fabric.log(err);
    }
  } else {
    this.checkIfDone();
  }
};

fabric.ElementsParser.prototype._createObject = function (klass, el, index) {
  if (klass.async) {
    klass.fromElement(el, this.createCallback(index, el), this.options);
  } else {
    var obj = klass.fromElement(el, this.options);
    this.reviver && this.reviver(el, obj);
    this.instances[index] = obj;
    this.checkIfDone();
  }
};

fabric.ElementsParser.prototype.createCallback = function (index, el) {
  var _this = this;
  return function (obj) {
    _this.reviver && _this.reviver(el, obj);
    _this.instances[index] = obj;
    _this.checkIfDone();
  };
};

fabric.ElementsParser.prototype.checkIfDone = function () {
  if (--this.numElements === 0) {
    this.instances = this.instances.filter(function (el) {
      return el != null;
    });
    fabric.resolveGradients(this.instances);
    this.callback(this.instances);
  }
};

(function (global) {
  "use strict";

  /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */

  var fabric = global.fabric || (global.fabric = {});

  if (fabric.Point) {
    fabric.warn("fabric.Point is already defined");
    return;
  }

  fabric.Point = Point;

  /**
   * Point class
   * @class fabric.Point
   * @memberOf fabric
   * @constructor
   * @param {Number} x
   * @param {Number} y
   * @return {fabric.Point} thisArg
   */
  function Point(x, y) {
    this.x = x;
    this.y = y;
  }

  Point.prototype = /** @lends fabric.Point.prototype */ {
    constructor: Point,

    /**
     * Adds another point to this one and returns another one
     * @param {fabric.Point} that
     * @return {fabric.Point} new Point instance with added values
     */
    add: function (that) {
      return new Point(this.x + that.x, this.y + that.y);
    },

    /**
     * Adds another point to this one
     * @param {fabric.Point} that
     * @return {fabric.Point} thisArg
     */
    addEquals: function (that) {
      this.x += that.x;
      this.y += that.y;
      return this;
    },

    /**
     * Adds value to this point and returns a new one
     * @param {Number} scalar
     * @return {fabric.Point} new Point with added value
     */
    scalarAdd: function (scalar) {
      return new Point(this.x + scalar, this.y + scalar);
    },

    /**
     * Adds value to this point
     * @param {Number} scalar
     * @return {fabric.Point} thisArg
     */
    scalarAddEquals: function (scalar) {
      this.x += scalar;
      this.y += scalar;
      return this;
    },

    /**
     * Subtracts another point from this point and returns a new one
     * @param {fabric.Point} that
     * @return {fabric.Point} new Point object with subtracted values
     */
    subtract: function (that) {
      return new Point(this.x - that.x, this.y - that.y);
    },

    /**
     * Subtracts another point from this point
     * @param {fabric.Point} that
     * @return {fabric.Point} thisArg
     */
    subtractEquals: function (that) {
      this.x -= that.x;
      this.y -= that.y;
      return this;
    },

    /**
     * Subtracts value from this point and returns a new one
     * @param {Number} scalar
     * @return {fabric.Point}
     */
    scalarSubtract: function (scalar) {
      return new Point(this.x - scalar, this.y - scalar);
    },

    /**
     * Subtracts value from this point
     * @param {Number} scalar
     * @return {fabric.Point} thisArg
     */
    scalarSubtractEquals: function (scalar) {
      this.x -= scalar;
      this.y -= scalar;
      return this;
    },

    /**
     * Miltiplies this point by a value and returns a new one
     * @param {Number} scalar
     * @return {fabric.Point}
     */
    multiply: function (scalar) {
      return new Point(this.x * scalar, this.y * scalar);
    },

    /**
     * Miltiplies this point by a value
     * @param {Number} scalar
     * @return {fabric.Point} thisArg
     */
    multiplyEquals: function (scalar) {
      this.x *= scalar;
      this.y *= scalar;
      return this;
    },

    /**
     * Divides this point by a value and returns a new one
     * @param {Number} scalar
     * @return {fabric.Point}
     */
    divide: function (scalar) {
      return new Point(this.x / scalar, this.y / scalar);
    },

    /**
     * Divides this point by a value
     * @param {Number} scalar
     * @return {fabric.Point} thisArg
     */
    divideEquals: function (scalar) {
      this.x /= scalar;
      this.y /= scalar;
      return this;
    },

    /**
     * Returns true if this point is equal to another one
     * @param {fabric.Point} that
     * @return {Boolean}
     */
    eq: function (that) {
      return this.x === that.x && this.y === that.y;
    },

    /**
     * Returns true if this point is less than another one
     * @param {fabric.Point} that
     * @return {Boolean}
     */
    lt: function (that) {
      return this.x < that.x && this.y < that.y;
    },

    /**
     * Returns true if this point is less than or equal to another one
     * @param {fabric.Point} that
     * @return {Boolean}
     */
    lte: function (that) {
      return this.x <= that.x && this.y <= that.y;
    },

    /**

     * Returns true if this point is greater another one
     * @param {fabric.Point} that
     * @return {Boolean}
     */
    gt: function (that) {
      return this.x > that.x && this.y > that.y;
    },

    /**
     * Returns true if this point is greater than or equal to another one
     * @param {fabric.Point} that
     * @return {Boolean}
     */
    gte: function (that) {
      return this.x >= that.x && this.y >= that.y;
    },

    /**
     * Returns new point which is the result of linear interpolation with this one and another one
     * @param {fabric.Point} that
     * @param {Number} t
     * @return {fabric.Point}
     */
    lerp: function (that, t) {
      return new Point(
        this.x + (that.x - this.x) * t,
        this.y + (that.y - this.y) * t
      );
    },

    /**
     * Returns distance from this point and another one
     * @param {fabric.Point} that
     * @return {Number}
     */
    distanceFrom: function (that) {
      var dx = this.x - that.x,
        dy = this.y - that.y;
      return Math.sqrt(dx * dx + dy * dy);
    },

    /**
     * Returns the point between this point and another one
     * @param {fabric.Point} that
     * @return {fabric.Point}
     */
    midPointFrom: function (that) {
      return new Point(
        this.x + (that.x - this.x) / 2,
        this.y + (that.y - this.y) / 2
      );
    },

    /**
     * Returns a new point which is the min of this and another one
     * @param {fabric.Point} that
     * @return {fabric.Point}
     */
    min: function (that) {
      return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
    },

    /**
     * Returns a new point which is the max of this and another one
     * @param {fabric.Point} that
     * @return {fabric.Point}
     */
    max: function (that) {
      return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y));
    },

    /**
     * Returns string representation of this point
     * @return {String}
     */
    toString: function () {
      return this.x + "," + this.y;
    },

    /**
     * Sets x/y of this point
     * @param {Number} x
     * @return {Number} y
     */
    setXY: function (x, y) {
      this.x = x;
      this.y = y;
    },

    /**
     * Sets x/y of this point from another point
     * @param {fabric.Point} that
     */
    setFromPoint: function (that) {
      this.x = that.x;
      this.y = that.y;
    },

    /**
     * Swaps x/y of this point and another point
     * @param {fabric.Point} that
     */
    swap: function (that) {
      var x = this.x,
        y = this.y;
      this.x = that.x;
      this.y = that.y;
      that.x = x;
      that.y = y;
    }
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
  var fabric = global.fabric || (global.fabric = {});

  if (fabric.Intersection) {
    fabric.warn("fabric.Intersection is already defined");
    return;
  }

  /**
   * Intersection class
   * @class fabric.Intersection
   * @memberOf fabric
   * @constructor
   */
  function Intersection(status) {
    this.status = status;
    this.points = [];
  }

  fabric.Intersection = Intersection;

  fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {
    /**
     * Appends a point to intersection
     * @param {fabric.Point} point
     */
    appendPoint: function (point) {
      this.points.push(point);
    },

    /**
     * Appends points to intersection
     * @param {Array} points
     */
    appendPoints: function (points) {
      this.points = this.points.concat(points);
    }
  };

  /**
   * Checks if one line intersects another
   * @static
   * @param {fabric.Point} a1
   * @param {fabric.Point} a2
   * @param {fabric.Point} b1
   * @param {fabric.Point} b2
   * @return {fabric.Intersection}
   */
  fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
    var result,
      uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
      ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
      uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
    if (uB !== 0) {
      var ua = uaT / uB,
        ub = ubT / uB;
      if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
        result = new Intersection("Intersection");
        result.points.push(
          new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))
        );
      } else {
        result = new Intersection();
      }
    } else {
      if (uaT === 0 || ubT === 0) {
        result = new Intersection("Coincident");
      } else {
        result = new Intersection("Parallel");
      }
    }
    return result;
  };

  /**
   * Checks if line intersects polygon
   * @static
   * @param {fabric.Point} a1
   * @param {fabric.Point} a2
   * @param {Array} points
   * @return {fabric.Intersection}
   */
  fabric.Intersection.intersectLinePolygon = function (a1, a2, points) {
    var result = new Intersection(),
      length = points.length;

    for (var i = 0; i < length; i++) {
      var b1 = points[i],
        b2 = points[(i + 1) % length],
        inter = Intersection.intersectLineLine(a1, a2, b1, b2);

      result.appendPoints(inter.points);
    }
    if (result.points.length > 0) {
      result.status = "Intersection";
    }
    return result;
  };

  /**
   * Checks if polygon intersects another polygon
   * @static
   * @param {Array} points1
   * @param {Array} points2
   * @return {fabric.Intersection}
   */
  fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
    var result = new Intersection(),
      length = points1.length;

    for (var i = 0; i < length; i++) {
      var a1 = points1[i],
        a2 = points1[(i + 1) % length],
        inter = Intersection.intersectLinePolygon(a1, a2, points2);

      result.appendPoints(inter.points);
    }
    if (result.points.length > 0) {
      result.status = "Intersection";
    }
    return result;
  };

  /**
   * Checks if polygon intersects rectangle
   * @static
   * @param {Array} points
   * @param {Number} r1
   * @param {Number} r2
   * @return {fabric.Intersection}
   */
  fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
    var min = r1.min(r2),
      max = r1.max(r2),
      topRight = new fabric.Point(max.x, min.y),
      bottomLeft = new fabric.Point(min.x, max.y),
      inter1 = Intersection.intersectLinePolygon(min, topRight, points),
      inter2 = Intersection.intersectLinePolygon(topRight, max, points),
      inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
      inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
      result = new Intersection();

    result.appendPoints(inter1.points);
    result.appendPoints(inter2.points);
    result.appendPoints(inter3.points);
    result.appendPoints(inter4.points);

    if (result.points.length > 0) {
      result.status = "Intersection";
    }
    return result;
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {});

  if (fabric.Color) {
    fabric.warn("fabric.Color is already defined.");
    return;
  }

  /**
   * Color class
   * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations;
   * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects.
   *
   * @class fabric.Color
   * @param {String} color optional in hex or rgb(a) format
   * @return {fabric.Color} thisArg
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors}
   */
  function Color(color) {
    if (!color) {
      this.setSource([0, 0, 0, 1]);
    } else {
      this._tryParsingColor(color);
    }
  }

  fabric.Color = Color;

  fabric.Color.prototype = /** @lends fabric.Color.prototype */ {
    /**
     * @private
     * @param {String|Array} color Color value to parse
     */
    _tryParsingColor: function (color) {
      var source;

      if (color in Color.colorNameMap) {
        color = Color.colorNameMap[color];
      }

      if (color === "transparent") {
        this.setSource([255, 255, 255, 0]);
        return;
      }

      source = Color.sourceFromHex(color);

      if (!source) {
        source = Color.sourceFromRgb(color);
      }
      if (!source) {
        source = Color.sourceFromHsl(color);
      }
      if (source) {
        this.setSource(source);
      }
    },

    /**
     * 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>
     * @private
     * @param {Number} r Red color value
     * @param {Number} g Green color value
     * @param {Number} b Blue color value
     * @return {Array} Hsl color
     */
    _rgbToHsl: function (r, g, b) {
      (r /= 255), (g /= 255), (b /= 255);

      var h,
        s,
        l,
        max = fabric.util.array.max([r, g, b]),
        min = fabric.util.array.min([r, g, b]);

      l = (max + min) / 2;

      if (max === min) {
        h = s = 0; // achromatic
      } else {
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch (max) {
          case r:
            h = (g - b) / d + (g < b ? 6 : 0);
            break;
          case g:
            h = (b - r) / d + 2;
            break;
          case b:
            h = (r - g) / d + 4;
            break;
        }
        h /= 6;
      }

      return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
    },

    /**
     * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])
     * @return {Array}
     */
    getSource: function () {
      return this._source;
    },

    /**
     * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1])
     * @param {Array} source
     */
    setSource: function (source) {
      this._source = source;
    },

    /**
     * Returns color represenation in RGB format
     * @return {String} ex: rgb(0-255,0-255,0-255)
     */
    toRgb: function () {
      var source = this.getSource();
      return "rgb(" + source[0] + "," + source[1] + "," + source[2] + ")";
    },

    /**
     * Returns color represenation in RGBA format
     * @return {String} ex: rgba(0-255,0-255,0-255,0-1)
     */
    toRgba: function () {
      var source = this.getSource();
      return (
        "rgba(" +
        source[0] +
        "," +
        source[1] +
        "," +
        source[2] +
        "," +
        source[3] +
        ")"
      );
    },

    /**
     * Returns color represenation in HSL format
     * @return {String} ex: hsl(0-360,0%-100%,0%-100%)
     */
    toHsl: function () {
      var source = this.getSource(),
        hsl = this._rgbToHsl(source[0], source[1], source[2]);

      return "hsl(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%)";
    },

    /**
     * Returns color represenation in HSLA format
     * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1)
     */
    toHsla: function () {
      var source = this.getSource(),
        hsl = this._rgbToHsl(source[0], source[1], source[2]);

      return (
        "hsla(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%," + source[3] + ")"
      );
    },

    /**
     * Returns color represenation in HEX format
     * @return {String} ex: FF5555
     */
    toHex: function () {
      var source = this.getSource(),
        r,
        g,
        b;

      r = source[0].toString(16);
      r = r.length === 1 ? "0" + r : r;

      g = source[1].toString(16);
      g = g.length === 1 ? "0" + g : g;

      b = source[2].toString(16);
      b = b.length === 1 ? "0" + b : b;

      return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();
    },

    /**
     * Gets value of alpha channel for this color
     * @return {Number} 0-1
     */
    getAlpha: function () {
      return this.getSource()[3];
    },

    /**
     * Sets value of alpha channel for this color
     * @param {Number} alpha Alpha value 0-1
     * @return {fabric.Color} thisArg
     */
    setAlpha: function (alpha) {
      var source = this.getSource();
      source[3] = alpha;
      this.setSource(source);
      return this;
    },

    /**
     * Transforms color to its grayscale representation
     * @return {fabric.Color} thisArg
     */
    toGrayscale: function () {
      var source = this.getSource(),
        average = parseInt(
          (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0),
          10
        ),
        currentAlpha = source[3];
      this.setSource([average, average, average, currentAlpha]);
      return this;
    },

    /**
     * Transforms color to its black and white representation
     * @param {Number} threshold
     * @return {fabric.Color} thisArg
     */
    toBlackWhite: function (threshold) {
      var source = this.getSource(),
        average = (
          source[0] * 0.3 +
          source[1] * 0.59 +
          source[2] * 0.11
        ).toFixed(0),
        currentAlpha = source[3];

      threshold = threshold || 127;

      average = Number(average) < Number(threshold) ? 0 : 255;
      this.setSource([average, average, average, currentAlpha]);
      return this;
    },

    /**
     * Overlays color with another color
     * @param {String|fabric.Color} otherColor
     * @return {fabric.Color} thisArg
     */
    overlayWith: function (otherColor) {
      if (!(otherColor instanceof Color)) {
        otherColor = new Color(otherColor);
      }

      var result = [],
        alpha = this.getAlpha(),
        otherAlpha = 0.5,
        source = this.getSource(),
        otherSource = otherColor.getSource();

      for (var i = 0; i < 3; i++) {
        result.push(
          Math.round(source[i] * (1 - otherAlpha) + otherSource[i] * otherAlpha)
        );
      }

      result[3] = alpha;
      this.setSource(result);
      return this;
    }
  };

  /**
   * 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))
   * @static
   * @field
   * @memberOf fabric.Color
   */
  fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;

  /**
   * 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 ))
   * @static
   * @field
   * @memberOf fabric.Color
   */
  fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;

  /**
   * Regex matching color in HEX format (ex: #FF5555, 010155, aff)
   * @static
   * @field
   * @memberOf fabric.Color
   */
  fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i;

  /**
   * Map of the 17 basic color names with HEX code
   * @static
   * @field
   * @memberOf fabric.Color
   * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units
   */
  fabric.Color.colorNameMap = {
    aqua: "#00FFFF",
    black: "#000000",
    blue: "#0000FF",
    fuchsia: "#FF00FF",
    gray: "#808080",
    green: "#008000",
    lime: "#00FF00",
    maroon: "#800000",
    navy: "#000080",
    olive: "#808000",
    orange: "#FFA500",
    purple: "#800080",
    red: "#FF0000",
    silver: "#C0C0C0",
    teal: "#008080",
    white: "#FFFFFF",
    yellow: "#FFFF00"
  };

  /**
   * @private
   * @param {Number} p
   * @param {Number} q
   * @param {Number} t
   * @return {Number}
   */
  function hue2rgb(p, q, t) {
    if (t < 0) {
      t += 1;
    }
    if (t > 1) {
      t -= 1;
    }
    if (t < 1 / 6) {
      return p + (q - p) * 6 * t;
    }
    if (t < 1 / 2) {
      return q;
    }
    if (t < 2 / 3) {
      return p + (q - p) * (2 / 3 - t) * 6;
    }
    return p;
  }

  /**
   * Returns new color object, when given a color in RGB format
   * @memberOf fabric.Color
   * @param {String} color Color value ex: rgb(0-255,0-255,0-255)
   * @return {fabric.Color}
   */
  fabric.Color.fromRgb = function (color) {
    return Color.fromSource(Color.sourceFromRgb(color));
  };

  /**
   * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format
   * @memberOf fabric.Color
   * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%)
   * @return {Array} source
   */
  fabric.Color.sourceFromRgb = function (color) {
    var match = color.match(Color.reRGBa);
    if (match) {
      var r =
          (parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1)) *
          (/%$/.test(match[1]) ? 255 : 1),
        g =
          (parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1)) *
          (/%$/.test(match[2]) ? 255 : 1),
        b =
          (parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1)) *
          (/%$/.test(match[3]) ? 255 : 1);

      return [
        parseInt(r, 10),
        parseInt(g, 10),
        parseInt(b, 10),
        match[4] ? parseFloat(match[4]) : 1
      ];
    }
  };

  /**
   * Returns new color object, when given a color in RGBA format
   * @static
   * @function
   * @memberOf fabric.Color
   * @param {String} color
   * @return {fabric.Color}
   */
  fabric.Color.fromRgba = Color.fromRgb;

  /**
   * Returns new color object, when given a color in HSL format
   * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%)
   * @memberOf fabric.Color
   * @return {fabric.Color}
   */
  fabric.Color.fromHsl = function (color) {
    return Color.fromSource(Color.sourceFromHsl(color));
  };

  /**
   * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format.
   * 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>
   * @memberOf fabric.Color
   * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1)
   * @return {Array} source
   * @see http://http://www.w3.org/TR/css3-color/#hsl-color
   */
  fabric.Color.sourceFromHsl = function (color) {
    var match = color.match(Color.reHSLa);
    if (!match) return;

    var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,
      s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1),
      l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1),
      r,
      g,
      b;

    if (s === 0) {
      r = g = b = l;
    } else {
      var q = l <= 0.5 ? l * (s + 1) : l + s - l * s,
        p = l * 2 - q;

      r = hue2rgb(p, q, h + 1 / 3);
      g = hue2rgb(p, q, h);
      b = hue2rgb(p, q, h - 1 / 3);
    }

    return [
      Math.round(r * 255),
      Math.round(g * 255),
      Math.round(b * 255),
      match[4] ? parseFloat(match[4]) : 1
    ];
  };

  /**
   * Returns new color object, when given a color in HSLA format
   * @static
   * @function
   * @memberOf fabric.Color
   * @param {String} color
   * @return {fabric.Color}
   */
  fabric.Color.fromHsla = Color.fromHsl;

  /**
   * Returns new color object, when given a color in HEX format
   * @static
   * @memberOf fabric.Color
   * @param {String} color Color value ex: FF5555
   * @return {fabric.Color}
   */
  fabric.Color.fromHex = function (color) {
    return Color.fromSource(Color.sourceFromHex(color));
  };

  /**
   * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format
   * @static
   * @memberOf fabric.Color
   * @param {String} color ex: FF5555
   * @return {Array} source
   */
  fabric.Color.sourceFromHex = function (color) {
    if (color.match(Color.reHex)) {
      var value = color.slice(color.indexOf("#") + 1),
        isShortNotation = value.length === 3,
        r = isShortNotation
          ? value.charAt(0) + value.charAt(0)
          : value.substring(0, 2),
        g = isShortNotation
          ? value.charAt(1) + value.charAt(1)
          : value.substring(2, 4),
        b = isShortNotation
          ? value.charAt(2) + value.charAt(2)
          : value.substring(4, 6);

      return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), 1];
    }
  };

  /**
   * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5])
   * @static
   * @memberOf fabric.Color
   * @param {Array} source
   * @return {fabric.Color}
   */
  fabric.Color.fromSource = function (source) {
    var oColor = new Color();
    oColor.setSource(source);
    return oColor;
  };
})(typeof exports !== "undefined" ? exports : this);

(function () {
  /* _FROM_SVG_START_ */
  function getColorStop(el) {
    var style = el.getAttribute("style"),
      offset = el.getAttribute("offset"),
      color,
      opacity;

    // convert percents to absolute values
    offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);

    if (style) {
      var keyValuePairs = style.split(/\s*;\s*/);

      if (keyValuePairs[keyValuePairs.length - 1] === "") {
        keyValuePairs.pop();
      }

      for (var i = keyValuePairs.length; i--; ) {
        var split = keyValuePairs[i].split(/\s*:\s*/),
          key = split[0].trim(),
          value = split[1].trim();

        if (key === "stop-color") {
          color = value;
        } else if (key === "stop-opacity") {
          opacity = value;
        }
      }
    }

    if (!color) {
      color = el.getAttribute("stop-color") || "rgb(0,0,0)";
    }
    if (!opacity) {
      opacity = el.getAttribute("stop-opacity");
    }

    // convert rgba color to rgb color - alpha value has no affect in svg
    color = new fabric.Color(color).toRgb();

    return {
      offset: offset,
      color: color,
      opacity: isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity)
    };
  }

  function getLinearCoords(el) {
    return {
      x1: el.getAttribute("x1") || 0,
      y1: el.getAttribute("y1") || 0,
      x2: el.getAttribute("x2") || "100%",
      y2: el.getAttribute("y2") || 0
    };
  }

  function getRadialCoords(el) {
    return {
      x1: el.getAttribute("fx") || el.getAttribute("cx") || "50%",
      y1: el.getAttribute("fy") || el.getAttribute("cy") || "50%",
      r1: 0,
      x2: el.getAttribute("cx") || "50%",
      y2: el.getAttribute("cy") || "50%",
      r2: el.getAttribute("r") || "50%"
    };
  }
  /* _FROM_SVG_END_ */

  /**
   * Gradient class
   * @class fabric.Gradient
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients}
   * @see {@link fabric.Gradient#initialize} for constructor definition
   */
  fabric.Gradient = fabric.util.createClass(
    /** @lends fabric.Gradient.prototype */ {
      /**
       * Constructor
       * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops
       * @return {fabric.Gradient} thisArg
       */
      initialize: function (options) {
        options || (options = {});

        var coords = {};

        this.id = fabric.Object.__uid++;
        this.type = options.type || "linear";

        coords = {
          x1: options.coords.x1 || 0,
          y1: options.coords.y1 || 0,
          x2: options.coords.x2 || 0,
          y2: options.coords.y2 || 0
        };

        if (this.type === "radial") {
          coords.r1 = options.coords.r1 || 0;
          coords.r2 = options.coords.r2 || 0;
        }

        this.coords = coords;
        this.gradientUnits = options.gradientUnits || "objectBoundingBox";
        this.colorStops = options.colorStops.slice();
      },

      /**
       * Adds another colorStop
       * @param {Object} colorStop Object with offset and color
       * @return {fabric.Gradient} thisArg
       */
      addColorStop: function (colorStop) {
        for (var position in colorStop) {
          var color = new fabric.Color(colorStop[position]);
          this.colorStops.push({
            offset: position,
            color: color.toRgb(),
            opacity: color.getAlpha()
          });
        }
        return this;
      },

      /**
       * Returns object representation of a gradient
       * @return {Object}
       */
      toObject: function () {
        return {
          type: this.type,
          coords: this.coords,
          gradientUnits: this.gradientUnits,
          colorStops: this.colorStops
        };
      },

      /* _TO_SVG_START_ */
      /**
       * Returns SVG representation of an gradient
       * @param {Object} object Object to create a gradient for
       * @param {Boolean} normalize Whether coords should be normalized
       * @return {String} SVG representation of an gradient (linear/radial)
       */
      toSVG: function (object, normalize) {
        var coords = fabric.util.object.clone(this.coords),
          markup;

        // colorStops must be sorted ascending
        this.colorStops.sort(function (a, b) {
          return a.offset - b.offset;
        });

        if (normalize && this.gradientUnits === "userSpaceOnUse") {
          coords.x1 += object.width / 2;
          coords.y1 += object.height / 2;
          coords.x2 += object.width / 2;
          coords.y2 += object.height / 2;
        } else if (this.gradientUnits === "objectBoundingBox") {
          _convertValuesToPercentUnits(object, coords);
        }

        if (this.type === "linear") {
          markup = [
            "<linearGradient ",
            'id="SVGID_',
            this.id,
            '" gradientUnits="',
            this.gradientUnits,
            '" x1="',
            coords.x1,
            '" y1="',
            coords.y1,
            '" x2="',
            coords.x2,
            '" y2="',
            coords.y2,
            '">'
          ];
        } else if (this.type === "radial") {
          markup = [
            "<radialGradient ",
            'id="SVGID_',
            this.id,
            '" gradientUnits="',
            this.gradientUnits,
            '" cx="',
            coords.x2,
            '" cy="',
            coords.y2,
            '" r="',
            coords.r2,
            '" fx="',
            coords.x1,
            '" fy="',
            coords.y1,
            '">'
          ];
        }

        for (var i = 0; i < this.colorStops.length; i++) {
          markup.push(
            "<stop ",
            'offset="',
            this.colorStops[i].offset * 100 + "%",
            '" style="stop-color:',
            this.colorStops[i].color,
            this.colorStops[i].opacity
              ? ";stop-opacity: " + this.colorStops[i].opacity
              : ";",
            '"/>'
          );
        }

        markup.push(
          this.type === "linear" ? "</linearGradient>" : "</radialGradient>"
        );

        return markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * Returns an instance of CanvasGradient
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @return {CanvasGradient}
       */
      toLive: function (ctx) {
        var gradient;

        if (!this.type) return;

        if (this.type === "linear") {
          gradient = ctx.createLinearGradient(
            this.coords.x1,
            this.coords.y1,
            this.coords.x2,
            this.coords.y2
          );
        } else if (this.type === "radial") {
          gradient = ctx.createRadialGradient(
            this.coords.x1,
            this.coords.y1,
            this.coords.r1,
            this.coords.x2,
            this.coords.y2,
            this.coords.r2
          );
        }

        for (var i = 0, len = this.colorStops.length; i < len; i++) {
          var color = this.colorStops[i].color,
            opacity = this.colorStops[i].opacity,
            offset = this.colorStops[i].offset;

          if (typeof opacity !== "undefined") {
            color = new fabric.Color(color).setAlpha(opacity).toRgba();
          }
          gradient.addColorStop(parseFloat(offset), color);
        }

        return gradient;
      }
    }
  );

  fabric.util.object.extend(fabric.Gradient, {
    /* _FROM_SVG_START_ */
    /**
     * Returns {@link fabric.Gradient} instance from an SVG element
     * @static
     * @memberof fabric.Gradient
     * @param {SVGGradientElement} el SVG gradient element
     * @param {fabric.Object} instance
     * @return {fabric.Gradient} Gradient instance
     * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
     * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement
     */
    fromElement: function (el, instance) {
      /**
       *  @example:
       *
       *  <linearGradient id="linearGrad1">
       *    <stop offset="0%" stop-color="white"/>
       *    <stop offset="100%" stop-color="black"/>
       *  </linearGradient>
       *
       *  OR
       *
       *  <linearGradient id="linearGrad2">
       *    <stop offset="0" style="stop-color:rgb(255,255,255)"/>
       *    <stop offset="1" style="stop-color:rgb(0,0,0)"/>
       *  </linearGradient>
       *
       *  OR
       *
       *  <radialGradient id="radialGrad1">
       *    <stop offset="0%" stop-color="white" stop-opacity="1" />
       *    <stop offset="50%" stop-color="black" stop-opacity="0.5" />
       *    <stop offset="100%" stop-color="white" stop-opacity="1" />
       *  </radialGradient>
       *
       *  OR
       *
       *  <radialGradient id="radialGrad2">
       *    <stop offset="0" stop-color="rgb(255,255,255)" />
       *    <stop offset="0.5" stop-color="rgb(0,0,0)" />
       *    <stop offset="1" stop-color="rgb(255,255,255)" />
       *  </radialGradient>
       *
       */

      var colorStopEls = el.getElementsByTagName("stop"),
        type = el.nodeName === "linearGradient" ? "linear" : "radial",
        gradientUnits = el.getAttribute("gradientUnits") || "objectBoundingBox",
        colorStops = [],
        coords = {};

      if (type === "linear") {
        coords = getLinearCoords(el);
      } else if (type === "radial") {
        coords = getRadialCoords(el);
      }

      for (var i = colorStopEls.length; i--; ) {
        colorStops.push(getColorStop(colorStopEls[i]));
      }

      _convertPercentUnitsToValues(instance, coords);

      return new fabric.Gradient({
        type: type,
        coords: coords,
        gradientUnits: gradientUnits,
        colorStops: colorStops
      });
    },
    /* _FROM_SVG_END_ */

    /**
     * Returns {@link fabric.Gradient} instance from its object representation
     * @static
     * @memberof fabric.Gradient
     * @param {Object} obj
     * @param {Object} [options] Options object
     */
    forObject: function (obj, options) {
      options || (options = {});
      _convertPercentUnitsToValues(obj, options);
      return new fabric.Gradient(options);
    }
  });

  /**
   * @private
   */
  function _convertPercentUnitsToValues(object, options) {
    for (var prop in options) {
      if (typeof options[prop] === "string" && /^\d+%$/.test(options[prop])) {
        var percents = parseFloat(options[prop], 10);
        if (prop === "x1" || prop === "x2" || prop === "r2") {
          options[prop] = fabric.util.toFixed(
            (object.width * percents) / 100,
            2
          );
        } else if (prop === "y1" || prop === "y2") {
          options[prop] = fabric.util.toFixed(
            (object.height * percents) / 100,
            2
          );
        }
      }
      normalize(options, prop, object);
    }
  }

  // normalize rendering point (should be from top/left corner rather than center of the shape)
  function normalize(options, prop, object) {
    if (prop === "x1" || prop === "x2") {
      options[prop] -= fabric.util.toFixed(object.width / 2, 2);
    } else if (prop === "y1" || prop === "y2") {
      options[prop] -= fabric.util.toFixed(object.height / 2, 2);
    }
  }

  /* _TO_SVG_START_ */
  /**
   * @private
   */
  function _convertValuesToPercentUnits(object, options) {
    for (var prop in options) {
      normalize(options, prop, object);

      // convert to percent units
      if (prop === "x1" || prop === "x2" || prop === "r2") {
        options[prop] =
          fabric.util.toFixed((options[prop] / object.width) * 100, 2) + "%";
      } else if (prop === "y1" || prop === "y2") {
        options[prop] =
          fabric.util.toFixed((options[prop] / object.height) * 100, 2) + "%";
      }
    }
  }
  /* _TO_SVG_END_ */
})();

/**
 * Pattern class
 * @class fabric.Pattern
 * @see {@link http://fabricjs.com/patterns/|Pattern demo}
 * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo}
 * @see {@link fabric.Pattern#initialize} for constructor definition
 */
fabric.Pattern = fabric.util.createClass(
  /** @lends fabric.Pattern.prototype */ {
    /**
     * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
     * @type String
     * @default
     */
    repeat: "repeat",

    /**
     * Pattern horizontal offset from object's left/top corner
     * @type Number
     * @default
     */
    offsetX: 0,

    /**
     * Pattern vertical offset from object's left/top corner
     * @type Number
     * @default
     */
    offsetY: 0,

    /**
     * Constructor
     * @param {Object} [options] Options object
     * @return {fabric.Pattern} thisArg
     */
    initialize: function (options) {
      options || (options = {});

      this.id = fabric.Object.__uid++;

      if (options.source) {
        if (typeof options.source === "string") {
          // function string
          if (
            typeof fabric.util.getFunctionBody(options.source) !== "undefined"
          ) {
            this.source = new Function(
              fabric.util.getFunctionBody(options.source)
            );
          } else {
            // img src string
            var _this = this;
            this.source = fabric.util.createImage();
            fabric.util.loadImage(options.source, function (img) {
              _this.source = img;
            });
          }
        } else {
          // img element
          this.source = options.source;
        }
      }
      if (options.repeat) {
        this.repeat = options.repeat;
      }
      if (options.offsetX) {
        this.offsetX = options.offsetX;
      }
      if (options.offsetY) {
        this.offsetY = options.offsetY;
      }
    },

    /**
     * Returns object representation of a pattern
     * @return {Object} Object representation of a pattern instance
     */
    toObject: function () {
      var source;

      // callback
      if (typeof this.source === "function") {
        source = String(this.source);
      }
      // <img> element
      else if (typeof this.source.src === "string") {
        source = this.source.src;
      }

      return {
        source: source,
        repeat: this.repeat,
        offsetX: this.offsetX,
        offsetY: this.offsetY
      };
    },

    /* _TO_SVG_START_ */
    /**
     * Returns SVG representation of a pattern
     * @param {fabric.Object} object
     * @return {String} SVG representation of a pattern
     */
    toSVG: function (object) {
      var patternSource =
          typeof this.source === "function" ? this.source() : this.source,
        patternWidth = patternSource.width / object.getWidth(),
        patternHeight = patternSource.height / object.getHeight(),
        patternImgSrc = "";

      if (patternSource.src) {
        patternImgSrc = patternSource.src;
      } else if (patternSource.toDataURL) {
        patternImgSrc = patternSource.toDataURL();
      }

      return (
        '<pattern id="SVGID_' +
        this.id +
        '" x="' +
        this.offsetX +
        '" y="' +
        this.offsetY +
        '" width="' +
        patternWidth +
        '" height="' +
        patternHeight +
        '">' +
        '<image x="0" y="0"' +
        ' width="' +
        patternSource.width +
        '" height="' +
        patternSource.height +
        '" xlink:href="' +
        patternImgSrc +
        '"></image>' +
        "</pattern>"
      );
    },
    /* _TO_SVG_END_ */

    /**
     * Returns an instance of CanvasPattern
     * @param {CanvasRenderingContext2D} ctx Context to create pattern
     * @return {CanvasPattern}
     */
    toLive: function (ctx) {
      var source =
        typeof this.source === "function" ? this.source() : this.source;

      // if the image failed to load, return, and allow rest to continue loading
      if (!source) {
        return "";
      }

      // if an image
      if (typeof source.src !== "undefined") {
        if (!source.complete) {
          return "";
        }
        if (source.naturalWidth === 0 || source.naturalHeight === 0) {
          return "";
        }
      }
      return ctx.createPattern(source, this.repeat);
    }
  }
);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {});

  if (fabric.Shadow) {
    fabric.warn("fabric.Shadow is already defined.");
    return;
  }

  /**
   * Shadow class
   * @class fabric.Shadow
   * @see {@link http://fabricjs.com/shadows/|Shadow demo}
   * @see {@link fabric.Shadow#initialize} for constructor definition
   */
  fabric.Shadow = fabric.util.createClass(
    /** @lends fabric.Shadow.prototype */ {
      /**
       * Shadow color
       * @type String
       * @default
       */
      color: "rgb(0,0,0)",

      /**
       * Shadow blur
       * @type Number
       */
      blur: 0,

      /**
       * Shadow horizontal offset
       * @type Number
       * @default
       */
      offsetX: 0,

      /**
       * Shadow vertical offset
       * @type Number
       * @default
       */
      offsetY: 0,

      /**
       * Whether the shadow should affect stroke operations
       * @type Boolean
       * @default
       */
      affectStroke: false,

      /**
       * Indicates whether toObject should include default values
       * @type Boolean
       * @default
       */
      includeDefaultValues: true,

      /**
       * Constructor
       * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)")
       * @return {fabric.Shadow} thisArg
       */
      initialize: function (options) {
        if (typeof options === "string") {
          options = this._parseShadow(options);
        }

        for (var prop in options) {
          this[prop] = options[prop];
        }

        this.id = fabric.Object.__uid++;
      },

      /**
       * @private
       * @param {String} shadow Shadow value to parse
       * @return {Object} Shadow object with color, offsetX, offsetY and blur
       */
      _parseShadow: function (shadow) {
        var shadowStr = shadow.trim(),
          offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [],
          color =
            shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, "") ||
            "rgb(0,0,0)";

        return {
          color: color.trim(),
          offsetX: parseInt(offsetsAndBlur[1], 10) || 0,
          offsetY: parseInt(offsetsAndBlur[2], 10) || 0,
          blur: parseInt(offsetsAndBlur[3], 10) || 0
        };
      },

      /**
       * Returns a string representation of an instance
       * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow
       * @return {String} Returns CSS3 text-shadow declaration
       */
      toString: function () {
        return [this.offsetX, this.offsetY, this.blur, this.color].join("px ");
      },

      /* _TO_SVG_START_ */
      /**
       * Returns SVG representation of a shadow
       * @param {fabric.Object} object
       * @return {String} SVG representation of a shadow
       */
      toSVG: function (object) {
        var mode = "SourceAlpha";

        if (
          object &&
          (object.fill === this.color || object.stroke === this.color)
        ) {
          mode = "SourceGraphic";
        }

        return (
          '<filter id="SVGID_' +
          this.id +
          '" y="-40%" height="180%">' +
          '<feGaussianBlur in="' +
          mode +
          '" stdDeviation="' +
          (this.blur ? this.blur / 3 : 0) +
          '"></feGaussianBlur>' +
          '<feOffset dx="' +
          this.offsetX +
          '" dy="' +
          this.offsetY +
          '"></feOffset>' +
          "<feMerge>" +
          "<feMergeNode></feMergeNode>" +
          '<feMergeNode in="SourceGraphic"></feMergeNode>' +
          "</feMerge>" +
          "</filter>"
        );
      },
      /* _TO_SVG_END_ */

      /**
       * Returns object representation of a shadow
       * @return {Object} Object representation of a shadow instance
       */
      toObject: function () {
        if (this.includeDefaultValues) {
          return {
            color: this.color,
            blur: this.blur,
            offsetX: this.offsetX,
            offsetY: this.offsetY
          };
        }
        var obj = {},
          proto = fabric.Shadow.prototype;
        if (this.color !== proto.color) {
          obj.color = this.color;
        }
        if (this.blur !== proto.blur) {
          obj.blur = this.blur;
        }
        if (this.offsetX !== proto.offsetX) {
          obj.offsetX = this.offsetX;
        }
        if (this.offsetY !== proto.offsetY) {
          obj.offsetY = this.offsetY;
        }
        return obj;
      }
    }
  );

  /**
   * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px")
   * @static
   * @field
   * @memberOf fabric.Shadow
   */
  fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/;
})(typeof exports !== "undefined" ? exports : this);

(function () {
  "use strict";

  if (fabric.StaticCanvas) {
    fabric.warn("fabric.StaticCanvas is already defined.");
    return;
  }

  // aliases for faster resolution
  var extend = fabric.util.object.extend,
    getElementOffset = fabric.util.getElementOffset,
    removeFromArray = fabric.util.removeFromArray,
    CANVAS_INIT_ERROR = new Error("Could not initialize `canvas` element");

  /**
   * Static canvas class
   * @class fabric.StaticCanvas
   * @mixes fabric.Collection
   * @mixes fabric.Observable
   * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo}
   * @see {@link fabric.StaticCanvas#initialize} for constructor definition
   * @fires before:render
   * @fires after:render
   * @fires canvas:cleared
   * @fires object:added
   * @fires object:removed
   */
  fabric.StaticCanvas = fabric.util.createClass(
    /** @lends fabric.StaticCanvas.prototype */ {
      /**
       * Constructor
       * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
       * @param {Object} [options] Options object
       * @return {Object} thisArg
       */
      initialize: function (el, options) {
        options || (options = {});

        this._initStatic(el, options);
        fabric.StaticCanvas.activeInstance = this;
      },

      /**
       * Background color of canvas instance.
       * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}.
       * @type {(String|fabric.Pattern)}
       * @default
       */
      backgroundColor: "",

      /**
       * Background image of canvas instance.
       * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}.
       * <b>Backwards incompatibility note:</b> The "backgroundImageOpacity"
       * and "backgroundImageStretch" properties are deprecated since 1.3.9.
       * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}.
       * @type fabric.Image
       * @default
       */
      backgroundImage: null,

      /**
       * Overlay color of canvas instance.
       * Should be set via {@link fabric.StaticCanvas#setOverlayColor}
       * @since 1.3.9
       * @type {(String|fabric.Pattern)}
       * @default
       */
      overlayColor: "",

      /**
       * Overlay image of canvas instance.
       * Should be set via {@link fabric.StaticCanvas#setOverlayImage}.
       * <b>Backwards incompatibility note:</b> The "overlayImageLeft"
       * and "overlayImageTop" properties are deprecated since 1.3.9.
       * Use {@link fabric.Image#left} and {@link fabric.Image#top}.
       * @type fabric.Image
       * @default
       */
      overlayImage: null,

      /**
       * Indicates whether toObject/toDatalessObject should include default values
       * @type Boolean
       * @default
       */
      includeDefaultValues: true,

      /**
       * Indicates whether objects' state should be saved
       * @type Boolean
       * @default
       */
      stateful: true,

      /**
       * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas.
       * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once
       * (followed by a manual rendering after addition/deletion)
       * @type Boolean
       * @default
       */
      renderOnAddRemove: true,

      /**
       * Function that determines clipping of entire canvas area
       * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ}
       * @type Function
       * @default
       */
      clipTo: null,

      /**
       * Indicates whether object controls (borders/controls) are rendered above overlay image
       * @type Boolean
       * @default
       */
      controlsAboveOverlay: false,

      /**
       * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas
       * @type Boolean
       * @default
       */
      allowTouchScrolling: false,

      /**
       * Indicates whether this canvas will use image smoothing, this is on by default in browsers
       * @type Boolean
       * @default
       */
      imageSmoothingEnabled: true,

      /**
       * The transformation (in the format of Canvas transform) which focuses the viewport
       * @type Array
       * @default
       */
      viewportTransform: [1, 0, 0, 1, 0, 0],

      /**
       * Callback; invoked right before object is about to be scaled/rotated
       */
      onBeforeScaleRotate: function () {
        /* NOOP */
      },

      /**
       * @private
       * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
       * @param {Object} [options] Options object
       */
      _initStatic: function (el, options) {
        this._objects = [];

        this._createLowerCanvas(el);
        this._initOptions(options);
        this._setImageSmoothing();

        if (options.overlayImage) {
          this.setOverlayImage(options.overlayImage, this.renderAll.bind(this));
        }
        if (options.backgroundImage) {
          this.setBackgroundImage(
            options.backgroundImage,
            this.renderAll.bind(this)
          );
        }
        if (options.backgroundColor) {
          this.setBackgroundColor(
            options.backgroundColor,
            this.renderAll.bind(this)
          );
        }
        if (options.overlayColor) {
          this.setOverlayColor(options.overlayColor, this.renderAll.bind(this));
        }
        this.calcOffset();
      },

      /**
       * Calculates canvas element offset relative to the document
       * This method is also attached as "resize" event handler of window
       * @return {fabric.Canvas} instance
       * @chainable
       */
      calcOffset: function () {
        this._offset = getElementOffset(this.lowerCanvasEl);
        return this;
      },

      /**
       * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas
       * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to
       * @param {Function} callback callback to invoke when image is loaded and set as an overlay
       * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}.
       * @return {fabric.Canvas} thisArg
       * @chainable
       * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo}
       * @example <caption>Normal overlayImage with left/top = 0</caption>
       * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
       *   // Needed to position overlayImage at 0/0
       *   originX: 'left',
       *   originY: 'top'
       * });
       * @example <caption>overlayImage with different properties</caption>
       * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
       *   opacity: 0.5,
       *   angle: 45,
       *   left: 400,
       *   top: 400,
       *   originX: 'left',
       *   originY: 'top'
       * });
       * @example <caption>Stretched overlayImage #1 - width/height correspond to canvas width/height</caption>
       * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) {
       *    img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
       *    canvas.setOverlayImage(img, canvas.renderAll.bind(canvas));
       * });
       * @example <caption>Stretched overlayImage #2 - width/height correspond to canvas width/height</caption>
       * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
       *   width: canvas.width,
       *   height: canvas.height,
       *   // Needed to position overlayImage at 0/0
       *   originX: 'left',
       *   originY: 'top'
       * });
       */
      setOverlayImage: function (image, callback, options) {
        return this.__setBgOverlayImage(
          "overlayImage",
          image,
          callback,
          options
        );
      },

      /**
       * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas
       * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to
       * @param {Function} callback Callback to invoke when image is loaded and set as background
       * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}.
       * @return {fabric.Canvas} thisArg
       * @chainable
       * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo}
       * @example <caption>Normal backgroundImage with left/top = 0</caption>
       * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
       *   // Needed to position backgroundImage at 0/0
       *   originX: 'left',
       *   originY: 'top'
       * });
       * @example <caption>backgroundImage with different properties</caption>
       * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
       *   opacity: 0.5,
       *   angle: 45,
       *   left: 400,
       *   top: 400,
       *   originX: 'left',
       *   originY: 'top'
       * });
       * @example <caption>Stretched backgroundImage #1 - width/height correspond to canvas width/height</caption>
       * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) {
       *    img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
       *    canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
       * });
       * @example <caption>Stretched backgroundImage #2 - width/height correspond to canvas width/height</caption>
       * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
       *   width: canvas.width,
       *   height: canvas.height,
       *   // Needed to position backgroundImage at 0/0
       *   originX: 'left',
       *   originY: 'top'
       * });
       */
      setBackgroundImage: function (image, callback, options) {
        return this.__setBgOverlayImage(
          "backgroundImage",
          image,
          callback,
          options
        );
      },

      /**
       * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas
       * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to
       * @param {Function} callback Callback to invoke when background color is set
       * @return {fabric.Canvas} thisArg
       * @chainable
       * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo}
       * @example <caption>Normal overlayColor - color value</caption>
       * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
       * @example <caption>fabric.Pattern used as overlayColor</caption>
       * canvas.setOverlayColor({
       *   source: 'http://fabricjs.com/assets/escheresque_ste.png'
       * }, canvas.renderAll.bind(canvas));
       * @example <caption>fabric.Pattern used as overlayColor with repeat and offset</caption>
       * canvas.setOverlayColor({
       *   source: 'http://fabricjs.com/assets/escheresque_ste.png',
       *   repeat: 'repeat',
       *   offsetX: 200,
       *   offsetY: 100
       * }, canvas.renderAll.bind(canvas));
       */
      setOverlayColor: function (overlayColor, callback) {
        return this.__setBgOverlayColor("overlayColor", overlayColor, callback);
      },

      /**
       * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas
       * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to
       * @param {Function} callback Callback to invoke when background color is set
       * @return {fabric.Canvas} thisArg
       * @chainable
       * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo}
       * @example <caption>Normal backgroundColor - color value</caption>
       * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
       * @example <caption>fabric.Pattern used as backgroundColor</caption>
       * canvas.setBackgroundColor({
       *   source: 'http://fabricjs.com/assets/escheresque_ste.png'
       * }, canvas.renderAll.bind(canvas));
       * @example <caption>fabric.Pattern used as backgroundColor with repeat and offset</caption>
       * canvas.setBackgroundColor({
       *   source: 'http://fabricjs.com/assets/escheresque_ste.png',
       *   repeat: 'repeat',
       *   offsetX: 200,
       *   offsetY: 100
       * }, canvas.renderAll.bind(canvas));
       */
      setBackgroundColor: function (backgroundColor, callback) {
        return this.__setBgOverlayColor(
          "backgroundColor",
          backgroundColor,
          callback
        );
      },

      /**
       * @private
       * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard}
       */
      _setImageSmoothing: function () {
        var ctx = this.getContext();

        ctx.imageSmoothingEnabled = this.imageSmoothingEnabled;
        ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled;
        ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled;
        ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled;
        ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled;
      },

      /**
       * @private
       * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage}
       * or {@link fabric.StaticCanvas#overlayImage|overlayImage})
       * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to
       * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay
       * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}.
       */
      __setBgOverlayImage: function (property, image, callback, options) {
        if (typeof image === "string") {
          fabric.util.loadImage(
            image,
            function (img) {
              this[property] = new fabric.Image(img, options);
              callback && callback();
            },
            this
          );
        } else {
          this[property] = image;
          callback && callback();
        }

        return this;
      },

      /**
       * @private
       * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor}
       * or {@link fabric.StaticCanvas#overlayColor|overlayColor})
       * @param {(Object|String|null)} color Object with pattern information, color value or null
       * @param {Function} [callback] Callback is invoked when color is set
       */
      __setBgOverlayColor: function (property, color, callback) {
        if (color && color.source) {
          var _this = this;
          fabric.util.loadImage(color.source, function (img) {
            _this[property] = new fabric.Pattern({
              source: img,
              repeat: color.repeat,
              offsetX: color.offsetX,
              offsetY: color.offsetY
            });
            callback && callback();
          });
        } else {
          this[property] = color;
          callback && callback();
        }

        return this;
      },

      /**
       * @private
       */
      _createCanvasElement: function () {
        var element = fabric.document.createElement("canvas");
        if (!element.style) {
          element.style = {};
        }
        if (!element) {
          throw CANVAS_INIT_ERROR;
        }
        this._initCanvasElement(element);
        return element;
      },

      /**
       * @private
       * @param {HTMLElement} element
       */
      _initCanvasElement: function (element) {
        fabric.util.createCanvasElement(element);

        if (typeof element.getContext === "undefined") {
          throw CANVAS_INIT_ERROR;
        }
      },

      /**
       * @private
       * @param {Object} [options] Options object
       */
      _initOptions: function (options) {
        for (var prop in options) {
          this[prop] = options[prop];
        }

        this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0;
        this.height =
          this.height || parseInt(this.lowerCanvasEl.height, 10) || 0;

        if (!this.lowerCanvasEl.style) return;

        this.lowerCanvasEl.width = this.width;
        this.lowerCanvasEl.height = this.height;

        this.lowerCanvasEl.style.width = this.width + "px";
        this.lowerCanvasEl.style.height = this.height + "px";

        this.viewportTransform = this.viewportTransform.slice();
      },

      /**
       * Creates a bottom canvas
       * @private
       * @param {HTMLElement} [canvasEl]
       */
      _createLowerCanvas: function (canvasEl) {
        this.lowerCanvasEl =
          fabric.util.getById(canvasEl) || this._createCanvasElement();
        this._initCanvasElement(this.lowerCanvasEl);

        fabric.util.addClass(this.lowerCanvasEl, "lower-canvas");

        if (this.interactive) {
          this._applyCanvasStyle(this.lowerCanvasEl);
        }

        this.contextContainer = this.lowerCanvasEl.getContext("2d");
      },

      /**
       * Returns canvas width (in px)
       * @return {Number}
       */
      getWidth: function () {
        return this.width;
      },

      /**
       * Returns canvas height (in px)
       * @return {Number}
       */
      getHeight: function () {
        return this.height;
      },

      /**
       * Sets width of this canvas instance
       * @param {Number} width value to set width to
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      setWidth: function (width) {
        return this._setDimension("width", width);
      },

      /**
       * Sets height of this canvas instance
       * @param {Number} height value to set height to
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      setHeight: function (height) {
        return this._setDimension("height", height);
      },

      /**
       * Sets dimensions (width, height) of this canvas instance
       * @param {Object} dimensions Object with width/height properties
       * @param {Number} [dimensions.width] Width of canvas element
       * @param {Number} [dimensions.height] Height of canvas element
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      setDimensions: function (dimensions) {
        for (var prop in dimensions) {
          this._setDimension(prop, dimensions[prop]);
        }
        return this;
      },

      /**
       * Helper for setting width/height
       * @private
       * @param {String} prop property (width|height)
       * @param {Number} value value to set property to
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      _setDimension: function (prop, value) {
        this.lowerCanvasEl[prop] = value;
        this.lowerCanvasEl.style[prop] = value + "px";

        if (this.upperCanvasEl) {
          this.upperCanvasEl[prop] = value;
          this.upperCanvasEl.style[prop] = value + "px";
        }

        if (this.cacheCanvasEl) {
          this.cacheCanvasEl[prop] = value;
        }

        if (this.wrapperEl) {
          this.wrapperEl.style[prop] = value + "px";
        }

        this[prop] = value;

        this.calcOffset();
        this.renderAll();

        return this;
      },

      /**
       * Returns canvas zoom level
       * @return {Number}
       */
      getZoom: function () {
        return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]);
      },

      /**
       * Sets viewport transform of this canvas instance
       * @param {Array} vpt the transform in the form of context.transform
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      setViewportTransform: function (vpt) {
        this.viewportTransform = vpt;
        this.renderAll();
        for (var i = 0, len = this._objects.length; i < len; i++) {
          this._objects[i].setCoords();
        }
        return this;
      },

      /**
       * Sets zoom level of this canvas instance, zoom centered around point
       * @param {fabric.Point} point to zoom with respect to
       * @param {Number} value to set zoom to, less than 1 zooms out
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      zoomToPoint: function (point, value) {
        // TODO: just change the scale, preserve other transformations
        var before = point;
        point = fabric.util.transformPoint(
          point,
          fabric.util.invertTransform(this.viewportTransform)
        );
        this.viewportTransform[0] = value;
        this.viewportTransform[3] = value;
        var after = fabric.util.transformPoint(point, this.viewportTransform);
        this.viewportTransform[4] += before.x - after.x;
        this.viewportTransform[5] += before.y - after.y;
        this.renderAll();
        for (var i = 0, len = this._objects.length; i < len; i++) {
          this._objects[i].setCoords();
        }
        return this;
      },

      /**
       * Sets zoom level of this canvas instance
       * @param {Number} value to set zoom to, less than 1 zooms out
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      setZoom: function (value) {
        this.zoomToPoint(new fabric.Point(0, 0), value);
        return this;
      },

      /**
       * Pan viewport so as to place point at top left corner of canvas
       * @param {fabric.Point} point to move to
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      absolutePan: function (point) {
        this.viewportTransform[4] = -point.x;
        this.viewportTransform[5] = -point.y;
        this.renderAll();
        for (var i = 0, len = this._objects.length; i < len; i++) {
          this._objects[i].setCoords();
        }
        return this;
      },

      /**
       * Pans viewpoint relatively
       * @param {fabric.Point} point (position vector) to move by
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      relativePan: function (point) {
        return this.absolutePan(
          new fabric.Point(
            -point.x - this.viewportTransform[4],
            -point.y - this.viewportTransform[5]
          )
        );
      },

      /**
       * Returns &lt;canvas> element corresponding to this instance
       * @return {HTMLCanvasElement}
       */
      getElement: function () {
        return this.lowerCanvasEl;
      },

      /**
       * Returns currently selected object, if any
       * @return {fabric.Object}
       */
      getActiveObject: function () {
        return null;
      },

      /**
       * Returns currently selected group of object, if any
       * @return {fabric.Group}
       */
      getActiveGroup: function () {
        return null;
      },

      /**
       * Given a context, renders an object on that context
       * @param {CanvasRenderingContext2D} ctx Context to render object on
       * @param {fabric.Object} object Object to render
       * @private
       */
      _draw: function (ctx, object) {
        if (!object) return;

        ctx.save();
        var v = this.viewportTransform;
        ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
        object.render(ctx);
        ctx.restore();
        if (!this.controlsAboveOverlay) object._renderControls(ctx);
      },

      /**
       * @private
       * @param {fabric.Object} obj Object that was added
       */
      _onObjectAdded: function (obj) {
        this.stateful && obj.setupState();
        obj.canvas = this;
        obj.setCoords();
        this.fire("object:added", { target: obj });
        obj.fire("added");
      },

      /**
       * @private
       * @param {fabric.Object} obj Object that was removed
       */
      _onObjectRemoved: function (obj) {
        // removing active object should fire "selection:cleared" events
        if (this.getActiveObject() === obj) {
          this.fire("before:selection:cleared", { target: obj });
          this._discardActiveObject();
          this.fire("selection:cleared");
        }

        this.fire("object:removed", { target: obj });
        obj.fire("removed");
      },

      /**
       * Clears specified context of canvas element
       * @param {CanvasRenderingContext2D} ctx Context to clear
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      clearContext: function (ctx) {
        ctx.clearRect(0, 0, this.width, this.height);
        return this;
      },

      /**
       * Returns context of canvas where objects are drawn
       * @return {CanvasRenderingContext2D}
       */
      getContext: function () {
        return this.contextContainer;
      },

      /**
       * Clears all contexts (background, main, top) of an instance
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      clear: function () {
        this._objects.length = 0;
        if (this.discardActiveGroup) {
          this.discardActiveGroup();
        }
        if (this.discardActiveObject) {
          this.discardActiveObject();
        }
        this.clearContext(this.contextContainer);
        if (this.contextTop) {
          this.clearContext(this.contextTop);
        }
        this.fire("canvas:cleared");
        this.renderAll();
        return this;
      },

      /**
       * Renders both the top canvas and the secondary container canvas.
       * @param {Boolean} [allOnTop] Whether we want to force all images to be rendered on the top canvas
       * @return {fabric.Canvas} instance
       * @chainable
       */
      renderAll: function (allOnTop) {
        var canvasToDrawOn = this[
            allOnTop === true && this.interactive
              ? "contextTop"
              : "contextContainer"
          ],
          activeGroup = this.getActiveGroup();

        if (this.contextTop && this.selection && !this._groupSelector) {
          this.clearContext(this.contextTop);
        }

        if (!allOnTop) {
          this.clearContext(canvasToDrawOn);
        }

        this.fire("before:render");

        if (this.clipTo) {
          fabric.util.clipContext(this, canvasToDrawOn);
        }

        this._renderBackground(canvasToDrawOn);
        this._renderObjects(canvasToDrawOn, activeGroup);
        this._renderActiveGroup(canvasToDrawOn, activeGroup);

        if (this.clipTo) {
          canvasToDrawOn.restore();
        }

        this._renderOverlay(canvasToDrawOn);

        if (this.controlsAboveOverlay && this.interactive) {
          this.drawControls(canvasToDrawOn);
        }

        this.fire("after:render");

        return this;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {fabric.Group} activeGroup
       */
      _renderObjects: function (ctx, activeGroup) {
        var i, length;

        // fast path
        if (!activeGroup) {
          for (i = 0, length = this._objects.length; i < length; ++i) {
            this._draw(ctx, this._objects[i]);
          }
        } else {
          for (i = 0, length = this._objects.length; i < length; ++i) {
            if (this._objects[i] && !activeGroup.contains(this._objects[i])) {
              this._draw(ctx, this._objects[i]);
            }
          }
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {fabric.Group} activeGroup
       */
      _renderActiveGroup: function (ctx, activeGroup) {
        // delegate rendering to group selection (if one exists)
        if (activeGroup) {
          //Store objects in group preserving order, then replace
          var sortedObjects = [];
          this.forEachObject(function (object) {
            if (activeGroup.contains(object)) {
              sortedObjects.push(object);
            }
          });
          activeGroup._set("objects", sortedObjects);
          this._draw(ctx, activeGroup);
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderBackground: function (ctx) {
        if (this.backgroundColor) {
          ctx.fillStyle = this.backgroundColor.toLive
            ? this.backgroundColor.toLive(ctx)
            : this.backgroundColor;

          ctx.fillRect(
            this.backgroundColor.offsetX || 0,
            this.backgroundColor.offsetY || 0,
            this.width,
            this.height
          );
        }
        if (this.backgroundImage) {
          this._draw(ctx, this.backgroundImage);
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderOverlay: function (ctx) {
        if (this.overlayColor) {
          ctx.fillStyle = this.overlayColor.toLive
            ? this.overlayColor.toLive(ctx)
            : this.overlayColor;

          ctx.fillRect(
            this.overlayColor.offsetX || 0,
            this.overlayColor.offsetY || 0,
            this.width,
            this.height
          );
        }
        if (this.overlayImage) {
          this._draw(ctx, this.overlayImage);
        }
      },

      /**
       * Method to render only the top canvas.
       * Also used to render the group selection box.
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      renderTop: function () {
        var ctx = this.contextTop || this.contextContainer;
        this.clearContext(ctx);

        // we render the top context - last object
        if (this.selection && this._groupSelector) {
          this._drawSelection();
        }

        // delegate rendering to group selection if one exists
        // used for drawing selection borders/controls
        var activeGroup = this.getActiveGroup();
        if (activeGroup) {
          activeGroup.render(ctx);
        }

        this._renderOverlay(ctx);

        this.fire("after:render");

        return this;
      },

      /**
       * Returns coordinates of a center of canvas.
       * Returned value is an object with top and left properties
       * @return {Object} object with "top" and "left" number values
       */
      getCenter: function () {
        return {
          top: this.getHeight() / 2,
          left: this.getWidth() / 2
        };
      },

      /**
       * Centers object horizontally.
       * You might need to call `setCoords` on an object after centering, to update controls area.
       * @param {fabric.Object} object Object to center horizontally
       * @return {fabric.Canvas} thisArg
       */
      centerObjectH: function (object) {
        this._centerObject(
          object,
          new fabric.Point(this.getCenter().left, object.getCenterPoint().y)
        );
        this.renderAll();
        return this;
      },

      /**
       * Centers object vertically.
       * You might need to call `setCoords` on an object after centering, to update controls area.
       * @param {fabric.Object} object Object to center vertically
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      centerObjectV: function (object) {
        this._centerObject(
          object,
          new fabric.Point(object.getCenterPoint().x, this.getCenter().top)
        );
        this.renderAll();
        return this;
      },

      /**
       * Centers object vertically and horizontally.
       * You might need to call `setCoords` on an object after centering, to update controls area.
       * @param {fabric.Object} object Object to center vertically and horizontally
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      centerObject: function (object) {
        var center = this.getCenter();

        this._centerObject(object, new fabric.Point(center.left, center.top));
        this.renderAll();
        return this;
      },

      /**
       * @private
       * @param {fabric.Object} object Object to center
       * @param {fabric.Point} center Center point
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      _centerObject: function (object, center) {
        object.setPositionByOrigin(center, "center", "center");
        return this;
      },

      /**
       * Returs dataless JSON representation of canvas
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {String} json string
       */
      toDatalessJSON: function (propertiesToInclude) {
        return this.toDatalessObject(propertiesToInclude);
      },

      /**
       * Returns object representation of canvas
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        return this._toObjectMethod("toObject", propertiesToInclude);
      },

      /**
       * Returns dataless object representation of canvas
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toDatalessObject: function (propertiesToInclude) {
        return this._toObjectMethod("toDatalessObject", propertiesToInclude);
      },

      /**
       * @private
       */
      _toObjectMethod: function (methodName, propertiesToInclude) {
        var activeGroup = this.getActiveGroup();
        if (activeGroup) {
          this.discardActiveGroup();
        }

        var data = {
          objects: this._toObjects(methodName, propertiesToInclude)
        };

        extend(data, this.__serializeBgOverlay());

        fabric.util.populateWithProperties(this, data, propertiesToInclude);

        if (activeGroup) {
          this.setActiveGroup(
            new fabric.Group(activeGroup.getObjects(), {
              originX: "center",
              originY: "center"
            })
          );
          activeGroup.forEachObject(function (o) {
            o.set("active", true);
          });

          if (this._currentTransform) {
            this._currentTransform.target = this.getActiveGroup();
          }
        }

        return data;
      },

      /**
       * @private
       */
      _toObjects: function (methodName, propertiesToInclude) {
        return this.getObjects().map(function (instance) {
          return this._toObject(instance, methodName, propertiesToInclude);
        }, this);
      },

      /**
       * @private
       */
      _toObject: function (instance, methodName, propertiesToInclude) {
        var originalValue;

        if (!this.includeDefaultValues) {
          originalValue = instance.includeDefaultValues;
          instance.includeDefaultValues = false;
        }
        var object = instance[methodName](propertiesToInclude);
        if (!this.includeDefaultValues) {
          instance.includeDefaultValues = originalValue;
        }
        return object;
      },

      /**
       * @private
       */
      __serializeBgOverlay: function () {
        var data = {
          background:
            this.backgroundColor && this.backgroundColor.toObject
              ? this.backgroundColor.toObject()
              : this.backgroundColor
        };

        if (this.overlayColor) {
          data.overlay = this.overlayColor.toObject
            ? this.overlayColor.toObject()
            : this.overlayColor;
        }
        if (this.backgroundImage) {
          data.backgroundImage = this.backgroundImage.toObject();
        }
        if (this.overlayImage) {
          data.overlayImage = this.overlayImage.toObject();
        }

        return data;
      },

      /* _TO_SVG_START_ */
      /**
       * Returns SVG representation of canvas
       * @function
       * @param {Object} [options] Options object for SVG output
       * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included
       * @param {Object} [options.viewBox] SVG viewbox object
       * @param {Number} [options.viewBox.x] x-cooridnate of viewbox
       * @param {Number} [options.viewBox.y] y-coordinate of viewbox
       * @param {Number} [options.viewBox.width] Width of viewbox
       * @param {Number} [options.viewBox.height] Height of viewbox
       * @param {String} [options.encoding=UTF-8] Encoding of SVG output
       * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation.
       * @return {String} SVG string
       * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization}
       * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo}
       * @example <caption>Normal SVG output</caption>
       * var svg = canvas.toSVG();
       * @example <caption>SVG output without preamble (without &lt;?xml ../>)</caption>
       * var svg = canvas.toSVG({suppressPreamble: true});
       * @example <caption>SVG output with viewBox attribute</caption>
       * var svg = canvas.toSVG({
       *   viewBox: {
       *     x: 100,
       *     y: 100,
       *     width: 200,
       *     height: 300
       *   }
       * });
       * @example <caption>SVG output with different encoding (default: UTF-8)</caption>
       * var svg = canvas.toSVG({encoding: 'ISO-8859-1'});
       * @example <caption>Modify SVG output with reviver function</caption>
       * var svg = canvas.toSVG(null, function(svg) {
       *   return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', '');
       * });
       */
      toSVG: function (options, reviver) {
        options || (options = {});

        var markup = [];

        this._setSVGPreamble(markup, options);
        this._setSVGHeader(markup, options);

        this._setSVGBgOverlayColor(markup, "backgroundColor");
        this._setSVGBgOverlayImage(markup, "backgroundImage");

        this._setSVGObjects(markup, reviver);

        this._setSVGBgOverlayColor(markup, "overlayColor");
        this._setSVGBgOverlayImage(markup, "overlayImage");

        markup.push("</svg>");

        return markup.join("");
      },

      /**
       * @private
       */
      _setSVGPreamble: function (markup, options) {
        if (!options.suppressPreamble) {
          markup.push(
            '<?xml version="1.0" encoding="',
            options.encoding || "UTF-8",
            '" standalone="no" ?>',
            '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ',
            '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n'
          );
        }
      },

      /**
       * @private
       */
      _setSVGHeader: function (markup, options) {
        markup.push(
          "<svg ",
          'xmlns="http://www.w3.org/2000/svg" ',
          'xmlns:xlink="http://www.w3.org/1999/xlink" ',
          'version="1.1" ',
          'width="',
          options.viewBox ? options.viewBox.width : this.width,
          '" ',
          'height="',
          options.viewBox ? options.viewBox.height : this.height,
          '" ',
          this.backgroundColor && !this.backgroundColor.toLive
            ? 'style="background-color: ' + this.backgroundColor + '" '
            : null,
          options.viewBox
            ? 'viewBox="' +
                options.viewBox.x +
                " " +
                options.viewBox.y +
                " " +
                options.viewBox.width +
                " " +
                options.viewBox.height +
                '" '
            : null,
          'xml:space="preserve">',
          "<desc>Created with Fabric.js ",
          fabric.version,
          "</desc>",
          "<defs>",
          fabric.createSVGFontFacesMarkup(this.getObjects()),
          fabric.createSVGRefElementsMarkup(this),
          "</defs>"
        );
      },

      /**
       * @private
       */
      _setSVGObjects: function (markup, reviver) {
        var activeGroup = this.getActiveGroup();
        if (activeGroup) {
          this.discardActiveGroup();
        }
        for (
          var i = 0, objects = this.getObjects(), len = objects.length;
          i < len;
          i++
        ) {
          markup.push(objects[i].toSVG(reviver));
        }
        if (activeGroup) {
          this.setActiveGroup(new fabric.Group(activeGroup.getObjects()));
          activeGroup.forEachObject(function (o) {
            o.set("active", true);
          });
        }
      },

      /**
       * @private
       */
      _setSVGBgOverlayImage: function (markup, property) {
        if (this[property] && this[property].toSVG) {
          markup.push(this[property].toSVG());
        }
      },

      /**
       * @private
       */
      _setSVGBgOverlayColor: function (markup, property) {
        if (this[property] && this[property].source) {
          markup.push(
            '<rect x="',
            this[property].offsetX,
            '" y="',
            this[property].offsetY,
            '" ',
            'width="',
            this[property].repeat === "repeat-y" ||
              this[property].repeat === "no-repeat"
              ? this[property].source.width
              : this.width,
            '" height="',
            this[property].repeat === "repeat-x" ||
              this[property].repeat === "no-repeat"
              ? this[property].source.height
              : this.height,
            '" fill="url(#' + property + 'Pattern)"',
            "></rect>"
          );
        } else if (this[property] && property === "overlayColor") {
          markup.push(
            '<rect x="0" y="0" ',
            'width="',
            this.width,
            '" height="',
            this.height,
            '" fill="',
            this[property],
            '"',
            "></rect>"
          );
        }
      },
      /* _TO_SVG_END_ */

      /**
       * Moves an object to the bottom of the stack of drawn objects
       * @param {fabric.Object} object Object to send to back
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      sendToBack: function (object) {
        removeFromArray(this._objects, object);
        this._objects.unshift(object);
        return this.renderAll && this.renderAll();
      },

      /**
       * Moves an object to the top of the stack of drawn objects
       * @param {fabric.Object} object Object to send
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      bringToFront: function (object) {
        removeFromArray(this._objects, object);
        this._objects.push(object);
        return this.renderAll && this.renderAll();
      },

      /**
       * Moves an object down in stack of drawn objects
       * @param {fabric.Object} object Object to send
       * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      sendBackwards: function (object, intersecting) {
        var idx = this._objects.indexOf(object);

        // if object is not on the bottom of stack
        if (idx !== 0) {
          var newIdx = this._findNewLowerIndex(object, idx, intersecting);

          removeFromArray(this._objects, object);
          this._objects.splice(newIdx, 0, object);
          this.renderAll && this.renderAll();
        }
        return this;
      },

      /**
       * @private
       */
      _findNewLowerIndex: function (object, idx, intersecting) {
        var newIdx;

        if (intersecting) {
          newIdx = idx;

          // traverse down the stack looking for the nearest intersecting object
          for (var i = idx - 1; i >= 0; --i) {
            var isIntersecting =
              object.intersectsWithObject(this._objects[i]) ||
              object.isContainedWithinObject(this._objects[i]) ||
              this._objects[i].isContainedWithinObject(object);

            if (isIntersecting) {
              newIdx = i;
              break;
            }
          }
        } else {
          newIdx = idx - 1;
        }

        return newIdx;
      },

      /**
       * Moves an object up in stack of drawn objects
       * @param {fabric.Object} object Object to send
       * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      bringForward: function (object, intersecting) {
        var idx = this._objects.indexOf(object);

        // if object is not on top of stack (last item in an array)
        if (idx !== this._objects.length - 1) {
          var newIdx = this._findNewUpperIndex(object, idx, intersecting);

          removeFromArray(this._objects, object);
          this._objects.splice(newIdx, 0, object);
          this.renderAll && this.renderAll();
        }
        return this;
      },

      /**
       * @private
       */
      _findNewUpperIndex: function (object, idx, intersecting) {
        var newIdx;

        if (intersecting) {
          newIdx = idx;

          // traverse up the stack looking for the nearest intersecting object
          for (var i = idx + 1; i < this._objects.length; ++i) {
            var isIntersecting =
              object.intersectsWithObject(this._objects[i]) ||
              object.isContainedWithinObject(this._objects[i]) ||
              this._objects[i].isContainedWithinObject(object);

            if (isIntersecting) {
              newIdx = i;
              break;
            }
          }
        } else {
          newIdx = idx + 1;
        }

        return newIdx;
      },

      /**
       * Moves an object to specified level in stack of drawn objects
       * @param {fabric.Object} object Object to send
       * @param {Number} index Position to move to
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      moveTo: function (object, index) {
        removeFromArray(this._objects, object);
        this._objects.splice(index, 0, object);
        return this.renderAll && this.renderAll();
      },

      /**
       * Clears a canvas element and removes all event listeners
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      dispose: function () {
        this.clear();
        this.interactive && this.removeListeners();
        return this;
      },

      /**
       * Returns a string representation of an instance
       * @return {String} string representation of an instance
       */
      toString: function () {
        return (
          "#<fabric.Canvas (" +
          this.complexity() +
          "): " +
          "{ objects: " +
          this.getObjects().length +
          " }>"
        );
      }
    }
  );

  extend(fabric.StaticCanvas.prototype, fabric.Observable);
  extend(fabric.StaticCanvas.prototype, fabric.Collection);
  extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter);

  extend(
    fabric.StaticCanvas,
    /** @lends fabric.StaticCanvas */ {
      /**
       * @static
       * @type String
       * @default
       */
      EMPTY_JSON: '{"objects": [], "background": "white"}',

      /**
       * Provides a way to check support of some of the canvas methods
       * (either those of HTMLCanvasElement itself, or rendering context)
       *
       * @param {String} methodName Method to check support for;
       *                            Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash"
       * @return {Boolean | null} `true` if method is supported (or at least exists),
       *                          `null` if canvas element or context can not be initialized
       */
      supports: function (methodName) {
        var el = fabric.util.createCanvasElement();

        if (!el || !el.getContext) {
          return null;
        }

        var ctx = el.getContext("2d");
        if (!ctx) {
          return null;
        }

        switch (methodName) {
          case "getImageData":
            return typeof ctx.getImageData !== "undefined";

          case "setLineDash":
            return typeof ctx.setLineDash !== "undefined";

          case "toDataURL":
            return typeof el.toDataURL !== "undefined";

          case "toDataURLWithQuality":
            try {
              el.toDataURL("image/jpeg", 0);
              return true;
            } catch (e) {}
            return false;

          default:
            return null;
        }
      }
    }
  );

  /**
   * Returns JSON representation of canvas
   * @function
   * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
   * @return {String} JSON string
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization}
   * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo}
   * @example <caption>JSON without additional properties</caption>
   * var json = canvas.toJSON();
   * @example <caption>JSON with additional properties included</caption>
   * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']);
   * @example <caption>JSON without default values</caption>
   * canvas.includeDefaultValues = false;
   * var json = canvas.toJSON();
   */
  fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
})();

/**
 * BaseBrush class
 * @class fabric.BaseBrush
 * @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo}
 */
fabric.BaseBrush = fabric.util.createClass(
  /** @lends fabric.BaseBrush.prototype */ {
    /**
     * Color of a brush
     * @type String
     * @default
     */
    color: "rgb(0, 0, 0)",

    /**
     * Width of a brush
     * @type Number
     * @default
     */
    width: 1,

    /**
     * Shadow object representing shadow of this shape.
     * <b>Backwards incompatibility note:</b> This property replaces "shadowColor" (String), "shadowOffsetX" (Number),
     * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12
     * @type fabric.Shadow
     * @default
     */
    shadow: null,

    /**
     * Line endings style of a brush (one of "butt", "round", "square")
     * @type String
     * @default
     */
    strokeLineCap: "round",

    /**
     * Corner style of a brush (one of "bevil", "round", "miter")
     * @type String
     * @default
     */
    strokeLineJoin: "round",

    /**
     * Sets shadow of an object
     * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)")
     * @return {fabric.Object} thisArg
     * @chainable
     */
    setShadow: function (options) {
      this.shadow = new fabric.Shadow(options);
      return this;
    },

    /**
     * Sets brush styles
     * @private
     */
    _setBrushStyles: function () {
      var ctx = this.canvas.contextTop;

      ctx.strokeStyle = this.color;
      ctx.lineWidth = this.width;
      ctx.lineCap = this.strokeLineCap;
      ctx.lineJoin = this.strokeLineJoin;
    },

    /**
     * Sets brush shadow styles
     * @private
     */
    _setShadow: function () {
      if (!this.shadow) return;

      var ctx = this.canvas.contextTop;

      ctx.shadowColor = this.shadow.color;
      ctx.shadowBlur = this.shadow.blur;
      ctx.shadowOffsetX = this.shadow.offsetX;
      ctx.shadowOffsetY = this.shadow.offsetY;
    },

    /**
     * Removes brush shadow styles
     * @private
     */
    _resetShadow: function () {
      var ctx = this.canvas.contextTop;

      ctx.shadowColor = "";
      ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
    }
  }
);

(function () {
  var utilMin = fabric.util.array.min,
    utilMax = fabric.util.array.max;

  /**
   * PencilBrush class
   * @class fabric.PencilBrush
   * @extends fabric.BaseBrush
   */
  fabric.PencilBrush = fabric.util.createClass(
    fabric.BaseBrush,
    /** @lends fabric.PencilBrush.prototype */ {
      /**
       * Constructor
       * @param {fabric.Canvas} canvas
       * @return {fabric.PencilBrush} Instance of a pencil brush
       */
      initialize: function (canvas) {
        this.canvas = canvas;
        this._points = [];
      },

      /**
       * Inovoked on mouse down
       * @param {Object} pointer
       */
      onMouseDown: function (pointer) {
        this._prepareForDrawing(pointer);
        // capture coordinates immediately
        // this allows to draw dots (when movement never occurs)
        this._captureDrawingPath(pointer);
        this._render();
      },

      /**
       * Inovoked on mouse move
       * @param {Object} pointer
       */
      onMouseMove: function (pointer) {
        this._captureDrawingPath(pointer);
        // redraw curve
        // clear top canvas
        this.canvas.clearContext(this.canvas.contextTop);
        this._render();
      },

      /**
       * Invoked on mouse up
       */
      onMouseUp: function () {
        this._finalizeAndAddPath();
      },

      /**
       * @private
       * @param {Object} pointer Actual mouse position related to the canvas.
       */
      _prepareForDrawing: function (pointer) {
        var p = new fabric.Point(pointer.x, pointer.y);

        this._reset();
        this._addPoint(p);

        this.canvas.contextTop.moveTo(p.x, p.y);
      },

      /**
       * @private
       * @param {fabric.Point} point Point to be added to points array
       */
      _addPoint: function (point) {
        this._points.push(point);
      },

      /**
       * Clear points array and set contextTop canvas style.
       * @private
       */
      _reset: function () {
        this._points.length = 0;

        this._setBrushStyles();
        this._setShadow();
      },

      /**
       * @private
       * @param {Object} pointer Actual mouse position related to the canvas.
       */
      _captureDrawingPath: function (pointer) {
        var pointerPoint = new fabric.Point(pointer.x, pointer.y);
        this._addPoint(pointerPoint);
      },

      /**
       * Draw a smooth path on the topCanvas using quadraticCurveTo
       * @private
       */
      _render: function () {
        var ctx = this.canvas.contextTop,
          v = this.canvas.viewportTransform,
          p1 = this._points[0],
          p2 = this._points[1];

        ctx.save();
        ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
        ctx.beginPath();

        //if we only have 2 points in the path and they are the same
        //it means that the user only clicked the canvas without moving the mouse
        //then we should be drawing a dot. A path isn't drawn between two identical dots
        //that's why we set them apart a bit
        if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) {
          p1.x -= 0.5;
          p2.x += 0.5;
        }
        ctx.moveTo(p1.x, p1.y);

        for (var i = 1, len = this._points.length; i < len; i++) {
          // we pick the point between pi + 1 & pi + 2 as the
          // end point and p1 as our control point.
          var midPoint = p1.midPointFrom(p2);
          ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);

          p1 = this._points[i];
          p2 = this._points[i + 1];
        }
        // Draw last line as a straight line while
        // we wait for the next point to be able to calculate
        // the bezier control point
        ctx.lineTo(p1.x, p1.y);
        ctx.stroke();
        ctx.restore();
      },

      /**
       * Return an SVG path based on our captured points and their bounding box
       * @private
       */
      _getSVGPathData: function () {
        this.box = this.getPathBoundingBox(this._points);
        return this.convertPointsToSVGPath(
          this._points,
          this.box.minX,
          this.box.minY
        );
      },

      /**
       * Returns bounding box of a path based on given points
       * @param {Array} points Array of points
       * @return {Object} Object with minX, minY, maxX, maxY
       */
      getPathBoundingBox: function (points) {
        var xBounds = [],
          yBounds = [],
          p1 = points[0],
          p2 = points[1],
          startPoint = p1;

        for (var i = 1, len = points.length; i < len; i++) {
          var midPoint = p1.midPointFrom(p2);
          // with startPoint, p1 as control point, midpoint as end point
          xBounds.push(startPoint.x);
          xBounds.push(midPoint.x);
          yBounds.push(startPoint.y);
          yBounds.push(midPoint.y);

          p1 = points[i];
          p2 = points[i + 1];
          startPoint = midPoint;
        }

        xBounds.push(p1.x);
        yBounds.push(p1.y);

        return {
          minX: utilMin(xBounds),
          minY: utilMin(yBounds),
          maxX: utilMax(xBounds),
          maxY: utilMax(yBounds)
        };
      },

      /**
       * Converts points to SVG path
       * @param {Array} points Array of points
       * @param {Number} minX
       * @param {Number} minY
       * @return {String} SVG path
       */
      convertPointsToSVGPath: function (points, minX, minY) {
        var path = [],
          p1 = new fabric.Point(points[0].x - minX, points[0].y - minY),
          p2 = new fabric.Point(points[1].x - minX, points[1].y - minY);

        path.push("M ", points[0].x - minX, " ", points[0].y - minY, " ");
        for (var i = 1, len = points.length; i < len; i++) {
          var midPoint = p1.midPointFrom(p2);
          // p1 is our bezier control point
          // midpoint is our endpoint
          // start point is p(i-1) value.
          path.push(
            "Q ",
            p1.x,
            " ",
            p1.y,
            " ",
            midPoint.x,
            " ",
            midPoint.y,
            " "
          );
          p1 = new fabric.Point(points[i].x - minX, points[i].y - minY);
          if (i + 1 < points.length) {
            p2 = new fabric.Point(
              points[i + 1].x - minX,
              points[i + 1].y - minY
            );
          }
        }
        path.push("L ", p1.x, " ", p1.y, " ");
        return path;
      },

      /**
       * Creates fabric.Path object to add on canvas
       * @param {String} pathData Path data
       * @return {fabric.Path} Path to add on canvas
       */
      createPath: function (pathData) {
        var path = new fabric.Path(pathData);
        path.fill = null;
        path.stroke = this.color;
        path.strokeWidth = this.width;
        path.strokeLineCap = this.strokeLineCap;
        path.strokeLineJoin = this.strokeLineJoin;

        if (this.shadow) {
          this.shadow.affectStroke = true;
          path.setShadow(this.shadow);
        }

        return path;
      },

      /**
       * On mouseup after drawing the path on contextTop canvas
       * we use the points captured to create an new fabric path object
       * and add it to the fabric canvas.
       */
      _finalizeAndAddPath: function () {
        var ctx = this.canvas.contextTop;
        ctx.closePath();

        var pathData = this._getSVGPathData().join("");
        if (pathData === "M 0 0 Q 0 0 0 0 L 0 0") {
          // do not create 0 width/height paths, as they are
          // rendered inconsistently across browsers
          // Firefox 4, for example, renders a dot,
          // whereas Chrome 10 renders nothing
          this.canvas.renderAll();
          return;
        }

        // set path origin coordinates based on our bounding box
        var originLeft = this.box.minx + (this.box.maxx - this.box.minx) / 2,
          originTop = this.box.miny + (this.box.maxy - this.box.miny) / 2;

        this.canvas.contextTop.arc(
          originLeft,
          originTop,
          3,
          0,
          Math.PI * 2,
          false
        );

        var path = this.createPath(pathData);
        path.set({
          left: originLeft,
          top: originTop,
          originX: "center",
          originY: "center"
        });

        this.canvas.add(path);
        path.setCoords();

        this.canvas.clearContext(this.canvas.contextTop);
        this._resetShadow();
        this.canvas.renderAll();

        // fire event 'path' created
        this.canvas.fire("path:created", { path: path });
      }
    }
  );
})();

/**
 * CircleBrush class
 * @class fabric.CircleBrush
 */
fabric.CircleBrush = fabric.util.createClass(
  fabric.BaseBrush,
  /** @lends fabric.CircleBrush.prototype */ {
    /**
     * Width of a brush
     * @type Number
     * @default
     */
    width: 10,

    /**
     * Constructor
     * @param {fabric.Canvas} canvas
     * @return {fabric.CircleBrush} Instance of a circle brush
     */
    initialize: function (canvas) {
      this.canvas = canvas;
      this.points = [];
    },
    /**
     * Invoked inside on mouse down and mouse move
     * @param {Object} pointer
     */
    drawDot: function (pointer) {
      var point = this.addPoint(pointer),
        ctx = this.canvas.contextTop,
        v = this.canvas.viewportTransform;
      ctx.save();
      ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);

      ctx.fillStyle = point.fill;
      ctx.beginPath();
      ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false);
      ctx.closePath();
      ctx.fill();

      ctx.restore();
    },

    /**
     * Invoked on mouse down
     */
    onMouseDown: function (pointer) {
      this.points.length = 0;
      this.canvas.clearContext(this.canvas.contextTop);
      this._setShadow();
      this.drawDot(pointer);
    },

    /**
     * Invoked on mouse move
     * @param {Object} pointer
     */
    onMouseMove: function (pointer) {
      this.drawDot(pointer);
    },

    /**
     * Invoked on mouse up
     */
    onMouseUp: function () {
      var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
      this.canvas.renderOnAddRemove = false;

      var circles = [];

      for (var i = 0, len = this.points.length; i < len; i++) {
        var point = this.points[i],
          circle = new fabric.Circle({
            radius: point.radius,
            left: point.x,
            top: point.y,
            originX: "center",
            originY: "center",
            fill: point.fill
          });

        this.shadow && circle.setShadow(this.shadow);

        circles.push(circle);
      }
      var group = new fabric.Group(circles, {
        originX: "center",
        originY: "center"
      });
      group.canvas = this.canvas;

      this.canvas.add(group);
      this.canvas.fire("path:created", { path: group });

      this.canvas.clearContext(this.canvas.contextTop);
      this._resetShadow();
      this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
      this.canvas.renderAll();
    },

    /**
     * @param {Object} pointer
     * @return {fabric.Point} Just added pointer point
     */
    addPoint: function (pointer) {
      var pointerPoint = new fabric.Point(pointer.x, pointer.y),
        circleRadius =
          fabric.util.getRandomInt(
            Math.max(0, this.width - 20),
            this.width + 20
          ) / 2,
        circleColor = new fabric.Color(this.color)
          .setAlpha(fabric.util.getRandomInt(0, 100) / 100)
          .toRgba();

      pointerPoint.radius = circleRadius;
      pointerPoint.fill = circleColor;

      this.points.push(pointerPoint);

      return pointerPoint;
    }
  }
);

/**
 * SprayBrush class
 * @class fabric.SprayBrush
 */
fabric.SprayBrush = fabric.util.createClass(
  fabric.BaseBrush,
  /** @lends fabric.SprayBrush.prototype */ {
    /**
     * Width of a spray
     * @type Number
     * @default
     */
    width: 10,

    /**
     * Density of a spray (number of dots per chunk)
     * @type Number
     * @default
     */
    density: 20,

    /**
     * Width of spray dots
     * @type Number
     * @default
     */
    dotWidth: 1,

    /**
     * Width variance of spray dots
     * @type Number
     * @default
     */
    dotWidthVariance: 1,

    /**
     * Whether opacity of a dot should be random
     * @type Boolean
     * @default
     */
    randomOpacity: false,

    /**
     * Whether overlapping dots (rectangles) should be removed (for performance reasons)
     * @type Boolean
     * @default
     */
    optimizeOverlapping: true,

    /**
     * Constructor
     * @param {fabric.Canvas} canvas
     * @return {fabric.SprayBrush} Instance of a spray brush
     */
    initialize: function (canvas) {
      this.canvas = canvas;
      this.sprayChunks = [];
    },

    /**
     * Invoked on mouse down
     * @param {Object} pointer
     */
    onMouseDown: function (pointer) {
      this.sprayChunks.length = 0;
      this.canvas.clearContext(this.canvas.contextTop);
      this._setShadow();

      this.addSprayChunk(pointer);
      this.render();
    },

    /**
     * Invoked on mouse move
     * @param {Object} pointer
     */
    onMouseMove: function (pointer) {
      this.addSprayChunk(pointer);
      this.render();
    },

    /**
     * Invoked on mouse up
     */
    onMouseUp: function () {
      var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
      this.canvas.renderOnAddRemove = false;

      var rects = [];

      for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) {
        var sprayChunk = this.sprayChunks[i];

        for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) {
          var rect = new fabric.Rect({
            width: sprayChunk[j].width,
            height: sprayChunk[j].width,
            left: sprayChunk[j].x + 1,
            top: sprayChunk[j].y + 1,
            originX: "center",
            originY: "center",
            fill: this.color
          });

          this.shadow && rect.setShadow(this.shadow);
          rects.push(rect);
        }
      }

      if (this.optimizeOverlapping) {
        rects = this._getOptimizedRects(rects);
      }

      var group = new fabric.Group(rects, {
        originX: "center",
        originY: "center"
      });
      group.canvas = this.canvas;

      this.canvas.add(group);
      this.canvas.fire("path:created", { path: group });

      this.canvas.clearContext(this.canvas.contextTop);
      this._resetShadow();
      this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
      this.canvas.renderAll();
    },

    /**
     * @private
     * @param {Array} rects
     */
    _getOptimizedRects: function (rects) {
      // avoid creating duplicate rects at the same coordinates
      var uniqueRects = {},
        key;

      for (var i = 0, len = rects.length; i < len; i++) {
        key = rects[i].left + "" + rects[i].top;
        if (!uniqueRects[key]) {
          uniqueRects[key] = rects[i];
        }
      }
      var uniqueRectsArray = [];
      for (key in uniqueRects) {
        uniqueRectsArray.push(uniqueRects[key]);
      }

      return uniqueRectsArray;
    },

    /**
     * Renders brush
     */
    render: function () {
      var ctx = this.canvas.contextTop;
      ctx.fillStyle = this.color;

      var v = this.canvas.viewportTransform;
      ctx.save();
      ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);

      for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) {
        var point = this.sprayChunkPoints[i];
        if (typeof point.opacity !== "undefined") {
          ctx.globalAlpha = point.opacity;
        }
        ctx.fillRect(point.x, point.y, point.width, point.width);
      }
      ctx.restore();
    },

    /**
     * @param {Object} pointer
     */
    addSprayChunk: function (pointer) {
      this.sprayChunkPoints = [];

      var x,
        y,
        width,
        radius = this.width / 2;

      for (var i = 0; i < this.density; i++) {
        x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius);
        y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius);

        if (this.dotWidthVariance) {
          width = fabric.util.getRandomInt(
            // bottom clamp width to 1
            Math.max(1, this.dotWidth - this.dotWidthVariance),
            this.dotWidth + this.dotWidthVariance
          );
        } else {
          width = this.dotWidth;
        }

        var point = new fabric.Point(x, y);
        point.width = width;

        if (this.randomOpacity) {
          point.opacity = fabric.util.getRandomInt(0, 100) / 100;
        }

        this.sprayChunkPoints.push(point);
      }

      this.sprayChunks.push(this.sprayChunkPoints);
    }
  }
);

/**
 * PatternBrush class
 * @class fabric.PatternBrush
 * @extends fabric.BaseBrush
 */
fabric.PatternBrush = fabric.util.createClass(
  fabric.PencilBrush,
  /** @lends fabric.PatternBrush.prototype */ {
    getPatternSrc: function () {
      var dotWidth = 20,
        dotDistance = 5,
        patternCanvas = fabric.document.createElement("canvas"),
        patternCtx = patternCanvas.getContext("2d");

      patternCanvas.width = patternCanvas.height = dotWidth + dotDistance;

      patternCtx.fillStyle = this.color;
      patternCtx.beginPath();
      patternCtx.arc(
        dotWidth / 2,
        dotWidth / 2,
        dotWidth / 2,
        0,
        Math.PI * 2,
        false
      );
      patternCtx.closePath();
      patternCtx.fill();

      return patternCanvas;
    },

    getPatternSrcFunction: function () {
      return String(this.getPatternSrc).replace(
        "this.color",
        '"' + this.color + '"'
      );
    },

    /**
     * Creates "pattern" instance property
     */
    getPattern: function () {
      return this.canvas.contextTop.createPattern(
        this.source || this.getPatternSrc(),
        "repeat"
      );
    },

    /**
     * Sets brush styles
     */
    _setBrushStyles: function () {
      this.callSuper("_setBrushStyles");
      this.canvas.contextTop.strokeStyle = this.getPattern();
    },

    /**
     * Creates path
     */
    createPath: function (pathData) {
      var path = this.callSuper("createPath", pathData);
      path.stroke = new fabric.Pattern({
        source: this.source || this.getPatternSrcFunction()
      });
      return path;
    }
  }
);

(function () {
  var getPointer = fabric.util.getPointer,
    degreesToRadians = fabric.util.degreesToRadians,
    radiansToDegrees = fabric.util.radiansToDegrees,
    atan2 = Math.atan2,
    abs = Math.abs,
    STROKE_OFFSET = 0.5;

  /**
   * Canvas class
   * @class fabric.Canvas
   * @extends fabric.StaticCanvas
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#canvas}
   * @see {@link fabric.Canvas#initialize} for constructor definition
   *
   * @fires object:modified
   * @fires object:rotating
   * @fires object:scaling
   * @fires object:moving
   * @fires object:selected
   *
   * @fires before:selection:cleared
   * @fires selection:cleared
   * @fires selection:created
   *
   * @fires path:created
   * @fires mouse:down
   * @fires mouse:move
   * @fires mouse:up
   * @fires mouse:over
   * @fires mouse:out
   *
   */
  fabric.Canvas = fabric.util.createClass(
    fabric.StaticCanvas,
    /** @lends fabric.Canvas.prototype */ {
      /**
       * Constructor
       * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
       * @param {Object} [options] Options object
       * @return {Object} thisArg
       */
      initialize: function (el, options) {
        options || (options = {});

        this._initStatic(el, options);
        this._initInteractive();
        this._createCacheCanvas();

        fabric.Canvas.activeInstance = this;
      },

      /**
       * When true, objects can be transformed by one side (unproportionally)
       * @type Boolean
       * @default
       */
      uniScaleTransform: false,

      /**
       * When true, objects use center point as the origin of scale transformation.
       * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
       * @since 1.3.4
       * @type Boolean
       * @default
       */
      centeredScaling: false,

      /**
       * When true, objects use center point as the origin of rotate transformation.
       * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
       * @since 1.3.4
       * @type Boolean
       * @default
       */
      centeredRotation: false,

      /**
       * Indicates that canvas is interactive. This property should not be changed.
       * @type Boolean
       * @default
       */
      interactive: true,

      /**
       * Indicates whether group selection should be enabled
       * @type Boolean
       * @default
       */
      selection: true,

      /**
       * Color of selection
       * @type String
       * @default
       */
      selectionColor: "rgba(100, 100, 255, 0.3)", // blue

      /**
       * Default dash array pattern
       * If not empty the selection border is dashed
       * @type Array
       */
      selectionDashArray: [],

      /**
       * Color of the border of selection (usually slightly darker than color of selection itself)
       * @type String
       * @default
       */
      selectionBorderColor: "rgba(255, 255, 255, 0.3)",

      /**
       * Width of a line used in object/group selection
       * @type Number
       * @default
       */
      selectionLineWidth: 1,

      /**
       * Default cursor value used when hovering over an object on canvas
       * @type String
       * @default
       */
      hoverCursor: "move",

      /**
       * Default cursor value used when moving an object on canvas
       * @type String
       * @default
       */
      moveCursor: "move",

      /**
       * Default cursor value used for the entire canvas
       * @type String
       * @default
       */
      defaultCursor: "default",

      /**
       * Cursor value used during free drawing
       * @type String
       * @default
       */
      freeDrawingCursor: "crosshair",

      /**
       * Cursor value used for rotation point
       * @type String
       * @default
       */
      rotationCursor: "crosshair",

      /**
       * Default element class that's given to wrapper (div) element of canvas
       * @type String
       * @default
       */
      containerClass: "canvas-container",

      /**
       * When true, object detection happens on per-pixel basis rather than on per-bounding-box
       * @type Boolean
       * @default
       */
      perPixelTargetFind: false,

      /**
       * Number of pixels around target pixel to tolerate (consider active) during object detection
       * @type Number
       * @default
       */
      targetFindTolerance: 0,

      /**
       * When true, target detection is skipped when hovering over canvas. This can be used to improve performance.
       * @type Boolean
       * @default
       */
      skipTargetFind: false,

      /**
       * @private
       */
      _initInteractive: function () {
        this._currentTransform = null;
        this._groupSelector = null;
        this._initWrapperElement();
        this._createUpperCanvas();
        this._initEventListeners();

        this.freeDrawingBrush =
          fabric.PencilBrush && new fabric.PencilBrush(this);

        this.calcOffset();
      },

      /**
       * Resets the current transform to its original values and chooses the type of resizing based on the event
       * @private
       * @param {Event} e Event object fired on mousemove
       */
      _resetCurrentTransform: function (e) {
        var t = this._currentTransform;

        t.target.set({
          scaleX: t.original.scaleX,
          scaleY: t.original.scaleY,
          left: t.original.left,
          top: t.original.top
        });

        if (this._shouldCenterTransform(e, t.target)) {
          if (t.action === "rotate") {
            this._setOriginToCenter(t.target);
          } else {
            if (t.originX !== "center") {
              if (t.originX === "right") {
                t.mouseXSign = -1;
              } else {
                t.mouseXSign = 1;
              }
            }
            if (t.originY !== "center") {
              if (t.originY === "bottom") {
                t.mouseYSign = -1;
              } else {
                t.mouseYSign = 1;
              }
            }

            t.originX = "center";
            t.originY = "center";
          }
        } else {
          t.originX = t.original.originX;
          t.originY = t.original.originY;
        }
      },

      /**
       * Checks if point is contained within an area of given object
       * @param {Event} e Event object
       * @param {fabric.Object} target Object to test against
       * @return {Boolean} true if point is contained within an area of given object
       */
      containsPoint: function (e, target) {
        var pointer = this.getPointer(e, true),
          xy = this._normalizePointer(target, pointer);

        // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
        // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
        return target.containsPoint(xy) || target._findTargetCorner(pointer);
      },

      /**
       * @private
       */
      _normalizePointer: function (object, pointer) {
        var activeGroup = this.getActiveGroup(),
          x = pointer.x,
          y = pointer.y,
          isObjectInGroup =
            activeGroup &&
            object.type !== "group" &&
            activeGroup.contains(object),
          lt;

        if (isObjectInGroup) {
          lt = new fabric.Point(activeGroup.left, activeGroup.top);
          lt = fabric.util.transformPoint(lt, this.viewportTransform, true);
          x -= lt.x;
          y -= lt.y;
        }
        return { x: x, y: y };
      },

      /**
       * Returns true if object is transparent at a certain location
       * @param {fabric.Object} target Object to check
       * @param {Number} x Left coordinate
       * @param {Number} y Top coordinate
       * @return {Boolean}
       */
      isTargetTransparent: function (target, x, y) {
        var hasBorders = target.hasBorders,
          transparentCorners = target.transparentCorners;

        target.hasBorders = target.transparentCorners = false;

        this._draw(this.contextCache, target);

        target.hasBorders = hasBorders;
        target.transparentCorners = transparentCorners;

        var isTransparent = fabric.util.isTransparent(
          this.contextCache,
          x,
          y,
          this.targetFindTolerance
        );

        this.clearContext(this.contextCache);

        return isTransparent;
      },

      /**
       * @private
       * @param {Event} e Event object
       * @param {fabric.Object} target
       */
      _shouldClearSelection: function (e, target) {
        var activeGroup = this.getActiveGroup(),
          activeObject = this.getActiveObject();

        return (
          !target ||
          (target &&
            activeGroup &&
            !activeGroup.contains(target) &&
            activeGroup !== target &&
            !e.shiftKey) ||
          (target && !target.evented) ||
          (target &&
            !target.selectable &&
            activeObject &&
            activeObject !== target)
        );
      },

      /**
       * @private
       * @param {Event} e Event object
       * @param {fabric.Object} target
       */
      _shouldCenterTransform: function (e, target) {
        if (!target) return;

        var t = this._currentTransform,
          centerTransform;

        if (
          t.action === "scale" ||
          t.action === "scaleX" ||
          t.action === "scaleY"
        ) {
          centerTransform = this.centeredScaling || target.centeredScaling;
        } else if (t.action === "rotate") {
          centerTransform = this.centeredRotation || target.centeredRotation;
        }

        return centerTransform ? !e.altKey : e.altKey;
      },

      /**
       * @private
       */
      _getOriginFromCorner: function (target, corner) {
        var origin = {
          x: target.originX,
          y: target.originY
        };

        if (corner === "ml" || corner === "tl" || corner === "bl") {
          origin.x = "right";
        } else if (corner === "mr" || corner === "tr" || corner === "br") {
          origin.x = "left";
        }

        if (corner === "tl" || corner === "mt" || corner === "tr") {
          origin.y = "bottom";
        } else if (corner === "bl" || corner === "mb" || corner === "br") {
          origin.y = "top";
        }

        return origin;
      },

      /**
       * @private
       */
      _getActionFromCorner: function (target, corner) {
        var action = "drag";
        if (corner) {
          action =
            corner === "ml" || corner === "mr"
              ? "scaleX"
              : corner === "mt" || corner === "mb"
              ? "scaleY"
              : corner === "mtr"
              ? "rotate"
              : "scale";
        }
        return action;
      },

      /**
       * @private
       * @param {Event} e Event object
       * @param {fabric.Object} target
       */
      _setupCurrentTransform: function (e, target) {
        if (!target) return;

        var pointer = this.getPointer(e),
          corner = target._findTargetCorner(this.getPointer(e, true)),
          action = this._getActionFromCorner(target, corner),
          origin = this._getOriginFromCorner(target, corner);

        this._currentTransform = {
          target: target,
          action: action,
          scaleX: target.scaleX,
          scaleY: target.scaleY,
          offsetX: pointer.x - target.left,
          offsetY: pointer.y - target.top,
          originX: origin.x,
          originY: origin.y,
          ex: pointer.x,
          ey: pointer.y,
          left: target.left,
          top: target.top,
          theta: degreesToRadians(target.angle),
          width: target.width * target.scaleX,
          mouseXSign: 1,
          mouseYSign: 1
        };

        this._currentTransform.original = {
          left: target.left,
          top: target.top,
          scaleX: target.scaleX,
          scaleY: target.scaleY,
          originX: origin.x,
          originY: origin.y
        };

        this._resetCurrentTransform(e);
      },

      /**
       * Translates object by "setting" its left/top
       * @private
       * @param {Number} x pointer's x coordinate
       * @param {Number} y pointer's y coordinate
       */
      _translateObject: function (x, y) {
        var target = this._currentTransform.target;

        if (!target.get("lockMovementX")) {
          target.set("left", x - this._currentTransform.offsetX);
        }
        if (!target.get("lockMovementY")) {
          target.set("top", y - this._currentTransform.offsetY);
        }
      },

      /**
       * Scales object by invoking its scaleX/scaleY methods
       * @private
       * @param {Number} x pointer's x coordinate
       * @param {Number} y pointer's y coordinate
       * @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object.
       *                    When not provided, an object is scaled by both dimensions equally
       */
      _scaleObject: function (x, y, by) {
        var t = this._currentTransform,
          target = t.target,
          lockScalingX = target.get("lockScalingX"),
          lockScalingY = target.get("lockScalingY");

        if (lockScalingX && lockScalingY) return;

        // Get the constraint point
        var constraintPosition = target.translateToOriginPoint(
            target.getCenterPoint(),
            t.originX,
            t.originY
          ),
          localMouse = target.toLocalPoint(
            new fabric.Point(x, y),
            t.originX,
            t.originY
          );

        this._setLocalMouse(localMouse, t);

        // Actually scale the object
        this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by);

        // Make sure the constraints apply
        target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
      },

      /**
       * @private
       */
      _setObjectScale: function (
        localMouse,
        transform,
        lockScalingX,
        lockScalingY,
        by
      ) {
        var target = transform.target;

        transform.newScaleX = target.scaleX;
        transform.newScaleY = target.scaleY;

        if (by === "equally" && !lockScalingX && !lockScalingY) {
          this._scaleObjectEqually(localMouse, target, transform);
        } else if (!by) {
          transform.newScaleX =
            localMouse.x / (target.width + target.strokeWidth);
          transform.newScaleY =
            localMouse.y / (target.height + target.strokeWidth);

          lockScalingX || target.set("scaleX", transform.newScaleX);
          lockScalingY || target.set("scaleY", transform.newScaleY);
        } else if (by === "x" && !target.get("lockUniScaling")) {
          transform.newScaleX =
            localMouse.x / (target.width + target.strokeWidth);
          lockScalingX || target.set("scaleX", transform.newScaleX);
        } else if (by === "y" && !target.get("lockUniScaling")) {
          transform.newScaleY =
            localMouse.y / (target.height + target.strokeWidth);
          lockScalingY || target.set("scaleY", transform.newScaleY);
        }

        this._flipObject(transform);
      },

      /**
       * @private
       */
      _scaleObjectEqually: function (localMouse, target, transform) {
        var dist = localMouse.y + localMouse.x,
          lastDist =
            (target.height + target.strokeWidth) * transform.original.scaleY +
            (target.width + target.strokeWidth) * transform.original.scaleX;

        // We use transform.scaleX/Y instead of target.scaleX/Y
        // because the object may have a min scale and we'll loose the proportions
        transform.newScaleX = (transform.original.scaleX * dist) / lastDist;
        transform.newScaleY = (transform.original.scaleY * dist) / lastDist;

        target.set("scaleX", transform.newScaleX);
        target.set("scaleY", transform.newScaleY);
      },

      /**
       * @private
       */
      _flipObject: function (transform) {
        if (transform.newScaleX < 0) {
          if (transform.originX === "left") {
            transform.originX = "right";
          } else if (transform.originX === "right") {
            transform.originX = "left";
          }
        }

        if (transform.newScaleY < 0) {
          if (transform.originY === "top") {
            transform.originY = "bottom";
          } else if (transform.originY === "bottom") {
            transform.originY = "top";
          }
        }
      },

      /**
       * @private
       */
      _setLocalMouse: function (localMouse, t) {
        var target = t.target;

        if (t.originX === "right") {
          localMouse.x *= -1;
        } else if (t.originX === "center") {
          localMouse.x *= t.mouseXSign * 2;

          if (localMouse.x < 0) {
            t.mouseXSign = -t.mouseXSign;
          }
        }

        if (t.originY === "bottom") {
          localMouse.y *= -1;
        } else if (t.originY === "center") {
          localMouse.y *= t.mouseYSign * 2;

          if (localMouse.y < 0) {
            t.mouseYSign = -t.mouseYSign;
          }
        }

        // adjust the mouse coordinates when dealing with padding
        if (abs(localMouse.x) > target.padding) {
          if (localMouse.x < 0) {
            localMouse.x += target.padding;
          } else {
            localMouse.x -= target.padding;
          }
        } else {
          // mouse is within the padding, set to 0
          localMouse.x = 0;
        }

        if (abs(localMouse.y) > target.padding) {
          if (localMouse.y < 0) {
            localMouse.y += target.padding;
          } else {
            localMouse.y -= target.padding;
          }
        } else {
          localMouse.y = 0;
        }
      },

      /**
       * Rotates object by invoking its rotate method
       * @private
       * @param {Number} x pointer's x coordinate
       * @param {Number} y pointer's y coordinate
       */
      _rotateObject: function (x, y) {
        var t = this._currentTransform;

        if (t.target.get("lockRotation")) return;

        var lastAngle = atan2(t.ey - t.top, t.ex - t.left),
          curAngle = atan2(y - t.top, x - t.left),
          angle = radiansToDegrees(curAngle - lastAngle + t.theta);

        // normalize angle to positive value
        if (angle < 0) {
          angle = 360 + angle;
        }

        t.target.angle = angle;
      },

      /**
       * Set the cursor type of the canvas element
       * @param {String} value Cursor type of the canvas element.
       * @see http://www.w3.org/TR/css3-ui/#cursor
       */
      setCursor: function (value) {
        this.upperCanvasEl.style.cursor = value;
      },

      /**
       * @private
       */
      _resetObjectTransform: function (target) {
        target.scaleX = 1;
        target.scaleY = 1;
        target.setAngle(0);
      },

      /**
       * @private
       */
      _drawSelection: function () {
        var ctx = this.contextTop,
          groupSelector = this._groupSelector,
          left = groupSelector.left,
          top = groupSelector.top,
          aleft = abs(left),
          atop = abs(top);

        ctx.fillStyle = this.selectionColor;

        ctx.fillRect(
          groupSelector.ex - (left > 0 ? 0 : -left),
          groupSelector.ey - (top > 0 ? 0 : -top),
          aleft,
          atop
        );

        ctx.lineWidth = this.selectionLineWidth;
        ctx.strokeStyle = this.selectionBorderColor;

        // selection border
        if (this.selectionDashArray.length > 1) {
          var px = groupSelector.ex + STROKE_OFFSET - (left > 0 ? 0 : aleft),
            py = groupSelector.ey + STROKE_OFFSET - (top > 0 ? 0 : atop);

          ctx.beginPath();

          fabric.util.drawDashedLine(
            ctx,
            px,
            py,
            px + aleft,
            py,
            this.selectionDashArray
          );
          fabric.util.drawDashedLine(
            ctx,
            px,
            py + atop - 1,
            px + aleft,
            py + atop - 1,
            this.selectionDashArray
          );
          fabric.util.drawDashedLine(
            ctx,
            px,
            py,
            px,
            py + atop,
            this.selectionDashArray
          );
          fabric.util.drawDashedLine(
            ctx,
            px + aleft - 1,
            py,
            px + aleft - 1,
            py + atop,
            this.selectionDashArray
          );

          ctx.closePath();
          ctx.stroke();
        } else {
          ctx.strokeRect(
            groupSelector.ex + STROKE_OFFSET - (left > 0 ? 0 : aleft),
            groupSelector.ey + STROKE_OFFSET - (top > 0 ? 0 : atop),
            aleft,
            atop
          );
        }
      },

      /**
       * @private
       */
      _isLastRenderedObject: function (e) {
        return (
          this.controlsAboveOverlay &&
          this.lastRenderedObjectWithControlsAboveOverlay &&
          this.lastRenderedObjectWithControlsAboveOverlay.visible &&
          this.containsPoint(
            e,
            this.lastRenderedObjectWithControlsAboveOverlay
          ) &&
          this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(
            this.getPointer(e, true)
          )
        );
      },

      /**
       * Method that determines what object we are clicking on
       * @param {Event} e mouse event
       * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through
       */
      findTarget: function (e, skipGroup) {
        if (this.skipTargetFind) return;

        if (this._isLastRenderedObject(e)) {
          return this.lastRenderedObjectWithControlsAboveOverlay;
        }

        // first check current group (if one exists)
        var activeGroup = this.getActiveGroup();
        if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) {
          return activeGroup;
        }

        var target = this._searchPossibleTargets(e);
        this._fireOverOutEvents(target);

        return target;
      },

      /**
       * @private
       */
      _fireOverOutEvents: function (target) {
        if (target) {
          if (this._hoveredTarget !== target) {
            this.fire("mouse:over", { target: target });
            target.fire("mouseover");
            if (this._hoveredTarget) {
              this.fire("mouse:out", { target: this._hoveredTarget });
              this._hoveredTarget.fire("mouseout");
            }
            this._hoveredTarget = target;
          }
        } else if (this._hoveredTarget) {
          this.fire("mouse:out", { target: this._hoveredTarget });
          this._hoveredTarget.fire("mouseout");
          this._hoveredTarget = null;
        }
      },

      /**
       * @private
       */
      _checkTarget: function (e, obj, pointer) {
        if (obj && obj.visible && obj.evented && this.containsPoint(e, obj)) {
          if (
            (this.perPixelTargetFind || obj.perPixelTargetFind) &&
            !obj.isEditing
          ) {
            var isTransparent = this.isTargetTransparent(
              obj,
              pointer.x,
              pointer.y
            );
            if (!isTransparent) {
              return true;
            }
          } else {
            return true;
          }
        }
      },

      /**
       * @private
       */
      _searchPossibleTargets: function (e) {
        // Cache all targets where their bounding box contains point.
        var target,
          pointer = this.getPointer(e, true),
          i = this._objects.length;

        while (i--) {
          if (this._checkTarget(e, this._objects[i], pointer)) {
            this.relatedTarget = this._objects[i];
            target = this._objects[i];
            break;
          }
        }

        return target;
      },

      /**
       * Returns pointer coordinates relative to canvas.
       * @param {Event} e
       * @return {Object} object with "x" and "y" number values
       */
      getPointer: function (e, ignoreZoom, upperCanvasEl) {
        if (!upperCanvasEl) {
          upperCanvasEl = this.upperCanvasEl;
        }
        var pointer = getPointer(e, upperCanvasEl),
          bounds = upperCanvasEl.getBoundingClientRect(),
          cssScale;

        pointer.x = pointer.x - this._offset.left;
        pointer.y = pointer.y - this._offset.top;
        if (!ignoreZoom) {
          pointer = fabric.util.transformPoint(
            pointer,
            fabric.util.invertTransform(this.viewportTransform)
          );
        }

        if (bounds.width === 0 || bounds.height === 0) {
          // If bounds are not available (i.e. not visible), do not apply scale.
          cssScale = { width: 1, height: 1 };
        } else {
          cssScale = {
            width: upperCanvasEl.width / bounds.width,
            height: upperCanvasEl.height / bounds.height
          };
        }
        return {
          x: pointer.x * cssScale.width,
          y: pointer.y * cssScale.height
        };
      },

      /**
       * @private
       * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
       */
      _createUpperCanvas: function () {
        var lowerCanvasClass = this.lowerCanvasEl.className.replace(
          /\s*lower-canvas\s*/,
          ""
        );

        this.upperCanvasEl = this._createCanvasElement();
        fabric.util.addClass(
          this.upperCanvasEl,
          "upper-canvas " + lowerCanvasClass
        );

        this.wrapperEl.appendChild(this.upperCanvasEl);

        this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl);
        this._applyCanvasStyle(this.upperCanvasEl);
        this.contextTop = this.upperCanvasEl.getContext("2d");
      },

      /**
       * @private
       */
      _createCacheCanvas: function () {
        this.cacheCanvasEl = this._createCanvasElement();
        this.cacheCanvasEl.setAttribute("width", this.width);
        this.cacheCanvasEl.setAttribute("height", this.height);
        this.contextCache = this.cacheCanvasEl.getContext("2d");
      },

      /**
       * @private
       */
      _initWrapperElement: function () {
        this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, "div", {
          class: this.containerClass
        });
        fabric.util.setStyle(this.wrapperEl, {
          width: this.getWidth() + "px",
          height: this.getHeight() + "px",
          position: "relative"
        });
        fabric.util.makeElementUnselectable(this.wrapperEl);
      },

      /**
       * @private
       * @param {HTMLElement} element canvas element to apply styles on
       */
      _applyCanvasStyle: function (element) {
        var width = this.getWidth() || element.width,
          height = this.getHeight() || element.height;

        fabric.util.setStyle(element, {
          position: "absolute",
          width: width + "px",
          height: height + "px",
          left: 0,
          top: 0
        });
        element.width = width;
        element.height = height;
        fabric.util.makeElementUnselectable(element);
      },

      /**
       * Copys the the entire inline style from one element (fromEl) to another (toEl)
       * @private
       * @param {Element} fromEl Element style is copied from
       * @param {Element} toEl Element copied style is applied to
       */
      _copyCanvasStyle: function (fromEl, toEl) {
        toEl.style.cssText = fromEl.style.cssText;
      },

      /**
       * Returns context of canvas where object selection is drawn
       * @return {CanvasRenderingContext2D}
       */
      getSelectionContext: function () {
        return this.contextTop;
      },

      /**
       * Returns &lt;canvas> element on which object selection is drawn
       * @return {HTMLCanvasElement}
       */
      getSelectionElement: function () {
        return this.upperCanvasEl;
      },

      /**
       * @private
       * @param {Object} object
       */
      _setActiveObject: function (object) {
        if (this._activeObject) {
          this._activeObject.set("active", false);
        }
        this._activeObject = object;
        object.set("active", true);
      },

      /**
       * Sets given object as the only active object on canvas
       * @param {fabric.Object} object Object to set as an active one
       * @param {Event} [e] Event (passed along when firing "object:selected")
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      setActiveObject: function (object, e) {
        this._setActiveObject(object);
        this.renderAll();
        this.fire("object:selected", { target: object, e: e });
        object.fire("selected", { e: e });
        return this;
      },

      /**
       * Returns currently active object
       * @return {fabric.Object} active object
       */
      getActiveObject: function () {
        return this._activeObject;
      },

      /**
       * @private
       */
      _discardActiveObject: function () {
        if (this._activeObject) {
          this._activeObject.set("active", false);
        }
        this._activeObject = null;
      },

      /**
       * Discards currently active object
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      discardActiveObject: function (e) {
        this._discardActiveObject();
        this.renderAll();
        this.fire("selection:cleared", { e: e });
        return this;
      },

      /**
       * @private
       * @param {fabric.Group} group
       */
      _setActiveGroup: function (group) {
        this._activeGroup = group;
        if (group) {
          group.set("active", true);
        }
      },

      /**
       * Sets active group to a speicified one
       * @param {fabric.Group} group Group to set as a current one
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      setActiveGroup: function (group, e) {
        this._setActiveGroup(group);
        if (group) {
          this.fire("object:selected", { target: group, e: e });
          group.fire("selected", { e: e });
        }
        return this;
      },

      /**
       * Returns currently active group
       * @return {fabric.Group} Current group
       */
      getActiveGroup: function () {
        return this._activeGroup;
      },

      /**
       * @private
       */
      _discardActiveGroup: function () {
        var g = this.getActiveGroup();
        if (g) {
          g.destroy();
        }
        this.setActiveGroup(null);
      },

      /**
       * Discards currently active group
       * @return {fabric.Canvas} thisArg
       */
      discardActiveGroup: function (e) {
        this._discardActiveGroup();
        this.fire("selection:cleared", { e: e });
        return this;
      },

      /**
       * Deactivates all objects on canvas, removing any active group or object
       * @return {fabric.Canvas} thisArg
       */
      deactivateAll: function () {
        var allObjects = this.getObjects(),
          i = 0,
          len = allObjects.length;
        for (; i < len; i++) {
          allObjects[i].set("active", false);
        }
        this._discardActiveGroup();
        this._discardActiveObject();
        return this;
      },

      /**
       * Deactivates all objects and dispatches appropriate events
       * @return {fabric.Canvas} thisArg
       */
      deactivateAllWithDispatch: function (e) {
        var activeObject = this.getActiveGroup() || this.getActiveObject();
        if (activeObject) {
          this.fire("before:selection:cleared", { target: activeObject, e: e });
        }
        this.deactivateAll();
        if (activeObject) {
          this.fire("selection:cleared", { e: e });
        }
        return this;
      },

      /**
       * Draws objects' controls (borders/controls)
       * @param {CanvasRenderingContext2D} ctx Context to render controls on
       */
      drawControls: function (ctx) {
        var activeGroup = this.getActiveGroup();
        if (activeGroup) {
          this._drawGroupControls(ctx, activeGroup);
        } else {
          this._drawObjectsControls(ctx);
        }
      },

      /**
       * @private
       */
      _drawGroupControls: function (ctx, activeGroup) {
        activeGroup._renderControls(ctx);
      },

      /**
       * @private
       */
      _drawObjectsControls: function (ctx) {
        for (var i = 0, len = this._objects.length; i < len; ++i) {
          if (!this._objects[i] || !this._objects[i].active) continue;
          this._objects[i]._renderControls(ctx);
          this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i];
        }
      }
    }
  );

  // copying static properties manually to work around Opera's bug,
  // where "prototype" property is enumerable and overrides existing prototype
  for (var prop in fabric.StaticCanvas) {
    if (prop !== "prototype") {
      fabric.Canvas[prop] = fabric.StaticCanvas[prop];
    }
  }

  if (fabric.isTouchSupported) {
    /** @ignore */
    fabric.Canvas.prototype._setCursorFromEvent = function () {};
  }

  /**
   * @class fabric.Element
   * @alias fabric.Canvas
   * @deprecated Use {@link fabric.Canvas} instead.
   * @constructor
   */
  fabric.Element = fabric.Canvas;
})();

(function () {
  var cursorOffset = {
      mt: 0, // n
      tr: 1, // ne
      mr: 2, // e
      br: 3, // se
      mb: 4, // s
      bl: 5, // sw
      ml: 6, // w
      tl: 7 // nw
    },
    addListener = fabric.util.addListener,
    removeListener = fabric.util.removeListener;

  fabric.util.object.extend(
    fabric.Canvas.prototype,
    /** @lends fabric.Canvas.prototype */ {
      /**
       * Map of cursor style values for each of the object controls
       * @private
       */
      cursorMap: [
        "n-resize",
        "ne-resize",
        "e-resize",
        "se-resize",
        "s-resize",
        "sw-resize",
        "w-resize",
        "nw-resize"
      ],

      /**
       * Adds mouse listeners to canvas
       * @private
       */
      _initEventListeners: function () {
        this._bindEvents();

        addListener(fabric.window, "resize", this._onResize);

        // mouse events
        addListener(this.upperCanvasEl, "mousedown", this._onMouseDown);
        addListener(this.upperCanvasEl, "mousemove", this._onMouseMove);
        addListener(this.upperCanvasEl, "mousewheel", this._onMouseWheel);

        // touch events
        addListener(this.upperCanvasEl, "touchstart", this._onMouseDown);
        addListener(this.upperCanvasEl, "touchmove", this._onMouseMove);

        if (typeof Event !== "undefined" && "add" in Event) {
          Event.add(this.upperCanvasEl, "gesture", this._onGesture);
          Event.add(this.upperCanvasEl, "drag", this._onDrag);
          Event.add(
            this.upperCanvasEl,
            "orientation",
            this._onOrientationChange
          );
          Event.add(this.upperCanvasEl, "shake", this._onShake);
        }
      },

      /**
       * @private
       */
      _bindEvents: function () {
        this._onMouseDown = this._onMouseDown.bind(this);
        this._onMouseMove = this._onMouseMove.bind(this);
        this._onMouseUp = this._onMouseUp.bind(this);
        this._onResize = this._onResize.bind(this);
        this._onGesture = this._onGesture.bind(this);
        this._onDrag = this._onDrag.bind(this);
        this._onShake = this._onShake.bind(this);
        this._onOrientationChange = this._onOrientationChange.bind(this);
        this._onMouseWheel = this._onMouseWheel.bind(this);
      },

      /**
       * Removes all event listeners
       */
      removeListeners: function () {
        removeListener(fabric.window, "resize", this._onResize);

        removeListener(this.upperCanvasEl, "mousedown", this._onMouseDown);
        removeListener(this.upperCanvasEl, "mousemove", this._onMouseMove);
        removeListener(this.upperCanvasEl, "mousewheel", this._onMouseWheel);

        removeListener(this.upperCanvasEl, "touchstart", this._onMouseDown);
        removeListener(this.upperCanvasEl, "touchmove", this._onMouseMove);

        if (typeof Event !== "undefined" && "remove" in Event) {
          Event.remove(this.upperCanvasEl, "gesture", this._onGesture);
          Event.remove(this.upperCanvasEl, "drag", this._onDrag);
          Event.remove(
            this.upperCanvasEl,
            "orientation",
            this._onOrientationChange
          );
          Event.remove(this.upperCanvasEl, "shake", this._onShake);
        }
      },

      /**
       * @private
       * @param {Event} [e] Event object fired on Event.js gesture
       * @param {Event} [self] Inner Event object
       */
      _onGesture: function (e, self) {
        this.__onTransformGesture && this.__onTransformGesture(e, self);
      },

      /**
       * @private
       * @param {Event} [e] Event object fired on Event.js drag
       * @param {Event} [self] Inner Event object
       */
      _onDrag: function (e, self) {
        this.__onDrag && this.__onDrag(e, self);
      },

      /**
       * @private
       * @param {Event} [e] Event object fired on Event.js wheel event
       * @param {Event} [self] Inner Event object
       */
      _onMouseWheel: function (e, self) {
        this.__onMouseWheel && this.__onMouseWheel(e, self);
      },

      /**
       * @private
       * @param {Event} [e] Event object fired on Event.js orientation change
       * @param {Event} [self] Inner Event object
       */
      _onOrientationChange: function (e, self) {
        this.__onOrientationChange && this.__onOrientationChange(e, self);
      },

      /**
       * @private
       * @param {Event} [e] Event object fired on Event.js shake
       * @param {Event} [self] Inner Event object
       */
      _onShake: function (e, self) {
        this.__onShake && this.__onShake(e, self);
      },

      /**
       * @private
       * @param {Event} e Event object fired on mousedown
       */
      _onMouseDown: function (e) {
        this.__onMouseDown(e);

        addListener(fabric.document, "touchend", this._onMouseUp);
        addListener(fabric.document, "touchmove", this._onMouseMove);

        removeListener(this.upperCanvasEl, "mousemove", this._onMouseMove);
        removeListener(this.upperCanvasEl, "touchmove", this._onMouseMove);

        if (e.type === "touchstart") {
          // Unbind mousedown to prevent double triggers from touch devices
          removeListener(this.upperCanvasEl, "mousedown", this._onMouseDown);
        } else {
          addListener(fabric.document, "mouseup", this._onMouseUp);
          addListener(fabric.document, "mousemove", this._onMouseMove);
        }
      },

      /**
       * @private
       * @param {Event} e Event object fired on mouseup
       */
      _onMouseUp: function (e) {
        this.__onMouseUp(e);

        removeListener(fabric.document, "mouseup", this._onMouseUp);
        removeListener(fabric.document, "touchend", this._onMouseUp);

        removeListener(fabric.document, "mousemove", this._onMouseMove);
        removeListener(fabric.document, "touchmove", this._onMouseMove);

        addListener(this.upperCanvasEl, "mousemove", this._onMouseMove);
        addListener(this.upperCanvasEl, "touchmove", this._onMouseMove);

        if (e.type === "touchend") {
          // Wait 400ms before rebinding mousedown to prevent double triggers
          // from touch devices
          var _this = this;
          setTimeout(function () {
            addListener(_this.upperCanvasEl, "mousedown", _this._onMouseDown);
          }, 400);
        }
      },

      /**
       * @private
       * @param {Event} e Event object fired on mousemove
       */
      _onMouseMove: function (e) {
        !this.allowTouchScrolling && e.preventDefault && e.preventDefault();
        this.__onMouseMove(e);
      },

      /**
       * @private
       */
      _onResize: function () {
        this.calcOffset();
      },

      /**
       * Decides whether the canvas should be redrawn in mouseup and mousedown events.
       * @private
       * @param {Object} target
       * @param {Object} pointer
       */
      _shouldRender: function (target, pointer) {
        var activeObject = this.getActiveGroup() || this.getActiveObject();

        return !!(
          (target && (target.isMoving || target !== activeObject)) ||
          (!target && !!activeObject) ||
          (!target && !activeObject && !this._groupSelector) ||
          (pointer &&
            this._previousPointer &&
            this.selection &&
            (pointer.x !== this._previousPointer.x ||
              pointer.y !== this._previousPointer.y))
        );
      },

      /**
       * Method that defines the actions when mouse is released on canvas.
       * The method resets the currentTransform parameters, store the image corner
       * position in the image object and render the canvas on top.
       * @private
       * @param {Event} e Event object fired on mouseup
       */
      __onMouseUp: function (e) {
        var target;

        if (this.isDrawingMode && this._isCurrentlyDrawing) {
          this._onMouseUpInDrawingMode(e);
          return;
        }

        if (this._currentTransform) {
          this._finalizeCurrentTransform();
          target = this._currentTransform.target;
        } else {
          target = this.findTarget(e, true);
        }

        var shouldRender = this._shouldRender(target, this.getPointer(e));

        this._maybeGroupObjects(e);

        if (target) {
          target.isMoving = false;
        }

        shouldRender && this.renderAll();

        this._handleCursorAndEvent(e, target);
      },

      _handleCursorAndEvent: function (e, target) {
        this._setCursorFromEvent(e, target);

        // TODO: why are we doing this?
        var _this = this;
        setTimeout(function () {
          _this._setCursorFromEvent(e, target);
        }, 50);

        this.fire("mouse:up", { target: target, e: e });
        target && target.fire("mouseup", { e: e });
      },

      /**
       * @private
       */
      _finalizeCurrentTransform: function () {
        var transform = this._currentTransform,
          target = transform.target;

        if (target._scaling) {
          target._scaling = false;
        }

        target.setCoords();

        // only fire :modified event if target coordinates were changed during mousedown-mouseup
        if (this.stateful && target.hasStateChanged()) {
          this.fire("object:modified", { target: target });
          target.fire("modified");
        }

        this._restoreOriginXY(target);
      },

      /**
       * @private
       * @param {Object} target Object to restore
       */
      _restoreOriginXY: function (target) {
        if (this._previousOriginX && this._previousOriginY) {
          var originPoint = target.translateToOriginPoint(
            target.getCenterPoint(),
            this._previousOriginX,
            this._previousOriginY
          );

          target.originX = this._previousOriginX;
          target.originY = this._previousOriginY;

          target.left = originPoint.x;
          target.top = originPoint.y;

          this._previousOriginX = null;
          this._previousOriginY = null;
        }
      },

      /**
       * @private
       * @param {Event} e Event object fired on mousedown
       */
      _onMouseDownInDrawingMode: function (e) {
        this._isCurrentlyDrawing = true;
        this.discardActiveObject(e).renderAll();
        if (this.clipTo) {
          fabric.util.clipContext(this, this.contextTop);
        }
        var ivt = fabric.util.invertTransform(this.viewportTransform),
          pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt);
        this.freeDrawingBrush.onMouseDown(pointer);
        this.fire("mouse:down", { e: e });
      },

      /**
       * @private
       * @param {Event} e Event object fired on mousemove
       */
      _onMouseMoveInDrawingMode: function (e) {
        if (this._isCurrentlyDrawing) {
          var ivt = fabric.util.invertTransform(this.viewportTransform),
            pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt);
          this.freeDrawingBrush.onMouseMove(pointer);
        }
        this.setCursor(this.freeDrawingCursor);
        this.fire("mouse:move", { e: e });
      },

      /**
       * @private
       * @param {Event} e Event object fired on mouseup
       */
      _onMouseUpInDrawingMode: function (e) {
        this._isCurrentlyDrawing = false;
        if (this.clipTo) {
          this.contextTop.restore();
        }
        this.freeDrawingBrush.onMouseUp();
        this.fire("mouse:up", { e: e });
      },

      /**
       * Method that defines the actions when mouse is clic ked on canvas.
       * The method inits the currentTransform parameters and renders all the
       * canvas so the current image can be placed on the top canvas and the rest
       * in on the container one.
       * @private
       * @param {Event} e Event object fired on mousedown
       */
      __onMouseDown: function (e) {
        // accept only left clicks
        var isLeftClick = "which" in e ? e.which === 1 : e.button === 1;
        if (!isLeftClick && !fabric.isTouchSupported) return;

        if (this.isDrawingMode) {
          this._onMouseDownInDrawingMode(e);
          return;
        }

        // ignore if some object is being transformed at this moment
        if (this._currentTransform) return;

        var target = this.findTarget(e),
          pointer = this.getPointer(e, true);

        // save pointer for check in __onMouseUp event
        this._previousPointer = pointer;

        var shouldRender = this._shouldRender(target, pointer),
          shouldGroup = this._shouldGroup(e, target);

        if (this._shouldClearSelection(e, target)) {
          this._clearSelection(e, target, pointer);
        } else if (shouldGroup) {
          this._handleGrouping(e, target);
          target = this.getActiveGroup();
        }

        if (target && target.selectable && !shouldGroup) {
          this._beforeTransform(e, target);
          this._setupCurrentTransform(e, target);
        }
        // we must renderAll so that active image is placed on the top canvas
        shouldRender && this.renderAll();

        this.fire("mouse:down", { target: target, e: e });
        target && target.fire("mousedown", { e: e });
      },

      /**
       * @private
       */
      _beforeTransform: function (e, target) {
        var corner;

        this.stateful && target.saveState();

        // determine if it's a drag or rotate case
        if ((corner = target._findTargetCorner(this.getPointer(e)))) {
          this.onBeforeScaleRotate(target);
        }

        if (
          target !== this.getActiveGroup() &&
          target !== this.getActiveObject()
        ) {
          this.deactivateAll();
          this.setActiveObject(target, e);
        }
      },

      /**
       * @private
       */
      _clearSelection: function (e, target, pointer) {
        this.deactivateAllWithDispatch(e);

        if (target && target.selectable) {
          this.setActiveObject(target, e);
        } else if (this.selection) {
          this._groupSelector = {
            ex: pointer.x,
            ey: pointer.y,
            top: 0,
            left: 0
          };
        }
      },

      /**
       * @private
       * @param {Object} target Object for that origin is set to center
       */
      _setOriginToCenter: function (target) {
        this._previousOriginX = this._currentTransform.target.originX;
        this._previousOriginY = this._currentTransform.target.originY;

        var center = target.getCenterPoint();

        target.originX = "center";
        target.originY = "center";

        target.left = center.x;
        target.top = center.y;

        this._currentTransform.left = target.left;
        this._currentTransform.top = target.top;
      },

      /**
       * @private
       * @param {Object} target Object for that center is set to origin
       */
      _setCenterToOrigin: function (target) {
        var originPoint = target.translateToOriginPoint(
          target.getCenterPoint(),
          this._previousOriginX,
          this._previousOriginY
        );

        target.originX = this._previousOriginX;
        target.originY = this._previousOriginY;

        target.left = originPoint.x;
        target.top = originPoint.y;

        this._previousOriginX = null;
        this._previousOriginY = null;
      },

      /**
       * Method that defines the actions when mouse is hovering the canvas.
       * The currentTransform parameter will definde whether the user is rotating/scaling/translating
       * an image or neither of them (only hovering). A group selection is also possible and would cancel
       * all any other type of action.
       * In case of an image transformation only the top canvas will be rendered.
       * @private
       * @param {Event} e Event object fired on mousemove
       */
      __onMouseMove: function (e) {
        var target, pointer;

        if (this.isDrawingMode) {
          this._onMouseMoveInDrawingMode(e);
          return;
        }

        var groupSelector = this._groupSelector;

        // We initially clicked in an empty area, so we draw a box for multiple selection
        if (groupSelector) {
          pointer = this.getPointer(e, true);

          groupSelector.left = pointer.x - groupSelector.ex;
          groupSelector.top = pointer.y - groupSelector.ey;

          this.renderTop();
        } else if (!this._currentTransform) {
          target = this.findTarget(e);

          if (!target || (target && !target.selectable)) {
            this.setCursor(this.defaultCursor);
          } else {
            this._setCursorFromEvent(e, target);
          }
        } else {
          this._transformObject(e);
        }

        this.fire("mouse:move", { target: target, e: e });
        target && target.fire("mousemove", { e: e });
      },

      /**
       * @private
       * @param {Event} e Event fired on mousemove
       */
      _transformObject: function (e) {
        var pointer = this.getPointer(e),
          transform = this._currentTransform;

        (transform.reset = false), (transform.target.isMoving = true);

        this._beforeScaleTransform(e, transform);
        this._performTransformAction(e, transform, pointer);

        this.renderAll();
      },

      /**
       * @private
       */
      _performTransformAction: function (e, transform, pointer) {
        var x = pointer.x,
          y = pointer.y,
          target = transform.target,
          action = transform.action;

        if (action === "rotate") {
          this._rotateObject(x, y);
          this._fire("rotating", target, e);
        } else if (action === "scale") {
          this._onScale(e, transform, x, y);
          this._fire("scaling", target, e);
        } else if (action === "scaleX") {
          this._scaleObject(x, y, "x");
          this._fire("scaling", target, e);
        } else if (action === "scaleY") {
          this._scaleObject(x, y, "y");
          this._fire("scaling", target, e);
        } else {
          this._translateObject(x, y);
          this._fire("moving", target, e);
          this.setCursor(this.moveCursor);
        }
      },

      /**
       * @private
       */
      _fire: function (eventName, target, e) {
        this.fire("object:" + eventName, { target: target, e: e });
        target.fire(eventName, { e: e });
      },

      /**
       * @private
       */
      _beforeScaleTransform: function (e, transform) {
        if (
          transform.action === "scale" ||
          transform.action === "scaleX" ||
          transform.action === "scaleY"
        ) {
          var centerTransform = this._shouldCenterTransform(
            e,
            transform.target
          );

          // Switch from a normal resize to center-based
          if (
            (centerTransform &&
              (transform.originX !== "center" ||
                transform.originY !== "center")) ||
            // Switch from center-based resize to normal one
            (!centerTransform &&
              transform.originX === "center" &&
              transform.originY === "center")
          ) {
            this._resetCurrentTransform(e);
            transform.reset = true;
          }
        }
      },

      /**
       * @private
       */
      _onScale: function (e, transform, x, y) {
        // rotate object only if shift key is not pressed
        // and if it is not a group we are transforming
        if (
          (e.shiftKey || this.uniScaleTransform) &&
          !transform.target.get("lockUniScaling")
        ) {
          transform.currentAction = "scale";
          this._scaleObject(x, y);
        } else {
          // Switch from a normal resize to proportional
          if (!transform.reset && transform.currentAction === "scale") {
            this._resetCurrentTransform(e, transform.target);
          }

          transform.currentAction = "scaleEqually";
          this._scaleObject(x, y, "equally");
        }
      },

      /**
       * Sets the cursor depending on where the canvas is being hovered.
       * Note: very buggy in Opera
       * @param {Event} e Event object
       * @param {Object} target Object that the mouse is hovering, if so.
       */
      _setCursorFromEvent: function (e, target) {
        if (!target || !target.selectable) {
          this.setCursor(this.defaultCursor);
          return false;
        } else {
          var activeGroup = this.getActiveGroup(),
            // only show proper corner when group selection is not active
            corner =
              target._findTargetCorner &&
              (!activeGroup || !activeGroup.contains(target)) &&
              target._findTargetCorner(this.getPointer(e, true));

          if (!corner) {
            this.setCursor(target.hoverCursor || this.hoverCursor);
          } else {
            this._setCornerCursor(corner, target);
          }
        }
        return true;
      },

      /**
       * @private
       */
      _setCornerCursor: function (corner, target) {
        if (corner in cursorOffset) {
          this.setCursor(this._getRotatedCornerCursor(corner, target));
        } else if (corner === "mtr" && target.hasRotatingPoint) {
          this.setCursor(this.rotationCursor);
        } else {
          this.setCursor(this.defaultCursor);
          return false;
        }
      },

      /**
       * @private
       */
      _getRotatedCornerCursor: function (corner, target) {
        var n = Math.round((target.getAngle() % 360) / 45);

        if (n < 0) {
          n += 8; // full circle ahead
        }
        n += cursorOffset[corner];
        // normalize n to be from 0 to 7
        n %= 8;

        return this.cursorMap[n];
      }
    }
  );
})();

(function () {
  var min = Math.min,
    max = Math.max;

  fabric.util.object.extend(
    fabric.Canvas.prototype,
    /** @lends fabric.Canvas.prototype */ {
      /**
       * @private
       * @param {Event} e Event object
       * @param {fabric.Object} target
       * @return {Boolean}
       */
      _shouldGroup: function (e, target) {
        var activeObject = this.getActiveObject();
        return (
          e.shiftKey &&
          (this.getActiveGroup() ||
            (activeObject && activeObject !== target)) &&
          this.selection
        );
      },

      /**
       * @private
       * @param {Event} e Event object
       * @param {fabric.Object} target
       */
      _handleGrouping: function (e, target) {
        if (target === this.getActiveGroup()) {
          // if it's a group, find target again, this time skipping group
          target = this.findTarget(e, true);

          // if even object is not found, bail out
          if (!target || target.isType("group")) {
            return;
          }
        }
        if (this.getActiveGroup()) {
          this._updateActiveGroup(target, e);
        } else {
          this._createActiveGroup(target, e);
        }

        if (this._activeGroup) {
          this._activeGroup.saveCoords();
        }
      },

      /**
       * @private
       */
      _updateActiveGroup: function (target, e) {
        var activeGroup = this.getActiveGroup();

        if (activeGroup.contains(target)) {
          activeGroup.removeWithUpdate(target);
          this._resetObjectTransform(activeGroup);
          target.set("active", false);

          if (activeGroup.size() === 1) {
            // remove group alltogether if after removal it only contains 1 object
            this.discardActiveGroup(e);
            // activate last remaining object
            this.setActiveObject(activeGroup.item(0));
            return;
          }
        } else {
          activeGroup.addWithUpdate(target);
          this._resetObjectTransform(activeGroup);
        }
        this.fire("selection:created", { target: activeGroup, e: e });
        activeGroup.set("active", true);
      },

      /**
       * @private
       */
      _createActiveGroup: function (target, e) {
        if (this._activeObject && target !== this._activeObject) {
          var group = this._createGroup(target);
          group.addWithUpdate();

          this.setActiveGroup(group);
          this._activeObject = null;

          this.fire("selection:created", { target: group, e: e });
        }

        target.set("active", true);
      },

      /**
       * @private
       * @param {Object} target
       */
      _createGroup: function (target) {
        var objects = this.getObjects(),
          isActiveLower =
            objects.indexOf(this._activeObject) < objects.indexOf(target),
          groupObjects = isActiveLower
            ? [this._activeObject, target]
            : [target, this._activeObject];

        return new fabric.Group(groupObjects, {
          originX: "center",
          originY: "center",
          canvas: this
        });
      },

      /**
       * @private
       * @param {Event} e mouse event
       */
      _groupSelectedObjects: function (e) {
        var group = this._collectObjects();

        // do not create group for 1 element only
        if (group.length === 1) {
          this.setActiveObject(group[0], e);
        } else if (group.length > 1) {
          group = new fabric.Group(group.reverse(), {
            originX: "center",
            originY: "center",
            canvas: this
          });
          group.addWithUpdate();
          this.setActiveGroup(group, e);
          group.saveCoords();
          this.fire("selection:created", { target: group });
          this.renderAll();
        }
      },

      /**
       * @private
       */
      _collectObjects: function () {
        var group = [],
          currentObject,
          x1 = this._groupSelector.ex,
          y1 = this._groupSelector.ey,
          x2 = x1 + this._groupSelector.left,
          y2 = y1 + this._groupSelector.top,
          selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
          selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)),
          isClick = x1 === x2 && y1 === y2;

        for (var i = this._objects.length; i--; ) {
          currentObject = this._objects[i];

          if (
            !currentObject ||
            !currentObject.selectable ||
            !currentObject.visible
          )
            continue;

          if (
            currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) ||
            currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) ||
            currentObject.containsPoint(selectionX1Y1) ||
            currentObject.containsPoint(selectionX2Y2)
          ) {
            currentObject.set("active", true);
            group.push(currentObject);

            // only add one object if it's a click
            if (isClick) break;
          }
        }

        return group;
      },

      /**
       * @private
       */
      _maybeGroupObjects: function (e) {
        if (this.selection && this._groupSelector) {
          this._groupSelectedObjects(e);
        }

        var activeGroup = this.getActiveGroup();
        if (activeGroup) {
          activeGroup.setObjectsCoords().setCoords();
          activeGroup.isMoving = false;
          this.setCursor(this.defaultCursor);
        }

        // clear selection and current transformation
        this._groupSelector = null;
        this._currentTransform = null;
      }
    }
  );
})();

fabric.util.object.extend(
  fabric.StaticCanvas.prototype,
  /** @lends fabric.StaticCanvas.prototype */ {
    /**
     * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately
     * @param {Object} [options] Options object
     * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
     * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
     * @param {Number} [options.multiplier=1] Multiplier to scale by
     * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
     * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
     * @param {Number} [options.width] Cropping width. Introduced in v1.2.14
     * @param {Number} [options.height] Cropping height. Introduced in v1.2.14
     * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
     * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo}
     * @example <caption>Generate jpeg dataURL with lower quality</caption>
     * var dataURL = canvas.toDataURL({
     *   format: 'jpeg',
     *   quality: 0.8
     * });
     * @example <caption>Generate cropped png dataURL (clipping of canvas)</caption>
     * var dataURL = canvas.toDataURL({
     *   format: 'png',
     *   left: 100,
     *   top: 100,
     *   width: 200,
     *   height: 200
     * });
     * @example <caption>Generate double scaled png dataURL</caption>
     * var dataURL = canvas.toDataURL({
     *   format: 'png',
     *   multiplier: 2
     * });
     */
    toDataURL: function (options) {
      options || (options = {});

      var format = options.format || "png",
        quality = options.quality || 1,
        multiplier = options.multiplier || 1,
        cropping = {
          left: options.left,
          top: options.top,
          width: options.width,
          height: options.height
        };

      if (multiplier !== 1) {
        return this.__toDataURLWithMultiplier(
          format,
          quality,
          cropping,
          multiplier
        );
      } else {
        return this.__toDataURL(format, quality, cropping);
      }
    },

    /**
     * @private
     */
    __toDataURL: function (format, quality, cropping) {
      this.renderAll(true);

      var canvasEl = this.upperCanvasEl || this.lowerCanvasEl,
        croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping);

      // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
      if (format === "jpg") {
        format = "jpeg";
      }

      var data = fabric.StaticCanvas.supports("toDataURLWithQuality")
        ? (croppedCanvasEl || canvasEl).toDataURL("image/" + format, quality)
        : (croppedCanvasEl || canvasEl).toDataURL("image/" + format);

      this.contextTop && this.clearContext(this.contextTop);
      this.renderAll();

      if (croppedCanvasEl) {
        croppedCanvasEl = null;
      }

      return data;
    },

    /**
     * @private
     */
    __getCroppedCanvas: function (canvasEl, cropping) {
      var croppedCanvasEl,
        croppedCtx,
        shouldCrop =
          "left" in cropping ||
          "top" in cropping ||
          "width" in cropping ||
          "height" in cropping;

      if (shouldCrop) {
        croppedCanvasEl = fabric.util.createCanvasElement();
        croppedCtx = croppedCanvasEl.getContext("2d");

        croppedCanvasEl.width = cropping.width || this.width;
        croppedCanvasEl.height = cropping.height || this.height;

        croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0);
      }

      return croppedCanvasEl;
    },

    /**
     * @private
     */
    __toDataURLWithMultiplier: function (
      format,
      quality,
      cropping,
      multiplier
    ) {
      var origWidth = this.getWidth(),
        origHeight = this.getHeight(),
        scaledWidth = origWidth * multiplier,
        scaledHeight = origHeight * multiplier,
        activeObject = this.getActiveObject(),
        activeGroup = this.getActiveGroup(),
        ctx = this.contextTop || this.contextContainer;

      if (multiplier > 1) {
        this.setWidth(scaledWidth).setHeight(scaledHeight);
      }
      ctx.scale(multiplier, multiplier);

      if (cropping.left) {
        cropping.left *= multiplier;
      }
      if (cropping.top) {
        cropping.top *= multiplier;
      }
      if (cropping.width) {
        cropping.width *= multiplier;
      } else if (multiplier < 1) {
        cropping.width = scaledWidth;
      }
      if (cropping.height) {
        cropping.height *= multiplier;
      } else if (multiplier < 1) {
        cropping.height = scaledHeight;
      }

      if (activeGroup) {
        // not removing group due to complications with restoring it with correct state afterwords
        this._tempRemoveBordersControlsFromGroup(activeGroup);
      } else if (activeObject && this.deactivateAll) {
        this.deactivateAll();
      }

      this.renderAll(true);

      var data = this.__toDataURL(format, quality, cropping);

      // restoring width, height for `renderAll` to draw
      // background properly (while context is scaled)
      this.width = origWidth;
      this.height = origHeight;

      ctx.scale(1 / multiplier, 1 / multiplier);
      this.setWidth(origWidth).setHeight(origHeight);

      if (activeGroup) {
        this._restoreBordersControlsOnGroup(activeGroup);
      } else if (activeObject && this.setActiveObject) {
        this.setActiveObject(activeObject);
      }

      this.contextTop && this.clearContext(this.contextTop);
      this.renderAll();

      return data;
    },

    /**
     * Exports canvas element to a dataurl image (allowing to change image size via multiplier).
     * @deprecated since 1.0.13
     * @param {String} format (png|jpeg)
     * @param {Number} multiplier
     * @param {Number} quality (0..1)
     * @return {String}
     */
    toDataURLWithMultiplier: function (format, multiplier, quality) {
      return this.toDataURL({
        format: format,
        multiplier: multiplier,
        quality: quality
      });
    },

    /**
     * @private
     */
    _tempRemoveBordersControlsFromGroup: function (group) {
      group.origHasControls = group.hasControls;
      group.origBorderColor = group.borderColor;

      group.hasControls = true;
      group.borderColor = "rgba(0,0,0,0)";

      group.forEachObject(function (o) {
        o.origBorderColor = o.borderColor;
        o.borderColor = "rgba(0,0,0,0)";
      });
    },

    /**
     * @private
     */
    _restoreBordersControlsOnGroup: function (group) {
      group.hideControls = group.origHideControls;
      group.borderColor = group.origBorderColor;

      group.forEachObject(function (o) {
        o.borderColor = o.origBorderColor;
        delete o.origBorderColor;
      });
    }
  }
);

fabric.util.object.extend(
  fabric.StaticCanvas.prototype,
  /** @lends fabric.StaticCanvas.prototype */ {
    /**
     * Populates canvas with data from the specified dataless JSON.
     * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON}
     * @deprecated since 1.2.2
     * @param {String|Object} json JSON string or object
     * @param {Function} callback Callback, invoked when json is parsed
     *                            and corresponding objects (e.g: {@link fabric.Image})
     *                            are initialized
     * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.
     * @return {fabric.Canvas} instance
     * @chainable
     * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization}
     */
    loadFromDatalessJSON: function (json, callback, reviver) {
      return this.loadFromJSON(json, callback, reviver);
    },

    /**
     * Populates canvas with data from the specified JSON.
     * JSON format must conform to the one of {@link fabric.Canvas#toJSON}
     * @param {String|Object} json JSON string or object
     * @param {Function} callback Callback, invoked when json is parsed
     *                            and corresponding objects (e.g: {@link fabric.Image})
     *                            are initialized
     * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.
     * @return {fabric.Canvas} instance
     * @chainable
     * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization}
     * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo}
     * @example <caption>loadFromJSON</caption>
     * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas));
     * @example <caption>loadFromJSON with reviver</caption>
     * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) {
     *   // `o` = json object
     *   // `object` = fabric.Object instance
     *   // ... do some stuff ...
     * });
     */
    loadFromJSON: function (json, callback, reviver) {
      if (!json) return;

      // serialize if it wasn't already
      var serialized = typeof json === "string" ? JSON.parse(json) : json;

      this.clear();

      var _this = this;
      this._enlivenObjects(
        serialized.objects,
        function () {
          _this._setBgOverlay(serialized, callback);
        },
        reviver
      );

      return this;
    },

    /**
     * @private
     * @param {Object} serialized Object with background and overlay information
     * @param {Function} callback Invoked after all background and overlay images/patterns loaded
     */
    _setBgOverlay: function (serialized, callback) {
      var _this = this,
        loaded = {
          backgroundColor: false,
          overlayColor: false,
          backgroundImage: false,
          overlayImage: false
        };

      if (
        !serialized.backgroundImage &&
        !serialized.overlayImage &&
        !serialized.background &&
        !serialized.overlay
      ) {
        callback && callback();
        return;
      }

      var cbIfLoaded = function () {
        if (
          loaded.backgroundImage &&
          loaded.overlayImage &&
          loaded.backgroundColor &&
          loaded.overlayColor
        ) {
          _this.renderAll();
          callback && callback();
        }
      };

      this.__setBgOverlay(
        "backgroundImage",
        serialized.backgroundImage,
        loaded,
        cbIfLoaded
      );
      this.__setBgOverlay(
        "overlayImage",
        serialized.overlayImage,
        loaded,
        cbIfLoaded
      );
      this.__setBgOverlay(
        "backgroundColor",
        serialized.background,
        loaded,
        cbIfLoaded
      );
      this.__setBgOverlay(
        "overlayColor",
        serialized.overlay,
        loaded,
        cbIfLoaded
      );

      cbIfLoaded();
    },

    /**
     * @private
     * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor)
     * @param {(Object|String)} value Value to set
     * @param {Object} loaded Set loaded property to true if property is set
     * @param {Object} callback Callback function to invoke after property is set
     */
    __setBgOverlay: function (property, value, loaded, callback) {
      var _this = this;

      if (!value) {
        loaded[property] = true;
        return;
      }

      if (property === "backgroundImage" || property === "overlayImage") {
        fabric.Image.fromObject(value, function (img) {
          _this[property] = img;
          loaded[property] = true;
          callback && callback();
        });
      } else {
        this["set" + fabric.util.string.capitalize(property, true)](
          value,
          function () {
            loaded[property] = true;
            callback && callback();
          }
        );
      }
    },

    /**
     * @private
     * @param {Array} objects
     * @param {Function} callback
     * @param {Function} [reviver]
     */
    _enlivenObjects: function (objects, callback, reviver) {
      var _this = this;

      if (!objects || objects.length === 0) {
        callback && callback();
        return;
      }

      var renderOnAddRemove = this.renderOnAddRemove;
      this.renderOnAddRemove = false;

      fabric.util.enlivenObjects(
        objects,
        function (enlivenedObjects) {
          enlivenedObjects.forEach(function (obj, index) {
            _this.insertAt(obj, index, true);
          });

          _this.renderOnAddRemove = renderOnAddRemove;
          callback && callback();
        },
        null,
        reviver
      );
    },

    /**
     * @private
     * @param {String} format
     * @param {Function} callback
     */
    _toDataURL: function (format, callback) {
      this.clone(function (clone) {
        callback(clone.toDataURL(format));
      });
    },

    /**
     * @private
     * @param {String} format
     * @param {Number} multiplier
     * @param {Function} callback
     */
    _toDataURLWithMultiplier: function (format, multiplier, callback) {
      this.clone(function (clone) {
        callback(clone.toDataURLWithMultiplier(format, multiplier));
      });
    },

    /**
     * Clones canvas instance
     * @param {Object} [callback] Receives cloned instance as a first argument
     * @param {Array} [properties] Array of properties to include in the cloned canvas and children
     */
    clone: function (callback, properties) {
      var data = JSON.stringify(this.toJSON(properties));
      this.cloneWithoutData(function (clone) {
        clone.loadFromJSON(data, function () {
          callback && callback(clone);
        });
      });
    },

    /**
     * Clones canvas instance without cloning existing data.
     * This essentially copies canvas dimensions, clipping properties, etc.
     * but leaves data empty (so that you can populate it with your own)
     * @param {Object} [callback] Receives cloned instance as a first argument
     */
    cloneWithoutData: function (callback) {
      var el = fabric.document.createElement("canvas");

      el.width = this.getWidth();
      el.height = this.getHeight();

      var clone = new fabric.Canvas(el);
      clone.clipTo = this.clipTo;
      if (this.backgroundImage) {
        clone.setBackgroundImage(this.backgroundImage.src, function () {
          clone.renderAll();
          callback && callback(clone);
        });
        clone.backgroundImageOpacity = this.backgroundImageOpacity;
        clone.backgroundImageStretch = this.backgroundImageStretch;
      } else {
        callback && callback(clone);
      }
    }
  }
);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend,
    toFixed = fabric.util.toFixed,
    capitalize = fabric.util.string.capitalize,
    degreesToRadians = fabric.util.degreesToRadians,
    supportsLineDash = fabric.StaticCanvas.supports("setLineDash");

  if (fabric.Object) {
    return;
  }

  /**
   * Root object class from which all 2d shape classes inherit from
   * @class fabric.Object
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects}
   * @see {@link fabric.Object#initialize} for constructor definition
   *
   * @fires added
   * @fires removed
   *
   * @fires selected
   * @fires modified
   * @fires rotating
   * @fires scaling
   * @fires moving
   *
   * @fires mousedown
   * @fires mouseup
   */
  fabric.Object = fabric.util.createClass(
    /** @lends fabric.Object.prototype */ {
      /**
       * Retrieves object's {@link fabric.Object#clipTo|clipping function}
       * @method getClipTo
       * @memberOf fabric.Object.prototype
       * @return {Function}
       */

      /**
       * Sets object's {@link fabric.Object#clipTo|clipping function}
       * @method setClipTo
       * @memberOf fabric.Object.prototype
       * @param {Function} clipTo Clipping function
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix}
       * @method getTransformMatrix
       * @memberOf fabric.Object.prototype
       * @return {Array} transformMatrix
       */

      /**
       * Sets object's {@link fabric.Object#transformMatrix|transformMatrix}
       * @method setTransformMatrix
       * @memberOf fabric.Object.prototype
       * @param {Array} transformMatrix
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#visible|visible} state
       * @method getVisible
       * @memberOf fabric.Object.prototype
       * @return {Boolean} True if visible
       */

      /**
       * Sets object's {@link fabric.Object#visible|visible} state
       * @method setVisible
       * @memberOf fabric.Object.prototype
       * @param {Boolean} value visible value
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#shadow|shadow}
       * @method getShadow
       * @memberOf fabric.Object.prototype
       * @return {Object} Shadow instance
       */

      /**
       * Retrieves object's {@link fabric.Object#stroke|stroke}
       * @method getStroke
       * @memberOf fabric.Object.prototype
       * @return {String} stroke value
       */

      /**
       * Sets object's {@link fabric.Object#stroke|stroke}
       * @method setStroke
       * @memberOf fabric.Object.prototype
       * @param {String} value stroke value
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth}
       * @method getStrokeWidth
       * @memberOf fabric.Object.prototype
       * @return {Number} strokeWidth value
       */

      /**
       * Sets object's {@link fabric.Object#strokeWidth|strokeWidth}
       * @method setStrokeWidth
       * @memberOf fabric.Object.prototype
       * @param {Number} value strokeWidth value
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#originX|originX}
       * @method getOriginX
       * @memberOf fabric.Object.prototype
       * @return {String} originX value
       */

      /**
       * Sets object's {@link fabric.Object#originX|originX}
       * @method setOriginX
       * @memberOf fabric.Object.prototype
       * @param {String} value originX value
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#originY|originY}
       * @method getOriginY
       * @memberOf fabric.Object.prototype
       * @return {String} originY value
       */

      /**
       * Sets object's {@link fabric.Object#originY|originY}
       * @method setOriginY
       * @memberOf fabric.Object.prototype
       * @param {String} value originY value
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#fill|fill}
       * @method getFill
       * @memberOf fabric.Object.prototype
       * @return {String} Fill value
       */

      /**
       * Sets object's {@link fabric.Object#fill|fill}
       * @method setFill
       * @memberOf fabric.Object.prototype
       * @param {String} value Fill value
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#opacity|opacity}
       * @method getOpacity
       * @memberOf fabric.Object.prototype
       * @return {Number} Opacity value (0-1)
       */

      /**
       * Sets object's {@link fabric.Object#opacity|opacity}
       * @method setOpacity
       * @memberOf fabric.Object.prototype
       * @param {Number} value Opacity value (0-1)
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#angle|angle} (in degrees)
       * @method getAngle
       * @memberOf fabric.Object.prototype
       * @return {Number}
       */

      /**
       * Sets object's {@link fabric.Object#angle|angle}
       * @method setAngle
       * @memberOf fabric.Object.prototype
       * @param {Number} value Angle value (in degrees)
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#top|top position}
       * @method getTop
       * @memberOf fabric.Object.prototype
       * @return {Number} Top value (in pixels)
       */

      /**
       * Sets object's {@link fabric.Object#top|top position}
       * @method setTop
       * @memberOf fabric.Object.prototype
       * @param {Number} value Top value (in pixels)
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#left|left position}
       * @method getLeft
       * @memberOf fabric.Object.prototype
       * @return {Number} Left value (in pixels)
       */

      /**
       * Sets object's {@link fabric.Object#left|left position}
       * @method setLeft
       * @memberOf fabric.Object.prototype
       * @param {Number} value Left value (in pixels)
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#scaleX|scaleX} value
       * @method getScaleX
       * @memberOf fabric.Object.prototype
       * @return {Number} scaleX value
       */

      /**
       * Sets object's {@link fabric.Object#scaleX|scaleX} value
       * @method setScaleX
       * @memberOf fabric.Object.prototype
       * @param {Number} value scaleX value
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#scaleY|scaleY} value
       * @method getScaleY
       * @memberOf fabric.Object.prototype
       * @return {Number} scaleY value
       */

      /**
       * Sets object's {@link fabric.Object#scaleY|scaleY} value
       * @method setScaleY
       * @memberOf fabric.Object.prototype
       * @param {Number} value scaleY value
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#flipX|flipX} value
       * @method getFlipX
       * @memberOf fabric.Object.prototype
       * @return {Boolean} flipX value
       */

      /**
       * Sets object's {@link fabric.Object#flipX|flipX} value
       * @method setFlipX
       * @memberOf fabric.Object.prototype
       * @param {Boolean} value flipX value
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Retrieves object's {@link fabric.Object#flipY|flipY} value
       * @method getFlipY
       * @memberOf fabric.Object.prototype
       * @return {Boolean} flipY value
       */

      /**
       * Sets object's {@link fabric.Object#flipY|flipY} value
       * @method setFlipY
       * @memberOf fabric.Object.prototype
       * @param {Boolean} value flipY value
       * @return {fabric.Object} thisArg
       * @chainable
       */

      /**
       * Type of an object (rect, circle, path, etc.)
       * @type String
       * @default
       */
      type: "object",

      /**
       * Horizontal origin of transformation of an object (one of "left", "right", "center")
       * @type String
       * @default
       */
      originX: "left",

      /**
       * Vertical origin of transformation of an object (one of "top", "bottom", "center")
       * @type String
       * @default
       */
      originY: "top",

      /**
       * Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom}
       * @type Number
       * @default
       */
      top: 0,

      /**
       * Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right}
       * @type Number
       * @default
       */
      left: 0,

      /**
       * Object width
       * @type Number
       * @default
       */
      width: 0,

      /**
       * Object height
       * @type Number
       * @default
       */
      height: 0,

      /**
       * Object scale factor (horizontal)
       * @type Number
       * @default
       */
      scaleX: 1,

      /**
       * Object scale factor (vertical)
       * @type Number
       * @default
       */
      scaleY: 1,

      /**
       * When true, an object is rendered as flipped horizontally
       * @type Boolean
       * @default
       */
      flipX: false,

      /**
       * When true, an object is rendered as flipped vertically
       * @type Boolean
       * @default
       */
      flipY: false,

      /**
       * Opacity of an object
       * @type Number
       * @default
       */
      opacity: 1,

      /**
       * Angle of rotation of an object (in degrees)
       * @type Number
       * @default
       */
      angle: 0,

      /**
       * Size of object's controlling corners (in pixels)
       * @type Number
       * @default
       */
      cornerSize: 12,

      /**
       * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill)
       * @type Boolean
       * @default
       */
      transparentCorners: true,

      /**
       * Default cursor value used when hovering over this object on canvas
       * @type String
       * @default
       */
      hoverCursor: null,

      /**
       * Padding between object and its controlling borders (in pixels)
       * @type Number
       * @default
       */
      padding: 0,

      /**
       * Color of controlling borders of an object (when it's active)
       * @type String
       * @default
       */
      borderColor: "rgba(102,153,255,0.75)",

      /**
       * Color of controlling corners of an object (when it's active)
       * @type String
       * @default
       */
      cornerColor: "rgba(102,153,255,0.5)",

      /**
       * When true, this object will use center point as the origin of transformation
       * when being scaled via the controls.
       * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
       * @since 1.3.4
       * @type Boolean
       * @default
       */
      centeredScaling: false,

      /**
       * When true, this object will use center point as the origin of transformation
       * when being rotated via the controls.
       * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
       * @since 1.3.4
       * @type Boolean
       * @default
       */
      centeredRotation: true,

      /**
       * Color of object's fill
       * @type String
       * @default
       */
      fill: "rgb(0,0,0)",

      /**
       * Fill rule used to fill an object
       * @type String
       * @default
       */
      fillRule: "source-over",

      /**
       * Background color of an object. Only works with text objects at the moment.
       * @type String
       * @default
       */
      backgroundColor: "",

      /**
       * When defined, an object is rendered via stroke and this property specifies its color
       * @type String
       * @default
       */
      stroke: null,

      /**
       * Width of a stroke used to render this object
       * @type Number
       * @default
       */
      strokeWidth: 1,

      /**
       * Array specifying dash pattern of an object's stroke (stroke must be defined)
       * @type Array
       */
      strokeDashArray: null,

      /**
       * Line endings style of an object's stroke (one of "butt", "round", "square")
       * @type String
       * @default
       */
      strokeLineCap: "butt",

      /**
       * Corner style of an object's stroke (one of "bevil", "round", "miter")
       * @type String
       * @default
       */
      strokeLineJoin: "miter",

      /**
       * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke
       * @type Number
       * @default
       */
      strokeMiterLimit: 10,

      /**
       * Shadow object representing shadow of this shape
       * @type fabric.Shadow
       * @default
       */
      shadow: null,

      /**
       * Opacity of object's controlling borders when object is active and moving
       * @type Number
       * @default
       */
      borderOpacityWhenMoving: 0.4,

      /**
       * Scale factor of object's controlling borders
       * @type Number
       * @default
       */
      borderScaleFactor: 1,

      /**
       * Transform matrix (similar to SVG's transform matrix)
       * @type Array
       */
      transformMatrix: null,

      /**
       * Minimum allowed scale value of an object
       * @type Number
       * @default
       */
      minScaleLimit: 0.01,

      /**
       * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection).
       * But events still fire on it.
       * @type Boolean
       * @default
       */
      selectable: true,

      /**
       * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4
       * @type Boolean
       * @default
       */
      evented: true,

      /**
       * When set to `false`, an object is not rendered on canvas
       * @type Boolean
       * @default
       */
      visible: true,

      /**
       * When set to `false`, object's controls are not displayed and can not be used to manipulate object
       * @type Boolean
       * @default
       */
      hasControls: true,

      /**
       * When set to `false`, object's controlling borders are not rendered
       * @type Boolean
       * @default
       */
      hasBorders: true,

      /**
       * When set to `false`, object's controlling rotating point will not be visible or selectable
       * @type Boolean
       * @default
       */
      hasRotatingPoint: true,

      /**
       * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`)
       * @type Number
       * @default
       */
      rotatingPointOffset: 40,

      /**
       * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box
       * @type Boolean
       * @default
       */
      perPixelTargetFind: false,

      /**
       * When `false`, default object's values are not included in its serialization
       * @type Boolean
       * @default
       */
      includeDefaultValues: true,

      /**
       * Function that determines clipping of an object (context is passed as a first argument)
       * Note that context origin is at the object's center point (not left/top corner)
       * @type Function
       */
      clipTo: null,

      /**
       * When `true`, object horizontal movement is locked
       * @type Boolean
       * @default
       */
      lockMovementX: false,

      /**
       * When `true`, object vertical movement is locked
       * @type Boolean
       * @default
       */
      lockMovementY: false,

      /**
       * When `true`, object rotation is locked
       * @type Boolean
       * @default
       */
      lockRotation: false,

      /**
       * When `true`, object horizontal scaling is locked
       * @type Boolean
       * @default
       */
      lockScalingX: false,

      /**
       * When `true`, object vertical scaling is locked
       * @type Boolean
       * @default
       */
      lockScalingY: false,

      /**
       * When `true`, object non-uniform scaling is locked
       * @type Boolean
       * @default
       */
      lockUniScaling: false,

      /**
       * List of properties to consider when checking if state
       * of an object is changed (fabric.Object#hasStateChanged)
       * as well as for history (undo/redo) purposes
       * @type Array
       */
      stateProperties: (
        "top left width height scaleX scaleY flipX flipY originX originY transformMatrix " +
        "stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit " +
        "angle opacity fill fillRule shadow clipTo visible backgroundColor"
      ).split(" "),

      /**
       * Constructor
       * @param {Object} [options] Options object
       */
      initialize: function (options) {
        if (options) {
          this.setOptions(options);
        }
      },

      /**
       * @private
       * @param {Object} [options] Options object
       */
      _initGradient: function (options) {
        if (
          options.fill &&
          options.fill.colorStops &&
          !(options.fill instanceof fabric.Gradient)
        ) {
          this.set("fill", new fabric.Gradient(options.fill));
        }
      },

      /**
       * @private
       * @param {Object} [options] Options object
       */
      _initPattern: function (options) {
        if (
          options.fill &&
          options.fill.source &&
          !(options.fill instanceof fabric.Pattern)
        ) {
          this.set("fill", new fabric.Pattern(options.fill));
        }
        if (
          options.stroke &&
          options.stroke.source &&
          !(options.stroke instanceof fabric.Pattern)
        ) {
          this.set("stroke", new fabric.Pattern(options.stroke));
        }
      },

      /**
       * @private
       * @param {Object} [options] Options object
       */
      _initClipping: function (options) {
        if (!options.clipTo || typeof options.clipTo !== "string") return;

        var functionBody = fabric.util.getFunctionBody(options.clipTo);
        if (typeof functionBody !== "undefined") {
          this.clipTo = new Function("ctx", functionBody);
        }
      },

      /**
       * Sets object's properties from options
       * @param {Object} [options] Options object
       */
      setOptions: function (options) {
        for (var prop in options) {
          this.set(prop, options[prop]);
        }
        this._initGradient(options);
        this._initPattern(options);
        this._initClipping(options);
      },

      /**
       * Transforms context when rendering an object
       * @param {CanvasRenderingContext2D} ctx Context
       * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node
       */
      transform: function (ctx, fromLeft) {
        if (this.group) {
          this.group.transform(ctx, fromLeft);
        }
        ctx.globalAlpha = this.opacity;

        var center = fromLeft
          ? this._getLeftTopCoords()
          : this.getCenterPoint();
        ctx.translate(center.x, center.y);
        ctx.rotate(degreesToRadians(this.angle));
        ctx.scale(
          this.scaleX * (this.flipX ? -1 : 1),
          this.scaleY * (this.flipY ? -1 : 1)
        );
      },

      /**
       * Returns an object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} Object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
          object = {
            type: this.type,
            originX: this.originX,
            originY: this.originY,
            left: toFixed(this.left, NUM_FRACTION_DIGITS),
            top: toFixed(this.top, NUM_FRACTION_DIGITS),
            width: toFixed(this.width, NUM_FRACTION_DIGITS),
            height: toFixed(this.height, NUM_FRACTION_DIGITS),
            fill:
              this.fill && this.fill.toObject
                ? this.fill.toObject()
                : this.fill,
            stroke:
              this.stroke && this.stroke.toObject
                ? this.stroke.toObject()
                : this.stroke,
            strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS),
            strokeDashArray: this.strokeDashArray,
            strokeLineCap: this.strokeLineCap,
            strokeLineJoin: this.strokeLineJoin,
            strokeMiterLimit: toFixed(
              this.strokeMiterLimit,
              NUM_FRACTION_DIGITS
            ),
            scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS),
            scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS),
            angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS),
            flipX: this.flipX,
            flipY: this.flipY,
            opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS),
            shadow:
              this.shadow && this.shadow.toObject
                ? this.shadow.toObject()
                : this.shadow,
            visible: this.visible,
            clipTo: this.clipTo && String(this.clipTo),
            backgroundColor: this.backgroundColor
          };

        if (!this.includeDefaultValues) {
          object = this._removeDefaultValues(object);
        }

        fabric.util.populateWithProperties(this, object, propertiesToInclude);

        return object;
      },

      /**
       * Returns (dataless) object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} Object representation of an instance
       */
      toDatalessObject: function (propertiesToInclude) {
        // will be overwritten by subclasses
        return this.toObject(propertiesToInclude);
      },

      /**
       * @private
       * @param {Object} object
       */
      _removeDefaultValues: function (object) {
        var prototype = fabric.util.getKlass(object.type).prototype,
          stateProperties = prototype.stateProperties;

        stateProperties.forEach(function (prop) {
          if (object[prop] === prototype[prop]) {
            delete object[prop];
          }
        });

        return object;
      },

      /**
       * Returns a string representation of an instance
       * @return {String}
       */
      toString: function () {
        return "#<fabric." + capitalize(this.type) + ">";
      },

      /**
       * Basic getter
       * @param {String} property Property name
       * @return {Any} value of a property
       */
      get: function (property) {
        return this[property];
      },

      /**
       * @private
       */
      _setObject: function (obj) {
        for (var prop in obj) {
          this._set(prop, obj[prop]);
        }
      },

      /**
       * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`.
       * @param {String|Object} key Property name or object (if object, iterate over the object properties)
       * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one)
       * @return {fabric.Object} thisArg
       * @chainable
       */
      set: function (key, value) {
        if (typeof key === "object") {
          this._setObject(key);
        } else {
          if (typeof value === "function" && key !== "clipTo") {
            this._set(key, value(this.get(key)));
          } else {
            this._set(key, value);
          }
        }
        return this;
      },

      /**
       * @private
       * @param {String} key
       * @param {Any} value
       * @return {fabric.Object} thisArg
       */
      _set: function (key, value) {
        var shouldConstrainValue = key === "scaleX" || key === "scaleY";

        if (shouldConstrainValue) {
          value = this._constrainScale(value);
        }
        if (key === "scaleX" && value < 0) {
          this.flipX = !this.flipX;
          value *= -1;
        } else if (key === "scaleY" && value < 0) {
          this.flipY = !this.flipY;
          value *= -1;
        } else if (key === "width" || key === "height") {
          this.minScaleLimit = toFixed(
            Math.min(0.1, 1 / Math.max(this.width, this.height)),
            2
          );
        } else if (
          key === "shadow" &&
          value &&
          !(value instanceof fabric.Shadow)
        ) {
          value = new fabric.Shadow(value);
        }

        this[key] = value;

        return this;
      },

      /**
       * Toggles specified property from `true` to `false` or from `false` to `true`
       * @param {String} property Property to toggle
       * @return {fabric.Object} thisArg
       * @chainable
       */
      toggle: function (property) {
        var value = this.get(property);
        if (typeof value === "boolean") {
          this.set(property, !value);
        }
        return this;
      },

      /**
       * Sets sourcePath of an object
       * @param {String} value Value to set sourcePath to
       * @return {fabric.Object} thisArg
       * @chainable
       */
      setSourcePath: function (value) {
        this.sourcePath = value;
        return this;
      },

      /**
       * Retrieves viewportTransform from Object's canvas if possible
       * @method getViewportTransform
       * @memberOf fabric.Object.prototype
       * @return {Boolean} flipY value // TODO
       */
      getViewportTransform: function () {
        if (this.canvas && this.canvas.viewportTransform)
          return this.canvas.viewportTransform;
        return [1, 0, 0, 1, 0, 0];
      },

      /**
       * Renders an object on a specified context
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Boolean} [noTransform] When true, context is not transformed
       */
      render: function (ctx, noTransform) {
        // do not render if width/height are zeros or object is not visible
        if (this.width === 0 || this.height === 0 || !this.visible) return;

        ctx.save();

        //setup fill rule for current object
        this._setupFillRule(ctx);

        this._transform(ctx, noTransform);
        this._setStrokeStyles(ctx);
        this._setFillStyles(ctx);

        var m = this.transformMatrix;
        if (m && this.group) {
          ctx.translate(-this.group.width / 2, -this.group.height / 2);
          ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
        }

        this._setShadow(ctx);
        this.clipTo && fabric.util.clipContext(this, ctx);
        this._render(ctx, noTransform);
        this.clipTo && ctx.restore();
        this._removeShadow(ctx);
        this._restoreFillRule(ctx);

        ctx.restore();
      },

      _transform: function (ctx, noTransform) {
        var m = this.transformMatrix;

        if (m && !this.group) {
          ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
        }
        if (!noTransform) {
          this.transform(ctx);
        }
      },

      _setStrokeStyles: function (ctx) {
        if (this.stroke) {
          ctx.lineWidth = this.strokeWidth;
          ctx.lineCap = this.strokeLineCap;
          ctx.lineJoin = this.strokeLineJoin;
          ctx.miterLimit = this.strokeMiterLimit;
          ctx.strokeStyle = this.stroke.toLive
            ? this.stroke.toLive(ctx)
            : this.stroke;
        }
      },

      _setFillStyles: function (ctx) {
        if (this.fill) {
          ctx.fillStyle = this.fill.toLive ? this.fill.toLive(ctx) : this.fill;
        }
      },

      /**
       * Renders controls and borders for the object
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Boolean} [noTransform] When true, context is not transformed
       */
      _renderControls: function (ctx, noTransform) {
        var vpt = this.getViewportTransform();

        ctx.save();
        if (this.active && !noTransform) {
          var center;
          if (this.group) {
            center = fabric.util.transformPoint(
              this.group.getCenterPoint(),
              vpt
            );
            ctx.translate(center.x, center.y);
            ctx.rotate(degreesToRadians(this.group.angle));
          }
          center = fabric.util.transformPoint(
            this.getCenterPoint(),
            vpt,
            null != this.group
          );
          if (this.group) {
            center.x *= this.group.scaleX;
            center.y *= this.group.scaleY;
          }
          ctx.translate(center.x, center.y);
          ctx.rotate(degreesToRadians(this.angle));
          this.drawBorders(ctx);
          this.drawControls(ctx);
        }
        ctx.restore();
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _setShadow: function (ctx) {
        if (!this.shadow) return;

        ctx.shadowColor = this.shadow.color;
        ctx.shadowBlur = this.shadow.blur;
        ctx.shadowOffsetX = this.shadow.offsetX;
        ctx.shadowOffsetY = this.shadow.offsetY;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _removeShadow: function (ctx) {
        if (!this.shadow) return;

        ctx.shadowColor = "";
        ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderFill: function (ctx) {
        if (!this.fill) return;

        if (this.fill.toLive) {
          ctx.save();
          ctx.translate(
            -this.width / 2 + this.fill.offsetX || 0,
            -this.height / 2 + this.fill.offsetY || 0
          );
        }
        if (this.fillRule === "destination-over") {
          ctx.fill("evenodd");
        } else {
          ctx.fill();
        }
        if (this.fill.toLive) {
          ctx.restore();
        }
        if (this.shadow && !this.shadow.affectStroke) {
          this._removeShadow(ctx);
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderStroke: function (ctx) {
        if (!this.stroke || this.strokeWidth === 0) return;

        ctx.save();
        if (this.strokeDashArray) {
          // Spec requires the concatenation of two copies the dash list when the number of elements is odd
          if (1 & this.strokeDashArray.length) {
            this.strokeDashArray.push.apply(
              this.strokeDashArray,
              this.strokeDashArray
            );
          }

          if (supportsLineDash) {
            ctx.setLineDash(this.strokeDashArray);
            this._stroke && this._stroke(ctx);
          } else {
            this._renderDashedStroke && this._renderDashedStroke(ctx);
          }
          ctx.stroke();
        } else {
          this._stroke ? this._stroke(ctx) : ctx.stroke();
        }
        this._removeShadow(ctx);
        ctx.restore();
      },

      /**
       * Clones an instance
       * @param {Function} callback Callback is invoked with a clone as a first argument
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {fabric.Object} clone of an instance
       */
      clone: function (callback, propertiesToInclude) {
        if (this.constructor.fromObject) {
          return this.constructor.fromObject(
            this.toObject(propertiesToInclude),
            callback
          );
        }
        return new fabric.Object(this.toObject(propertiesToInclude));
      },

      /**
       * Creates an instance of fabric.Image out of an object
       * @param {Function} callback callback, invoked with an instance as a first argument
       * @return {fabric.Object} thisArg
       */
      cloneAsImage: function (callback) {
        var dataUrl = this.toDataURL();
        fabric.util.loadImage(dataUrl, function (img) {
          if (callback) {
            callback(new fabric.Image(img));
          }
        });
        return this;
      },

      /**
       * Converts an object into a data-url-like string
       * @param {Object} options Options object
       * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
       * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
       * @param {Number} [options.multiplier=1] Multiplier to scale by
       * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
       * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
       * @param {Number} [options.width] Cropping width. Introduced in v1.2.14
       * @param {Number} [options.height] Cropping height. Introduced in v1.2.14
       * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
       */
      toDataURL: function (options) {
        options || (options = {});

        var el = fabric.util.createCanvasElement(),
          boundingRect = this.getBoundingRect();

        el.width = boundingRect.width;
        el.height = boundingRect.height;

        fabric.util.wrapElement(el, "div");
        var canvas = new fabric.Canvas(el);

        // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
        if (options.format === "jpg") {
          options.format = "jpeg";
        }

        if (options.format === "jpeg") {
          canvas.backgroundColor = "#fff";
        }

        var origParams = {
          active: this.get("active"),
          left: this.getLeft(),
          top: this.getTop()
        };

        this.set("active", false);
        this.setPositionByOrigin(
          new fabric.Point(el.width / 2, el.height / 2),
          "center",
          "center"
        );

        var originalCanvas = this.canvas;
        canvas.add(this);
        var data = canvas.toDataURL(options);

        this.set(origParams).setCoords();
        this.canvas = originalCanvas;

        canvas.dispose();
        canvas = null;

        return data;
      },

      /**
       * Returns true if specified type is identical to the type of an instance
       * @param {String} type Type to check against
       * @return {Boolean}
       */
      isType: function (type) {
        return this.type === type;
      },

      /**
       * Returns complexity of an instance
       * @return {Number} complexity of this instance
       */
      complexity: function () {
        return 0;
      },

      /**
       * Returns a JSON representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} JSON
       */
      toJSON: function (propertiesToInclude) {
        // delegate, not alias
        return this.toObject(propertiesToInclude);
      },

      /**
       * Sets gradient (fill or stroke) of an object
       * <b>Backwards incompatibility note:</b> This method was named "setGradientFill" until v1.1.0
       * @param {String} property Property name 'stroke' or 'fill'
       * @param {Object} [options] Options object
       * @param {String} [options.type] Type of gradient 'radial' or 'linear'
       * @param {Number} [options.x1=0] x-coordinate of start point
       * @param {Number} [options.y1=0] y-coordinate of start point
       * @param {Number} [options.x2=0] x-coordinate of end point
       * @param {Number} [options.y2=0] y-coordinate of end point
       * @param {Number} [options.r1=0] Radius of start point (only for radial gradients)
       * @param {Number} [options.r2=0] Radius of end point (only for radial gradients)
       * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'}
       * @return {fabric.Object} thisArg
       * @chainable
       * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo}
       * @example <caption>Set linear gradient</caption>
       * object.setGradient('fill', {
       *   type: 'linear',
       *   x1: -object.width / 2,
       *   y1: 0,
       *   x2: object.width / 2,
       *   y2: 0,
       *   colorStops: {
       *     0: 'red',
       *     0.5: '#005555',
       *     1: 'rgba(0,0,255,0.5)'
       *   }
       * });
       * canvas.renderAll();
       * @example <caption>Set radial gradient</caption>
       * object.setGradient('fill', {
       *   type: 'radial',
       *   x1: 0,
       *   y1: 0,
       *   x2: 0,
       *   y2: 0,
       *   r1: object.width / 2,
       *   r2: 10,
       *   colorStops: {
       *     0: 'red',
       *     0.5: '#005555',
       *     1: 'rgba(0,0,255,0.5)'
       *   }
       * });
       * canvas.renderAll();
       */
      setGradient: function (property, options) {
        options || (options = {});

        var gradient = { colorStops: [] };

        gradient.type =
          options.type || (options.r1 || options.r2 ? "radial" : "linear");
        gradient.coords = {
          x1: options.x1,
          y1: options.y1,
          x2: options.x2,
          y2: options.y2
        };

        if (options.r1 || options.r2) {
          gradient.coords.r1 = options.r1;
          gradient.coords.r2 = options.r2;
        }

        for (var position in options.colorStops) {
          var color = new fabric.Color(options.colorStops[position]);
          gradient.colorStops.push({
            offset: position,
            color: color.toRgb(),
            opacity: color.getAlpha()
          });
        }

        return this.set(property, fabric.Gradient.forObject(this, gradient));
      },

      /**
       * Sets pattern fill of an object
       * @param {Object} options Options object
       * @param {(String|HTMLImageElement)} options.source Pattern source
       * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
       * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner
       * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner
       * @return {fabric.Object} thisArg
       * @chainable
       * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo}
       * @example <caption>Set pattern</caption>
       * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) {
       *   object.setPatternFill({
       *     source: img,
       *     repeat: 'repeat'
       *   });
       *   canvas.renderAll();
       * });
       */
      setPatternFill: function (options) {
        return this.set("fill", new fabric.Pattern(options));
      },

      /**
       * Sets {@link fabric.Object#shadow|shadow} of an object
       * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)")
       * @param {String} [options.color=rgb(0,0,0)] Shadow color
       * @param {Number} [options.blur=0] Shadow blur
       * @param {Number} [options.offsetX=0] Shadow horizontal offset
       * @param {Number} [options.offsetY=0] Shadow vertical offset
       * @return {fabric.Object} thisArg
       * @chainable
       * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo}
       * @example <caption>Set shadow with string notation</caption>
       * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)');
       * canvas.renderAll();
       * @example <caption>Set shadow with object notation</caption>
       * object.setShadow({
       *   color: 'red',
       *   blur: 10,
       *   offsetX: 20,
       *   offsetY: 20
       * });
       * canvas.renderAll();
       */
      setShadow: function (options) {
        return this.set("shadow", options ? new fabric.Shadow(options) : null);
      },

      /**
       * Sets "color" of an instance (alias of `set('fill', &hellip;)`)
       * @param {String} color Color value
       * @return {fabric.Object} thisArg
       * @chainable
       */
      setColor: function (color) {
        this.set("fill", color);
        return this;
      },

      /**
       * Sets "angle" of an instance
       * @param {Number} angle Angle value
       * @return {fabric.Object} thisArg
       * @chainable
       */
      setAngle: function (angle) {
        var shouldCenterOrigin =
          (this.originX !== "center" || this.originY !== "center") &&
          this.centeredRotation;

        if (shouldCenterOrigin) {
          this._setOriginToCenter();
        }

        this.set("angle", angle);

        if (shouldCenterOrigin) {
          this._resetOrigin();
        }

        return this;
      },

      /**
       * Centers object horizontally on canvas to which it was added last.
       * You might need to call `setCoords` on an object after centering, to update controls area.
       * @return {fabric.Object} thisArg
       * @chainable
       */
      centerH: function () {
        this.canvas.centerObjectH(this);
        return this;
      },

      /**
       * Centers object vertically on canvas to which it was added last.
       * You might need to call `setCoords` on an object after centering, to update controls area.
       * @return {fabric.Object} thisArg
       * @chainable
       */
      centerV: function () {
        this.canvas.centerObjectV(this);
        return this;
      },

      /**
       * Centers object vertically and horizontally on canvas to which is was added last
       * You might need to call `setCoords` on an object after centering, to update controls area.
       * @return {fabric.Object} thisArg
       * @chainable
       */
      center: function () {
        this.canvas.centerObject(this);
        return this;
      },

      /**
       * Removes object from canvas to which it was added last
       * @return {fabric.Object} thisArg
       * @chainable
       */
      remove: function () {
        this.canvas.remove(this);
        return this;
      },

      /**
       * Returns coordinates of a pointer relative to an object
       * @param {Event} e Event to operate upon
       * @param {Object} [pointer] Pointer to operate upon (instead of event)
       * @return {Object} Coordinates of a pointer (x, y)
       */
      getLocalPointer: function (e, pointer) {
        pointer = pointer || this.canvas.getPointer(e);
        var objectLeftTop = this.translateToOriginPoint(
          this.getCenterPoint(),
          "left",
          "top"
        );
        return {
          x: pointer.x - objectLeftTop.x,
          y: pointer.y - objectLeftTop.y
        };
      },

      /**
       * Sets canvas globalCompositeOperation for specific object
       * custom composition operation for the particular object can be specifed using fillRule property
       * @param {CanvasRenderingContext2D} ctx Rendering canvas context
       */
      _setupFillRule: function (ctx) {
        if (this.fillRule) {
          this._prevFillRule = ctx.globalCompositeOperation;
          ctx.globalCompositeOperation = this.fillRule;
        }
      },

      /**
       * Restores previously saved canvas globalCompositeOperation after obeject rendering
       * @param {CanvasRenderingContext2D} ctx Rendering canvas context
       */
      _restoreFillRule: function (ctx) {
        if (this.fillRule && this._prevFillRule) {
          ctx.globalCompositeOperation = this._prevFillRule;
        }
      }
    }
  );

  fabric.util.createAccessors(fabric.Object);

  /**
   * Alias for {@link fabric.Object.prototype.setAngle}
   * @alias rotate -> setAngle
   * @memberof fabric.Object
   */
  fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle;

  extend(fabric.Object.prototype, fabric.Observable);

  /**
   * Defines the number of fraction digits to use when serializing object values.
   * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc.
   * @static
   * @memberof fabric.Object
   * @constant
   * @type Number
   */
  fabric.Object.NUM_FRACTION_DIGITS = 2;

  /**
   * Unique id used internally when creating SVG elements
   * @static
   * @memberof fabric.Object
   * @type Number
   */
  fabric.Object.__uid = 0;
})(typeof exports !== "undefined" ? exports : this);

(function () {
  var degreesToRadians = fabric.util.degreesToRadians;

  fabric.util.object.extend(
    fabric.Object.prototype,
    /** @lends fabric.Object.prototype */ {
      /**
       * Translates the coordinates from origin to center coordinates (based on the object's dimensions)
       * @param {fabric.Point} point The point which corresponds to the originX and originY params
       * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
       * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
       * @return {fabric.Point}
       */
      translateToCenterPoint: function (point, originX, originY) {
        var cx = point.x,
          cy = point.y,
          strokeWidth = this.stroke ? this.strokeWidth : 0;

        if (originX === "left") {
          cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2;
        } else if (originX === "right") {
          cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2;
        }

        if (originY === "top") {
          cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2;
        } else if (originY === "bottom") {
          cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2;
        }

        // Apply the reverse rotation to the point (it's already scaled properly)
        return fabric.util.rotatePoint(
          new fabric.Point(cx, cy),
          point,
          degreesToRadians(this.angle)
        );
      },

      /**
       * Translates the coordinates from center to origin coordinates (based on the object's dimensions)
       * @param {fabric.Point} center The point which corresponds to center of the object
       * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
       * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
       * @return {fabric.Point}
       */
      translateToOriginPoint: function (center, originX, originY) {
        var x = center.x,
          y = center.y,
          strokeWidth = this.stroke ? this.strokeWidth : 0;

        // Get the point coordinates
        if (originX === "left") {
          x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2;
        } else if (originX === "right") {
          x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2;
        }
        if (originY === "top") {
          y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2;
        } else if (originY === "bottom") {
          y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2;
        }

        // Apply the rotation to the point (it's already scaled properly)
        return fabric.util.rotatePoint(
          new fabric.Point(x, y),
          center,
          degreesToRadians(this.angle)
        );
      },

      /**
       * Returns the real center coordinates of the object
       * @return {fabric.Point}
       */
      getCenterPoint: function () {
        var leftTop = new fabric.Point(this.left, this.top);
        return this.translateToCenterPoint(leftTop, this.originX, this.originY);
      },

      /**
       * Returns the coordinates of the object based on center coordinates
       * @param {fabric.Point} point The point which corresponds to the originX and originY params
       * @return {fabric.Point}
       */
      // getOriginPoint: function(center) {
      //   return this.translateToOriginPoint(center, this.originX, this.originY);
      // },

      /**
       * Returns the coordinates of the object as if it has a different origin
       * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
       * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
       * @return {fabric.Point}
       */
      getPointByOrigin: function (originX, originY) {
        var center = this.getCenterPoint();
        return this.translateToOriginPoint(center, originX, originY);
      },

      /**
       * Returns the point in local coordinates
       * @param {fabric.Point} point The point relative to the global coordinate system
       * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
       * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
       * @return {fabric.Point}
       */
      toLocalPoint: function (point, originX, originY) {
        var center = this.getCenterPoint(),
          strokeWidth = this.stroke ? this.strokeWidth : 0,
          x,
          y;

        if (originX && originY) {
          if (originX === "left") {
            x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2;
          } else if (originX === "right") {
            x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2;
          } else {
            x = center.x;
          }

          if (originY === "top") {
            y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2;
          } else if (originY === "bottom") {
            y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2;
          } else {
            y = center.y;
          }
        } else {
          x = this.left;
          y = this.top;
        }

        return fabric.util
          .rotatePoint(
            new fabric.Point(point.x, point.y),
            center,
            -degreesToRadians(this.angle)
          )
          .subtractEquals(new fabric.Point(x, y));
      },

      /**
       * Returns the point in global coordinates
       * @param {fabric.Point} The point relative to the local coordinate system
       * @return {fabric.Point}
       */
      // toGlobalPoint: function(point) {
      //   return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top));
      // },

      /**
       * Sets the position of the object taking into consideration the object's origin
       * @param {fabric.Point} pos The new position of the object
       * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
       * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
       * @return {void}
       */
      setPositionByOrigin: function (pos, originX, originY) {
        var center = this.translateToCenterPoint(pos, originX, originY),
          position = this.translateToOriginPoint(
            center,
            this.originX,
            this.originY
          );

        this.set("left", position.x);
        this.set("top", position.y);
      },

      /**
       * @param {String} to One of 'left', 'center', 'right'
       */
      adjustPosition: function (to) {
        var angle = degreesToRadians(this.angle),
          hypotHalf = this.getWidth() / 2,
          xHalf = Math.cos(angle) * hypotHalf,
          yHalf = Math.sin(angle) * hypotHalf,
          hypotFull = this.getWidth(),
          xFull = Math.cos(angle) * hypotFull,
          yFull = Math.sin(angle) * hypotFull;

        if (
          (this.originX === "center" && to === "left") ||
          (this.originX === "right" && to === "center")
        ) {
          // move half left
          this.left -= xHalf;
          this.top -= yHalf;
        } else if (
          (this.originX === "left" && to === "center") ||
          (this.originX === "center" && to === "right")
        ) {
          // move half right
          this.left += xHalf;
          this.top += yHalf;
        } else if (this.originX === "left" && to === "right") {
          // move full right
          this.left += xFull;
          this.top += yFull;
        } else if (this.originX === "right" && to === "left") {
          // move full left
          this.left -= xFull;
          this.top -= yFull;
        }

        this.setCoords();
        this.originX = to;
      },

      /**
       * Sets the origin/position of the object to it's center point
       * @private
       * @return {void}
       */
      _setOriginToCenter: function () {
        this._originalOriginX = this.originX;
        this._originalOriginY = this.originY;

        var center = this.getCenterPoint();

        this.originX = "center";
        this.originY = "center";

        this.left = center.x;
        this.top = center.y;
      },

      /**
       * Resets the origin/position of the object to it's original origin
       * @private
       * @return {void}
       */
      _resetOrigin: function () {
        var originPoint = this.translateToOriginPoint(
          this.getCenterPoint(),
          this._originalOriginX,
          this._originalOriginY
        );

        this.originX = this._originalOriginX;
        this.originY = this._originalOriginY;

        this.left = originPoint.x;
        this.top = originPoint.y;

        this._originalOriginX = null;
        this._originalOriginY = null;
      },

      /**
       * @private
       */
      _getLeftTopCoords: function () {
        return this.translateToOriginPoint(
          this.getCenterPoint(),
          "left",
          "center"
        );
      }
    }
  );
})();

(function () {
  var degreesToRadians = fabric.util.degreesToRadians;

  fabric.util.object.extend(
    fabric.Object.prototype,
    /** @lends fabric.Object.prototype */ {
      /**
       * Object containing coordinates of object's controls
       * @type Object
       * @default
       */
      oCoords: null,

      /**
       * Checks if object intersects with an area formed by 2 points
       * @param {Object} pointTL top-left point of area
       * @param {Object} pointBR bottom-right point of area
       * @return {Boolean} true if object intersects with an area formed by 2 points
       */
      intersectsWithRect: function (pointTL, pointBR) {
        var oCoords = this.oCoords,
          tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),
          tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),
          bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y),
          br = new fabric.Point(oCoords.br.x, oCoords.br.y),
          intersection = fabric.Intersection.intersectPolygonRectangle(
            [tl, tr, br, bl],
            pointTL,
            pointBR
          );
        return intersection.status === "Intersection";
      },

      /**
       * Checks if object intersects with another object
       * @param {Object} other Object to test
       * @return {Boolean} true if object intersects with another object
       */
      intersectsWithObject: function (other) {
        // extracts coords
        function getCoords(oCoords) {
          return {
            tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y),
            tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y),
            bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y),
            br: new fabric.Point(oCoords.br.x, oCoords.br.y)
          };
        }
        var thisCoords = getCoords(this.oCoords),
          otherCoords = getCoords(other.oCoords),
          intersection = fabric.Intersection.intersectPolygonPolygon(
            [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl],
            [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl]
          );

        return intersection.status === "Intersection";
      },

      /**
       * Checks if object is fully contained within area of another object
       * @param {Object} other Object to test
       * @return {Boolean} true if object is fully contained within area of another object
       */
      isContainedWithinObject: function (other) {
        var boundingRect = other.getBoundingRect(),
          point1 = new fabric.Point(boundingRect.left, boundingRect.top),
          point2 = new fabric.Point(
            boundingRect.left + boundingRect.width,
            boundingRect.top + boundingRect.height
          );

        return this.isContainedWithinRect(point1, point2);
      },

      /**
       * Checks if object is fully contained within area formed by 2 points
       * @param {Object} pointTL top-left point of area
       * @param {Object} pointBR bottom-right point of area
       * @return {Boolean} true if object is fully contained within area formed by 2 points
       */
      isContainedWithinRect: function (pointTL, pointBR) {
        var boundingRect = this.getBoundingRect();

        return (
          boundingRect.left >= pointTL.x &&
          boundingRect.left + boundingRect.width <= pointBR.x &&
          boundingRect.top >= pointTL.y &&
          boundingRect.top + boundingRect.height <= pointBR.y
        );
      },

      /**
       * Checks if point is inside the object
       * @param {fabric.Point} point Point to check against
       * @return {Boolean} true if point is inside the object
       */
      containsPoint: function (point) {
        var lines = this._getImageLines(this.oCoords),
          xPoints = this._findCrossPoints(point, lines);

        // if xPoints is odd then point is inside the object
        return xPoints !== 0 && xPoints % 2 === 1;
      },

      /**
       * Method that returns an object with the object edges in it, given the coordinates of the corners
       * @private
       * @param {Object} oCoords Coordinates of the object corners
       */
      _getImageLines: function (oCoords) {
        return {
          topline: {
            o: oCoords.tl,
            d: oCoords.tr
          },
          rightline: {
            o: oCoords.tr,
            d: oCoords.br
          },
          bottomline: {
            o: oCoords.br,
            d: oCoords.bl
          },
          leftline: {
            o: oCoords.bl,
            d: oCoords.tl
          }
        };
      },

      /**
       * Helper method to determine how many cross points are between the 4 object edges
       * and the horizontal line determined by a point on canvas
       * @private
       * @param {fabric.Point} point Point to check
       * @param {Object} oCoords Coordinates of the object being evaluated
       */
      _findCrossPoints: function (point, oCoords) {
        var b1,
          b2,
          a1,
          a2,
          xi,
          yi,
          xcount = 0,
          iLine;

        for (var lineKey in oCoords) {
          iLine = oCoords[lineKey];
          // optimisation 1: line below point. no cross
          if (iLine.o.y < point.y && iLine.d.y < point.y) {
            continue;
          }
          // optimisation 2: line above point. no cross
          if (iLine.o.y >= point.y && iLine.d.y >= point.y) {
            continue;
          }
          // optimisation 3: vertical line case
          if (iLine.o.x === iLine.d.x && iLine.o.x >= point.x) {
            xi = iLine.o.x;
            yi = point.y;
          }
          // calculate the intersection point
          else {
            b1 = 0;
            b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x);
            a1 = point.y - b1 * point.x;
            a2 = iLine.o.y - b2 * iLine.o.x;

            xi = -(a1 - a2) / (b1 - b2);
            yi = a1 + b1 * xi;
          }
          // dont count xi < point.x cases
          if (xi >= point.x) {
            xcount += 1;
          }
          // optimisation 4: specific for square images
          if (xcount === 2) {
            break;
          }
        }
        return xcount;
      },

      /**
       * Returns width of an object's bounding rectangle
       * @deprecated since 1.0.4
       * @return {Number} width value
       */
      getBoundingRectWidth: function () {
        return this.getBoundingRect().width;
      },

      /**
       * Returns height of an object's bounding rectangle
       * @deprecated since 1.0.4
       * @return {Number} height value
       */
      getBoundingRectHeight: function () {
        return this.getBoundingRect().height;
      },

      /**
       * Returns coordinates of object's bounding rectangle (left, top, width, height)
       * @return {Object} Object with left, top, width, height properties
       */
      getBoundingRect: function () {
        this.oCoords || this.setCoords();

        var xCoords = [
            this.oCoords.tl.x,
            this.oCoords.tr.x,
            this.oCoords.br.x,
            this.oCoords.bl.x
          ],
          minX = fabric.util.array.min(xCoords),
          maxX = fabric.util.array.max(xCoords),
          width = Math.abs(minX - maxX),
          yCoords = [
            this.oCoords.tl.y,
            this.oCoords.tr.y,
            this.oCoords.br.y,
            this.oCoords.bl.y
          ],
          minY = fabric.util.array.min(yCoords),
          maxY = fabric.util.array.max(yCoords),
          height = Math.abs(minY - maxY);

        return {
          left: minX,
          top: minY,
          width: width,
          height: height
        };
      },

      /**
       * Returns width of an object
       * @return {Number} width value
       */
      getWidth: function () {
        return this.width * this.scaleX;
      },

      /**
       * Returns height of an object
       * @return {Number} height value
       */
      getHeight: function () {
        return this.height * this.scaleY;
      },

      /**
       * Makes sure the scale is valid and modifies it if necessary
       * @private
       * @param {Number} value
       * @return {Number}
       */
      _constrainScale: function (value) {
        if (Math.abs(value) < this.minScaleLimit) {
          if (value < 0) {
            return -this.minScaleLimit;
          } else {
            return this.minScaleLimit;
          }
        }
        return value;
      },

      /**
       * Scales an object (equally by x and y)
       * @param {Number} value Scale factor
       * @return {fabric.Object} thisArg
       * @chainable
       */
      scale: function (value) {
        value = this._constrainScale(value);

        if (value < 0) {
          this.flipX = !this.flipX;
          this.flipY = !this.flipY;
          value *= -1;
        }

        this.scaleX = value;
        this.scaleY = value;
        this.setCoords();
        return this;
      },

      /**
       * Scales an object to a given width, with respect to bounding box (scaling by x/y equally)
       * @param {Number} value New width value
       * @return {fabric.Object} thisArg
       * @chainable
       */
      scaleToWidth: function (value) {
        // adjust to bounding rect factor so that rotated shapes would fit as well
        var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth();
        return this.scale(value / this.width / boundingRectFactor);
      },

      /**
       * Scales an object to a given height, with respect to bounding box (scaling by x/y equally)
       * @param {Number} value New height value
       * @return {fabric.Object} thisArg
       * @chainable
       */
      scaleToHeight: function (value) {
        // adjust to bounding rect factor so that rotated shapes would fit as well
        var boundingRectFactor =
          this.getBoundingRectHeight() / this.getHeight();
        return this.scale(value / this.height / boundingRectFactor);
      },

      /**
       * Sets corner position coordinates based on current angle, width and height
       * @return {fabric.Object} thisArg
       * @chainable
       */
      setCoords: function () {
        var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0,
          theta = degreesToRadians(this.angle),
          vpt = this.getViewportTransform(),
          f = function (p) {
            return fabric.util.transformPoint(p, vpt);
          },
          w = this.width,
          h = this.height,
          capped =
            this.strokeLineCap === "round" || this.strokeLineCap === "square",
          vLine = this.type === "line" && this.width === 1,
          hLine = this.type === "line" && this.height === 1,
          strokeW = (capped && hLine) || this.type !== "line",
          strokeH = (capped && vLine) || this.type !== "line";

        if (vLine) {
          w = strokeWidth;
        } else if (hLine) {
          h = strokeWidth;
        }
        if (strokeW) {
          w += strokeWidth;
        }
        if (strokeH) {
          h += strokeWidth;
        }
        this.currentWidth = w * this.scaleX;
        this.currentHeight = h * this.scaleY;

        // If width is negative, make postive. Fixes path selection issue
        if (this.currentWidth < 0) {
          this.currentWidth = Math.abs(this.currentWidth);
        }

        var _hypotenuse = Math.sqrt(
            Math.pow(this.currentWidth / 2, 2) +
              Math.pow(this.currentHeight / 2, 2)
          ),
          _angle = Math.atan(
            isFinite(this.currentHeight / this.currentWidth)
              ? this.currentHeight / this.currentWidth
              : 0
          ),
          // offset added for rotate and scale actions
          offsetX = Math.cos(_angle + theta) * _hypotenuse,
          offsetY = Math.sin(_angle + theta) * _hypotenuse,
          sinTh = Math.sin(theta),
          cosTh = Math.cos(theta),
          coords = this.getCenterPoint(),
          wh = new fabric.Point(this.currentWidth, this.currentHeight),
          _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY),
          _tr = new fabric.Point(_tl.x + wh.x * cosTh, _tl.y + wh.x * sinTh),
          _bl = new fabric.Point(_tl.x - wh.y * sinTh, _tl.y + wh.y * cosTh),
          _mt = new fabric.Point(
            _tl.x + (wh.x / 2) * cosTh,
            _tl.y + (wh.x / 2) * sinTh
          ),
          tl = f(_tl),
          tr = f(_tr),
          br = f(new fabric.Point(_tr.x - wh.y * sinTh, _tr.y + wh.y * cosTh)),
          bl = f(_bl),
          ml = f(
            new fabric.Point(
              _tl.x - (wh.y / 2) * sinTh,
              _tl.y + (wh.y / 2) * cosTh
            )
          ),
          mt = f(_mt),
          mr = f(
            new fabric.Point(
              _tr.x - (wh.y / 2) * sinTh,
              _tr.y + (wh.y / 2) * cosTh
            )
          ),
          mb = f(
            new fabric.Point(
              _bl.x + (wh.x / 2) * cosTh,
              _bl.y + (wh.x / 2) * sinTh
            )
          ),
          mtr = f(new fabric.Point(_mt.x, _mt.y)),
          // padding
          padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2),
          padY = Math.sin(_angle + theta) * this.padding * Math.sqrt(2);

        tl = tl.add(new fabric.Point(-padX, -padY));
        tr = tr.add(new fabric.Point(padY, -padX));
        br = br.add(new fabric.Point(padX, padY));
        bl = bl.add(new fabric.Point(-padY, padX));
        ml = ml.add(new fabric.Point((-padX - padY) / 2, (-padY + padX) / 2));
        mt = mt.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2));
        mr = mr.add(new fabric.Point((padY + padX) / 2, (padY - padX) / 2));
        mb = mb.add(new fabric.Point((padX - padY) / 2, (padX + padY) / 2));
        mtr = mtr.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2));

        // debugging

        // setTimeout(function() {
        //   canvas.contextTop.fillStyle = 'green';
        //   canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);
        //   canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);
        //   canvas.contextTop.fillRect(br.x, br.y, 3, 3);
        //   canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);
        //   canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);
        //   canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);
        //   canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);
        //   canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);
        // }, 50);

        this.oCoords = {
          // corners
          tl: tl,
          tr: tr,
          br: br,
          bl: bl,
          // middle
          ml: ml,
          mt: mt,
          mr: mr,
          mb: mb,
          // rotating point
          mtr: mtr
        };

        // set coordinates of the draggable boxes in the corners used to scale/rotate the image
        this._setCornerCoords && this._setCornerCoords();

        return this;
      }
    }
  );
})();

fabric.util.object.extend(
  fabric.Object.prototype,
  /** @lends fabric.Object.prototype */ {
    /**
     * Moves an object to the bottom of the stack of drawn objects
     * @return {fabric.Object} thisArg
     * @chainable
     */
    sendToBack: function () {
      if (this.group) {
        fabric.StaticCanvas.prototype.sendToBack.call(this.group, this);
      } else {
        this.canvas.sendToBack(this);
      }
      return this;
    },

    /**
     * Moves an object to the top of the stack of drawn objects
     * @return {fabric.Object} thisArg
     * @chainable
     */
    bringToFront: function () {
      if (this.group) {
        fabric.StaticCanvas.prototype.bringToFront.call(this.group, this);
      } else {
        this.canvas.bringToFront(this);
      }
      return this;
    },

    /**
     * Moves an object down in stack of drawn objects
     * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object
     * @return {fabric.Object} thisArg
     * @chainable
     */
    sendBackwards: function (intersecting) {
      if (this.group) {
        fabric.StaticCanvas.prototype.sendBackwards.call(
          this.group,
          this,
          intersecting
        );
      } else {
        this.canvas.sendBackwards(this, intersecting);
      }
      return this;
    },

    /**
     * Moves an object up in stack of drawn objects
     * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object
     * @return {fabric.Object} thisArg
     * @chainable
     */
    bringForward: function (intersecting) {
      if (this.group) {
        fabric.StaticCanvas.prototype.bringForward.call(
          this.group,
          this,
          intersecting
        );
      } else {
        this.canvas.bringForward(this, intersecting);
      }
      return this;
    },

    /**
     * Moves an object to specified level in stack of drawn objects
     * @param {Number} index New position of object
     * @return {fabric.Object} thisArg
     * @chainable
     */
    moveTo: function (index) {
      if (this.group) {
        fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index);
      } else {
        this.canvas.moveTo(this, index);
      }
      return this;
    }
  }
);

/* _TO_SVG_START_ */
fabric.util.object.extend(
  fabric.Object.prototype,
  /** @lends fabric.Object.prototype */ {
    /**
     * Returns styles-string for svg-export
     * @return {String}
     */
    getSvgStyles: function () {
      var fill = this.fill
          ? this.fill.toLive
            ? "url(#SVGID_" + this.fill.id + ")"
            : this.fill
          : "none",
        stroke = this.stroke
          ? this.stroke.toLive
            ? "url(#SVGID_" + this.stroke.id + ")"
            : this.stroke
          : "none",
        strokeWidth = this.strokeWidth ? this.strokeWidth : "0",
        strokeDashArray = this.strokeDashArray
          ? this.strokeDashArray.join(" ")
          : "",
        strokeLineCap = this.strokeLineCap ? this.strokeLineCap : "butt",
        strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : "miter",
        strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : "4",
        opacity = typeof this.opacity !== "undefined" ? this.opacity : "1",
        visibility = this.visible ? "" : " visibility: hidden;",
        filter =
          this.shadow && this.type !== "text"
            ? "filter: url(#SVGID_" + this.shadow.id + ");"
            : "";

      return [
        "stroke: ",
        stroke,
        "; ",
        "stroke-width: ",
        strokeWidth,
        "; ",
        "stroke-dasharray: ",
        strokeDashArray,
        "; ",
        "stroke-linecap: ",
        strokeLineCap,
        "; ",
        "stroke-linejoin: ",
        strokeLineJoin,
        "; ",
        "stroke-miterlimit: ",
        strokeMiterLimit,
        "; ",
        "fill: ",
        fill,
        "; ",
        "opacity: ",
        opacity,
        ";",
        filter,
        visibility
      ].join("");
    },

    /**
     * Returns transform-string for svg-export
     * @return {String}
     */
    getSvgTransform: function () {
      var toFixed = fabric.util.toFixed,
        angle = this.getAngle(),
        vpt = this.getViewportTransform(),
        center = fabric.util.transformPoint(this.getCenterPoint(), vpt),
        NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
        translatePart =
          "translate(" +
          toFixed(center.x, NUM_FRACTION_DIGITS) +
          " " +
          toFixed(center.y, NUM_FRACTION_DIGITS) +
          ")",
        anglePart =
          angle !== 0
            ? " rotate(" + toFixed(angle, NUM_FRACTION_DIGITS) + ")"
            : "",
        scalePart =
          this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1
            ? ""
            : " scale(" +
              toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) +
              " " +
              toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) +
              ")",
        flipXPart = this.flipX ? "matrix(-1 0 0 1 0 0) " : "",
        flipYPart = this.flipY ? "matrix(1 0 0 -1 0 0)" : "";

      return [translatePart, anglePart, scalePart, flipXPart, flipYPart].join(
        ""
      );
    },

    /**
     * @private
     */
    _createBaseSVGMarkup: function () {
      var markup = [];

      if (this.fill && this.fill.toLive) {
        markup.push(this.fill.toSVG(this, false));
      }
      if (this.stroke && this.stroke.toLive) {
        markup.push(this.stroke.toSVG(this, false));
      }
      if (this.shadow) {
        markup.push(this.shadow.toSVG(this));
      }
      return markup;
    }
  }
);
/* _TO_SVG_END_ */

/*
  Depends on `stateProperties`
*/
fabric.util.object.extend(
  fabric.Object.prototype,
  /** @lends fabric.Object.prototype */ {
    /**
     * Returns true if object state (one of its state properties) was changed
     * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
     */
    hasStateChanged: function () {
      return this.stateProperties.some(function (prop) {
        return this.get(prop) !== this.originalState[prop];
      }, this);
    },

    /**
     * Saves state of an object
     * @param {Object} [options] Object with additional `stateProperties` array to include when saving state
     * @return {fabric.Object} thisArg
     */
    saveState: function (options) {
      this.stateProperties.forEach(function (prop) {
        this.originalState[prop] = this.get(prop);
      }, this);

      if (options && options.stateProperties) {
        options.stateProperties.forEach(function (prop) {
          this.originalState[prop] = this.get(prop);
        }, this);
      }

      return this;
    },

    /**
     * Setups state of an object
     * @return {fabric.Object} thisArg
     */
    setupState: function () {
      this.originalState = {};
      this.saveState();

      return this;
    }
  }
);

(function () {
  var degreesToRadians = fabric.util.degreesToRadians,
    isVML = function () {
      return typeof G_vmlCanvasManager !== "undefined";
    };

  fabric.util.object.extend(
    fabric.Object.prototype,
    /** @lends fabric.Object.prototype */ {
      /**
       * The object interactivity controls.
       * @private
       */
      _controlsVisibility: null,

      /**
       * Determines which corner has been clicked
       * @private
       * @param {Object} pointer The pointer indicating the mouse position
       * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
       */
      _findTargetCorner: function (pointer) {
        if (!this.hasControls || !this.active) {
          return false;
        }

        var ex = pointer.x,
          ey = pointer.y,
          xPoints,
          lines;

        for (var i in this.oCoords) {
          if (!this.isControlVisible(i)) {
            continue;
          }

          if (i === "mtr" && !this.hasRotatingPoint) {
            continue;
          }

          if (
            this.get("lockUniScaling") &&
            (i === "mt" || i === "mr" || i === "mb" || i === "ml")
          ) {
            continue;
          }

          lines = this._getImageLines(this.oCoords[i].corner);

          // debugging

          // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
          // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);

          // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
          // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);

          // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
          // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);

          // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
          // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);

          xPoints = this._findCrossPoints({ x: ex, y: ey }, lines);
          if (xPoints !== 0 && xPoints % 2 === 1) {
            this.__corner = i;
            return i;
          }
        }
        return false;
      },

      /**
       * Sets the coordinates of the draggable boxes in the corners of
       * the image used to scale/rotate it.
       * @private
       */
      _setCornerCoords: function () {
        var coords = this.oCoords,
          theta = degreesToRadians(this.angle),
          newTheta = degreesToRadians(45 - this.angle),
          cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2,
          cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
          sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
          sinTh = Math.sin(theta),
          cosTh = Math.cos(theta);

        coords.tl.corner = {
          tl: {
            x: coords.tl.x - sinHalfOffset,
            y: coords.tl.y - cosHalfOffset
          },
          tr: {
            x: coords.tl.x + cosHalfOffset,
            y: coords.tl.y - sinHalfOffset
          },
          bl: {
            x: coords.tl.x - cosHalfOffset,
            y: coords.tl.y + sinHalfOffset
          },
          br: {
            x: coords.tl.x + sinHalfOffset,
            y: coords.tl.y + cosHalfOffset
          }
        };

        coords.tr.corner = {
          tl: {
            x: coords.tr.x - sinHalfOffset,
            y: coords.tr.y - cosHalfOffset
          },
          tr: {
            x: coords.tr.x + cosHalfOffset,
            y: coords.tr.y - sinHalfOffset
          },
          br: {
            x: coords.tr.x + sinHalfOffset,
            y: coords.tr.y + cosHalfOffset
          },
          bl: {
            x: coords.tr.x - cosHalfOffset,
            y: coords.tr.y + sinHalfOffset
          }
        };

        coords.bl.corner = {
          tl: {
            x: coords.bl.x - sinHalfOffset,
            y: coords.bl.y - cosHalfOffset
          },
          bl: {
            x: coords.bl.x - cosHalfOffset,
            y: coords.bl.y + sinHalfOffset
          },
          br: {
            x: coords.bl.x + sinHalfOffset,
            y: coords.bl.y + cosHalfOffset
          },
          tr: {
            x: coords.bl.x + cosHalfOffset,
            y: coords.bl.y - sinHalfOffset
          }
        };

        coords.br.corner = {
          tr: {
            x: coords.br.x + cosHalfOffset,
            y: coords.br.y - sinHalfOffset
          },
          bl: {
            x: coords.br.x - cosHalfOffset,
            y: coords.br.y + sinHalfOffset
          },
          br: {
            x: coords.br.x + sinHalfOffset,
            y: coords.br.y + cosHalfOffset
          },
          tl: {
            x: coords.br.x - sinHalfOffset,
            y: coords.br.y - cosHalfOffset
          }
        };

        coords.ml.corner = {
          tl: {
            x: coords.ml.x - sinHalfOffset,
            y: coords.ml.y - cosHalfOffset
          },
          tr: {
            x: coords.ml.x + cosHalfOffset,
            y: coords.ml.y - sinHalfOffset
          },
          bl: {
            x: coords.ml.x - cosHalfOffset,
            y: coords.ml.y + sinHalfOffset
          },
          br: {
            x: coords.ml.x + sinHalfOffset,
            y: coords.ml.y + cosHalfOffset
          }
        };

        coords.mt.corner = {
          tl: {
            x: coords.mt.x - sinHalfOffset,
            y: coords.mt.y - cosHalfOffset
          },
          tr: {
            x: coords.mt.x + cosHalfOffset,
            y: coords.mt.y - sinHalfOffset
          },
          bl: {
            x: coords.mt.x - cosHalfOffset,
            y: coords.mt.y + sinHalfOffset
          },
          br: {
            x: coords.mt.x + sinHalfOffset,
            y: coords.mt.y + cosHalfOffset
          }
        };

        coords.mr.corner = {
          tl: {
            x: coords.mr.x - sinHalfOffset,
            y: coords.mr.y - cosHalfOffset
          },
          tr: {
            x: coords.mr.x + cosHalfOffset,
            y: coords.mr.y - sinHalfOffset
          },
          bl: {
            x: coords.mr.x - cosHalfOffset,
            y: coords.mr.y + sinHalfOffset
          },
          br: {
            x: coords.mr.x + sinHalfOffset,
            y: coords.mr.y + cosHalfOffset
          }
        };

        coords.mb.corner = {
          tl: {
            x: coords.mb.x - sinHalfOffset,
            y: coords.mb.y - cosHalfOffset
          },
          tr: {
            x: coords.mb.x + cosHalfOffset,
            y: coords.mb.y - sinHalfOffset
          },
          bl: {
            x: coords.mb.x - cosHalfOffset,
            y: coords.mb.y + sinHalfOffset
          },
          br: {
            x: coords.mb.x + sinHalfOffset,
            y: coords.mb.y + cosHalfOffset
          }
        };

        coords.mtr.corner = {
          tl: {
            x: coords.mtr.x - sinHalfOffset + sinTh * this.rotatingPointOffset,
            y: coords.mtr.y - cosHalfOffset - cosTh * this.rotatingPointOffset
          },
          tr: {
            x: coords.mtr.x + cosHalfOffset + sinTh * this.rotatingPointOffset,
            y: coords.mtr.y - sinHalfOffset - cosTh * this.rotatingPointOffset
          },
          bl: {
            x: coords.mtr.x - cosHalfOffset + sinTh * this.rotatingPointOffset,
            y: coords.mtr.y + sinHalfOffset - cosTh * this.rotatingPointOffset
          },
          br: {
            x: coords.mtr.x + sinHalfOffset + sinTh * this.rotatingPointOffset,
            y: coords.mtr.y + cosHalfOffset - cosTh * this.rotatingPointOffset
          }
        };
      },
      /**
       * Draws borders of an object's bounding box.
       * Requires public properties: width, height
       * Requires public options: padding, borderColor
       * @param {CanvasRenderingContext2D} ctx Context to draw on
       * @return {fabric.Object} thisArg
       * @chainable
       */
      drawBorders: function (ctx) {
        if (!this.hasBorders) return this;

        var padding = this.padding,
          padding2 = padding * 2,
          vpt = this.getViewportTransform();

        ctx.save();

        ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
        ctx.strokeStyle = this.borderColor;

        var scaleX = 1 / this._constrainScale(this.scaleX),
          scaleY = 1 / this._constrainScale(this.scaleY);

        ctx.lineWidth = 1 / this.borderScaleFactor;

        var w = this.getWidth(),
          h = this.getHeight(),
          strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0,
          capped =
            this.strokeLineCap === "round" || this.strokeLineCap === "square",
          vLine = this.type === "line" && this.width === 1,
          hLine = this.type === "line" && this.height === 1,
          strokeW = (capped && hLine) || this.type !== "line",
          strokeH = (capped && vLine) || this.type !== "line";
        if (vLine) {
          w = strokeWidth / scaleX;
        } else if (hLine) {
          h = strokeWidth / scaleY;
        }
        if (strokeW) {
          w += strokeWidth / scaleX;
        }
        if (strokeH) {
          h += strokeWidth / scaleY;
        }
        var wh = fabric.util.transformPoint(new fabric.Point(w, h), vpt, true),
          width = wh.x,
          height = wh.y;
        if (this.group) {
          width = width * this.group.scaleX;
          height = height * this.group.scaleY;
        }

        ctx.strokeRect(
          ~~(-(width / 2) - padding) - 0.5, // offset needed to make lines look sharper
          ~~(-(height / 2) - padding) - 0.5,
          ~~(width + padding2) + 1, // double offset needed to make lines look sharper
          ~~(height + padding2) + 1
        );

        if (
          this.hasRotatingPoint &&
          this.isControlVisible("mtr") &&
          !this.get("lockRotation") &&
          this.hasControls
        ) {
          var rotateHeight =
            (this.flipY ? height + padding * 2 : -height - padding * 2) / 2;

          ctx.beginPath();
          ctx.moveTo(0, rotateHeight);
          ctx.lineTo(
            0,
            rotateHeight +
              (this.flipY
                ? this.rotatingPointOffset
                : -this.rotatingPointOffset)
          );
          ctx.closePath();
          ctx.stroke();
        }

        ctx.restore();
        return this;
      },

      /**
       * Draws corners of an object's bounding box.
       * Requires public properties: width, height
       * Requires public options: cornerSize, padding
       * @param {CanvasRenderingContext2D} ctx Context to draw on
       * @return {fabric.Object} thisArg
       * @chainable
       */
      drawControls: function (ctx) {
        if (!this.hasControls) return this;

        var size = this.cornerSize,
          size2 = size / 2,
          vpt = this.getViewportTransform(),
          strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0,
          w = this.width,
          h = this.height,
          capped =
            this.strokeLineCap === "round" || this.strokeLineCap === "square",
          vLine = this.type === "line" && this.width === 1,
          hLine = this.type === "line" && this.height === 1,
          strokeW = (capped && hLine) || this.type !== "line",
          strokeH = (capped && vLine) || this.type !== "line";

        if (vLine) {
          w = strokeWidth;
        } else if (hLine) {
          h = strokeWidth;
        }
        if (strokeW) {
          w += strokeWidth;
        }
        if (strokeH) {
          h += strokeWidth;
        }
        w *= this.scaleX;
        h *= this.scaleY;

        var wh = fabric.util.transformPoint(new fabric.Point(w, h), vpt, true),
          width = wh.x,
          height = wh.y,
          left = -(width / 2),
          top = -(height / 2),
          padding = this.padding,
          scaleOffset = size2,
          scaleOffsetSize = size2 - size,
          methodName = this.transparentCorners ? "strokeRect" : "fillRect";

        ctx.save();

        ctx.lineWidth = 1;

        ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
        ctx.strokeStyle = ctx.fillStyle = this.cornerColor;

        // top-left
        this._drawControl(
          "tl",
          ctx,
          methodName,
          left - scaleOffset - padding,
          top - scaleOffset - padding
        );

        // top-right
        this._drawControl(
          "tr",
          ctx,
          methodName,
          left + width - scaleOffset + padding,
          top - scaleOffset - padding
        );

        // bottom-left
        this._drawControl(
          "bl",
          ctx,
          methodName,
          left - scaleOffset - padding,
          top + height + scaleOffsetSize + padding
        );

        // bottom-right
        this._drawControl(
          "br",
          ctx,
          methodName,
          left + width + scaleOffsetSize + padding,
          top + height + scaleOffsetSize + padding
        );

        if (!this.get("lockUniScaling")) {
          // middle-top
          this._drawControl(
            "mt",
            ctx,
            methodName,
            left + width / 2 - scaleOffset,
            top - scaleOffset - padding
          );

          // middle-bottom
          this._drawControl(
            "mb",
            ctx,
            methodName,
            left + width / 2 - scaleOffset,
            top + height + scaleOffsetSize + padding
          );

          // middle-right
          this._drawControl(
            "mr",
            ctx,
            methodName,
            left + width + scaleOffsetSize + padding,
            top + height / 2 - scaleOffset
          );

          // middle-left
          this._drawControl(
            "ml",
            ctx,
            methodName,
            left - scaleOffset - padding,
            top + height / 2 - scaleOffset
          );
        }

        // middle-top-rotate
        if (this.hasRotatingPoint) {
          this._drawControl(
            "mtr",
            ctx,
            methodName,
            left + width / 2 - scaleOffset,
            this.flipY
              ? top +
                  height +
                  this.rotatingPointOffset -
                  this.cornerSize / 2 +
                  padding
              : top - this.rotatingPointOffset - this.cornerSize / 2 - padding
          );
        }

        ctx.restore();

        return this;
      },

      /**
       * @private
       */
      _drawControl: function (control, ctx, methodName, left, top) {
        var size = this.cornerSize;

        if (this.isControlVisible(control)) {
          isVML() ||
            this.transparentCorners ||
            ctx.clearRect(left, top, size, size);
          ctx[methodName](left, top, size, size);
        }
      },

      /**
       * Returns true if the specified control is visible, false otherwise.
       * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
       * @returns {Boolean} true if the specified control is visible, false otherwise
       */
      isControlVisible: function (controlName) {
        return this._getControlsVisibility()[controlName];
      },

      /**
       * Sets the visibility of the specified control.
       * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
       * @param {Boolean} visible true to set the specified control visible, false otherwise
       * @return {fabric.Object} thisArg
       * @chainable
       */
      setControlVisible: function (controlName, visible) {
        this._getControlsVisibility()[controlName] = visible;
        return this;
      },

      /**
       * Sets the visibility state of object controls.
       * @param {Object} [options] Options object
       * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it
       * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it
       * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it
       * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it
       * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it
       * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it
       * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it
       * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it
       * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it
       * @return {fabric.Object} thisArg
       * @chainable
       */
      setControlsVisibility: function (options) {
        options || (options = {});

        for (var p in options) {
          this.setControlVisible(p, options[p]);
        }
        return this;
      },

      /**
       * Returns the instance of the control visibility set for this object.
       * @private
       * @returns {Object}
       */
      _getControlsVisibility: function () {
        if (!this._controlsVisibility) {
          this._controlsVisibility = {
            tl: true,
            tr: true,
            br: true,
            bl: true,
            ml: true,
            mt: true,
            mr: true,
            mb: true,
            mtr: true
          };
        }
        return this._controlsVisibility;
      }
    }
  );
})();

fabric.util.object.extend(
  fabric.StaticCanvas.prototype,
  /** @lends fabric.StaticCanvas.prototype */ {
    /**
     * Animation duration (in ms) for fx* methods
     * @type Number
     * @default
     */
    FX_DURATION: 500,

    /**
     * Centers object horizontally with animation.
     * @param {fabric.Object} object Object to center
     * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
     * @param {Function} [callbacks.onComplete] Invoked on completion
     * @param {Function} [callbacks.onChange] Invoked on every step of animation
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    fxCenterObjectH: function (object, callbacks) {
      callbacks = callbacks || {};

      var empty = function () {},
        onComplete = callbacks.onComplete || empty,
        onChange = callbacks.onChange || empty,
        _this = this;

      fabric.util.animate({
        startValue: object.get("left"),
        endValue: this.getCenter().left,
        duration: this.FX_DURATION,
        onChange: function (value) {
          object.set("left", value);
          _this.renderAll();
          onChange();
        },
        onComplete: function () {
          object.setCoords();
          onComplete();
        }
      });

      return this;
    },

    /**
     * Centers object vertically with animation.
     * @param {fabric.Object} object Object to center
     * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
     * @param {Function} [callbacks.onComplete] Invoked on completion
     * @param {Function} [callbacks.onChange] Invoked on every step of animation
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    fxCenterObjectV: function (object, callbacks) {
      callbacks = callbacks || {};

      var empty = function () {},
        onComplete = callbacks.onComplete || empty,
        onChange = callbacks.onChange || empty,
        _this = this;

      fabric.util.animate({
        startValue: object.get("top"),
        endValue: this.getCenter().top,
        duration: this.FX_DURATION,
        onChange: function (value) {
          object.set("top", value);
          _this.renderAll();
          onChange();
        },
        onComplete: function () {
          object.setCoords();
          onComplete();
        }
      });

      return this;
    },

    /**
     * Same as `fabric.Canvas#remove` but animated
     * @param {fabric.Object} object Object to remove
     * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
     * @param {Function} [callbacks.onComplete] Invoked on completion
     * @param {Function} [callbacks.onChange] Invoked on every step of animation
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    fxRemove: function (object, callbacks) {
      callbacks = callbacks || {};

      var empty = function () {},
        onComplete = callbacks.onComplete || empty,
        onChange = callbacks.onChange || empty,
        _this = this;

      fabric.util.animate({
        startValue: object.get("opacity"),
        endValue: 0,
        duration: this.FX_DURATION,
        onStart: function () {
          object.set("active", false);
        },
        onChange: function (value) {
          object.set("opacity", value);
          _this.renderAll();
          onChange();
        },
        onComplete: function () {
          _this.remove(object);
          onComplete();
        }
      });

      return this;
    }
  }
);

fabric.util.object.extend(
  fabric.Object.prototype,
  /** @lends fabric.Object.prototype */ {
    /**
     * Animates object's properties
     * @param {String|Object} property to animate (if string) or properties to animate (if object)
     * @param {Number|Object} value to animate property to (if string was given first) or options object
     * @return {fabric.Object} thisArg
     * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#animation}
     * @chainable
     *
     * As object — multiple properties
     *
     * object.animate({ left: ..., top: ... });
     * object.animate({ left: ..., top: ... }, { duration: ... });
     *
     * As string — one property
     *
     * object.animate('left', ...);
     * object.animate('left', { duration: ... });
     *
     */
    animate: function () {
      if (arguments[0] && typeof arguments[0] === "object") {
        var propsToAnimate = [],
          prop,
          skipCallbacks;
        for (prop in arguments[0]) {
          propsToAnimate.push(prop);
        }
        for (var i = 0, len = propsToAnimate.length; i < len; i++) {
          prop = propsToAnimate[i];
          skipCallbacks = i !== len - 1;
          this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);
        }
      } else {
        this._animate.apply(this, arguments);
      }
      return this;
    },

    /**
     * @private
     * @param {String} property Property to animate
     * @param {String} to Value to animate to
     * @param {Object} [options] Options object
     * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked
     */
    _animate: function (property, to, options, skipCallbacks) {
      var _this = this,
        propPair;

      to = to.toString();

      if (!options) {
        options = {};
      } else {
        options = fabric.util.object.clone(options);
      }

      if (~property.indexOf(".")) {
        propPair = property.split(".");
      }

      var currentValue = propPair
        ? this.get(propPair[0])[propPair[1]]
        : this.get(property);

      if (!("from" in options)) {
        options.from = currentValue;
      }

      if (~to.indexOf("=")) {
        to = currentValue + parseFloat(to.replace("=", ""));
      } else {
        to = parseFloat(to);
      }

      fabric.util.animate({
        startValue: options.from,
        endValue: to,
        byValue: options.by,
        easing: options.easing,
        duration: options.duration,
        abort:
          options.abort &&
          function () {
            return options.abort.call(_this);
          },
        onChange: function (value) {
          if (propPair) {
            _this[propPair[0]][propPair[1]] = value;
          } else {
            _this.set(property, value);
          }
          if (skipCallbacks) return;
          options.onChange && options.onChange();
        },
        onComplete: function () {
          if (skipCallbacks) return;

          _this.setCoords();
          options.onComplete && options.onComplete();
        }
      });
    }
  }
);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend,
    coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 },
    supportsLineDash = fabric.StaticCanvas.supports("setLineDash");

  if (fabric.Line) {
    fabric.warn("fabric.Line is already defined");
    return;
  }

  /**
   * Line class
   * @class fabric.Line
   * @extends fabric.Object
   * @see {@link fabric.Line#initialize} for constructor definition
   */
  fabric.Line = fabric.util.createClass(
    fabric.Object,
    /** @lends fabric.Line.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "line",

      /**
       * x value or first line edge
       * @type Number
       * @default
       */
      x1: 0,

      /**
       * y value or first line edge
       * @type Number
       * @default
       */
      y1: 0,

      /**
       * x value or second line edge
       * @type Number
       * @default
       */
      x2: 0,

      /**
       * y value or second line edge
       * @type Number
       * @default
       */
      y2: 0,

      /**
       * Constructor
       * @param {Array} [points] Array of points
       * @param {Object} [options] Options object
       * @return {fabric.Line} thisArg
       */
      initialize: function (points, options) {
        options = options || {};

        if (!points) {
          points = [0, 0, 0, 0];
        }

        this.callSuper("initialize", options);

        this.set("x1", points[0]);
        this.set("y1", points[1]);
        this.set("x2", points[2]);
        this.set("y2", points[3]);

        this._setWidthHeight(options);
      },

      /**
       * @private
       * @param {Object} [options] Options
       */
      _setWidthHeight: function (options) {
        options || (options = {});

        this.width = Math.abs(this.x2 - this.x1) || 1;
        this.height = Math.abs(this.y2 - this.y1) || 1;

        this.left = "left" in options ? options.left : this._getLeftToOriginX();

        this.top = "top" in options ? options.top : this._getTopToOriginY();
      },

      /**
       * @private
       * @param {String} key
       * @param {Any} value
       */
      _set: function (key, value) {
        this[key] = value;
        if (typeof coordProps[key] !== "undefined") {
          this._setWidthHeight();
        }
        return this;
      },

      /**
       * @private
       * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line.
       */
      _getLeftToOriginX: makeEdgeToOriginGetter(
        {
          // property names
          origin: "originX",
          axis1: "x1",
          axis2: "x2",
          dimension: "width"
        },
        {
          // possible values of origin
          nearest: "left",
          center: "center",
          farthest: "right"
        }
      ),

      /**
       * @private
       * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line.
       */
      _getTopToOriginY: makeEdgeToOriginGetter(
        {
          // property names
          origin: "originY",
          axis1: "y1",
          axis2: "y2",
          dimension: "height"
        },
        {
          // possible values of origin
          nearest: "top",
          center: "center",
          farthest: "bottom"
        }
      ),

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function (ctx) {
        ctx.beginPath();

        var isInPathGroup = this.group && this.group.type === "path-group";
        if (isInPathGroup) {
          //  Line coords are distances from left-top of canvas to origin of line.
          //
          //  To render line in a path-group, we need to translate them to
          //  distances from center of path-group to center of line.
          var cp = this.getCenterPoint();
          ctx.translate(cp.x, cp.y);
          if (!this.transformMatrix)
            ctx.translate(-this.group.width / 2, -this.group.height / 2);
        }

        if (
          !this.strokeDashArray ||
          (this.strokeDashArray && supportsLineDash)
        ) {
          // move from center (of virtual box) to its left/top corner
          // we can't assume x1, y1 is top left and x2, y2 is bottom right
          var xMult = this.x1 <= this.x2 ? -1 : 1,
            yMult = this.y1 <= this.y2 ? -1 : 1;

          ctx.moveTo(
            this.width === 1 ? 0 : (xMult * this.width) / 2,
            this.height === 1 ? 0 : (yMult * this.height) / 2
          );

          ctx.lineTo(
            this.width === 1 ? 0 : (xMult * -1 * this.width) / 2,
            this.height === 1 ? 0 : (yMult * -1 * this.height) / 2
          );
        }

        ctx.lineWidth = this.strokeWidth;

        // TODO: test this
        // make sure setting "fill" changes color of a line
        // (by copying fillStyle to strokeStyle, since line is stroked, not filled)
        var origStrokeStyle = ctx.strokeStyle;
        ctx.strokeStyle = this.stroke || ctx.fillStyle;
        this.stroke && this._renderStroke(ctx);
        ctx.strokeStyle = origStrokeStyle;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderDashedStroke: function (ctx) {
        var xMult = this.x1 <= this.x2 ? -1 : 1,
          yMult = this.y1 <= this.y2 ? -1 : 1,
          x = this.width === 1 ? 0 : (xMult * this.width) / 2,
          y = this.height === 1 ? 0 : (yMult * this.height) / 2;

        ctx.beginPath();
        fabric.util.drawDashedLine(ctx, x, y, -x, -y, this.strokeDashArray);
        ctx.closePath();
      },

      /**
       * Returns object representation of an instance
       * @methd toObject
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        return extend(this.callSuper("toObject", propertiesToInclude), {
          x1: this.get("x1"),
          y1: this.get("y1"),
          x2: this.get("x2"),
          y2: this.get("y2")
        });
      },

      /* _TO_SVG_START_ */
      /**
       * Returns SVG representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var markup = this._createBaseSVGMarkup();

        markup.push(
          "<line ",
          'x1="',
          this.get("x1"),
          '" y1="',
          this.get("y1"),
          '" x2="',
          this.get("x2"),
          '" y2="',
          this.get("y2"),
          '" style="',
          this.getSvgStyles(),
          '"/>'
        );

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * Returns complexity of an instance
       * @return {Number} complexity
       */
      complexity: function () {
        return 1;
      }
    }
  );

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement})
   * @static
   * @memberOf fabric.Line
   * @see http://www.w3.org/TR/SVG/shapes.html#LineElement
   */
  fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(
    "x1 y1 x2 y2".split(" ")
  );

  /**
   * Returns fabric.Line instance from an SVG element
   * @static
   * @memberOf fabric.Line
   * @param {SVGElement} element Element to parse
   * @param {Object} [options] Options object
   * @return {fabric.Line} instance of fabric.Line
   */
  fabric.Line.fromElement = function (element, options) {
    var parsedAttributes = fabric.parseAttributes(
        element,
        fabric.Line.ATTRIBUTE_NAMES
      ),
      points = [
        parsedAttributes.x1 || 0,
        parsedAttributes.y1 || 0,
        parsedAttributes.x2 || 0,
        parsedAttributes.y2 || 0
      ];
    return new fabric.Line(points, extend(parsedAttributes, options));
  };
  /* _FROM_SVG_END_ */

  /**
   * Returns fabric.Line instance from an object representation
   * @static
   * @memberOf fabric.Line
   * @param {Object} object Object to create an instance from
   * @return {fabric.Line} instance of fabric.Line
   */
  fabric.Line.fromObject = function (object) {
    var points = [object.x1, object.y1, object.x2, object.y2];
    return new fabric.Line(points, object);
  };

  /**
   * Produces a function that calculates distance from canvas edge to Line origin.
   */
  function makeEdgeToOriginGetter(propertyNames, originValues) {
    var origin = propertyNames.origin,
      axis1 = propertyNames.axis1,
      axis2 = propertyNames.axis2,
      dimension = propertyNames.dimension,
      nearest = originValues.nearest,
      center = originValues.center,
      farthest = originValues.farthest;

    return function () {
      switch (this.get(origin)) {
        case nearest:
          return Math.min(this.get(axis1), this.get(axis2));
        case center:
          return (
            Math.min(this.get(axis1), this.get(axis2)) +
            0.5 * this.get(dimension)
          );
        case farthest:
          return Math.max(this.get(axis1), this.get(axis2));
      }
    };
  }
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    piBy2 = Math.PI * 2,
    extend = fabric.util.object.extend;

  if (fabric.Circle) {
    fabric.warn("fabric.Circle is already defined.");
    return;
  }

  /**
   * Circle class
   * @class fabric.Circle
   * @extends fabric.Object
   * @see {@link fabric.Circle#initialize} for constructor definition
   */
  fabric.Circle = fabric.util.createClass(
    fabric.Object,
    /** @lends fabric.Circle.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "circle",

      /**
       * Radius of this circle
       * @type Number
       * @default
       */
      radius: 0,

      /**
       * Constructor
       * @param {Object} [options] Options object
       * @return {fabric.Circle} thisArg
       */
      initialize: function (options) {
        options = options || {};

        this.set("radius", options.radius || 0);
        this.callSuper("initialize", options);
      },

      /**
       * @private
       * @param {String} key
       * @param {Any} value
       * @return {fabric.Circle} thisArg
       */
      _set: function (key, value) {
        this.callSuper("_set", key, value);

        if (key === "radius") {
          this.setRadius(value);
        }

        return this;
      },

      /**
       * Returns object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        return extend(this.callSuper("toObject", propertiesToInclude), {
          radius: this.get("radius")
        });
      },

      /* _TO_SVG_START_ */
      /**
       * Returns svg representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var markup = this._createBaseSVGMarkup();

        markup.push(
          "<circle ",
          'cx="0" cy="0" ',
          'r="',
          this.radius,
          '" style="',
          this.getSvgStyles(),
          '" transform="',
          this.getSvgTransform(),
          '"/>'
        );

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx context to render on
       * @param {Boolean} [noTransform] When true, context is not transformed
       */
      _render: function (ctx, noTransform) {
        ctx.beginPath();
        // multiply by currently set alpha (the one that was set by path group where this object is contained, for example)
        ctx.globalAlpha = this.group
          ? ctx.globalAlpha * this.opacity
          : this.opacity;
        ctx.arc(
          noTransform ? this.left : 0,
          noTransform ? this.top : 0,
          this.radius,
          0,
          piBy2,
          false
        );
        this._renderFill(ctx);
        this.stroke && this._renderStroke(ctx);
      },

      /**
       * Returns horizontal radius of an object (according to how an object is scaled)
       * @return {Number}
       */
      getRadiusX: function () {
        return this.get("radius") * this.get("scaleX");
      },

      /**
       * Returns vertical radius of an object (according to how an object is scaled)
       * @return {Number}
       */
      getRadiusY: function () {
        return this.get("radius") * this.get("scaleY");
      },

      /**
       * Sets radius of an object (and updates width accordingly)
       * @return {Number}
       */
      setRadius: function (value) {
        this.radius = value;
        this.set("width", value * 2).set("height", value * 2);
      },

      /**
       * Returns complexity of an instance
       * @return {Number} complexity of this instance
       */
      complexity: function () {
        return 1;
      }
    }
  );

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement})
   * @static
   * @memberOf fabric.Circle
   * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement
   */
  fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(
    "cx cy r".split(" ")
  );

  /**
   * Returns {@link fabric.Circle} instance from an SVG element
   * @static
   * @memberOf fabric.Circle
   * @param {SVGElement} element Element to parse
   * @param {Object} [options] Options object
   * @throws {Error} If value of `r` attribute is missing or invalid
   * @return {fabric.Circle} Instance of fabric.Circle
   */
  fabric.Circle.fromElement = function (element, options) {
    options || (options = {});

    var parsedAttributes = fabric.parseAttributes(
      element,
      fabric.Circle.ATTRIBUTE_NAMES
    );

    if (!isValidRadius(parsedAttributes)) {
      throw new Error(
        "value of `r` attribute is required and can not be negative"
      );
    }

    if (!("left" in parsedAttributes)) {
      parsedAttributes.left = 0;
    }
    if (!("top" in parsedAttributes)) {
      parsedAttributes.top = 0;
    }
    if (!("transformMatrix" in parsedAttributes)) {
      parsedAttributes.left -= options.width ? options.width / 2 : 0;
      parsedAttributes.top -= options.height ? options.height / 2 : 0;
    }

    var obj = new fabric.Circle(extend(parsedAttributes, options));

    obj.cx = parseFloat(element.getAttribute("cx")) || 0;
    obj.cy = parseFloat(element.getAttribute("cy")) || 0;

    return obj;
  };

  /**
   * @private
   */
  function isValidRadius(attributes) {
    return "radius" in attributes && attributes.radius > 0;
  }
  /* _FROM_SVG_END_ */

  /**
   * Returns {@link fabric.Circle} instance from an object representation
   * @static
   * @memberOf fabric.Circle
   * @param {Object} object Object to create an instance from
   * @return {Object} Instance of fabric.Circle
   */
  fabric.Circle.fromObject = function (object) {
    return new fabric.Circle(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {});

  if (fabric.Triangle) {
    fabric.warn("fabric.Triangle is already defined");
    return;
  }

  /**
   * Triangle class
   * @class fabric.Triangle
   * @extends fabric.Object
   * @return {fabric.Triangle} thisArg
   * @see {@link fabric.Triangle#initialize} for constructor definition
   */
  fabric.Triangle = fabric.util.createClass(
    fabric.Object,
    /** @lends fabric.Triangle.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "triangle",

      /**
       * Constructor
       * @param {Object} [options] Options object
       * @return {Object} thisArg
       */
      initialize: function (options) {
        options = options || {};

        this.callSuper("initialize", options);

        this.set("width", options.width || 100).set(
          "height",
          options.height || 100
        );
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function (ctx) {
        var widthBy2 = this.width / 2,
          heightBy2 = this.height / 2;

        ctx.beginPath();
        ctx.moveTo(-widthBy2, heightBy2);
        ctx.lineTo(0, -heightBy2);
        ctx.lineTo(widthBy2, heightBy2);
        ctx.closePath();

        this._renderFill(ctx);
        this._renderStroke(ctx);
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderDashedStroke: function (ctx) {
        var widthBy2 = this.width / 2,
          heightBy2 = this.height / 2;

        ctx.beginPath();
        fabric.util.drawDashedLine(
          ctx,
          -widthBy2,
          heightBy2,
          0,
          -heightBy2,
          this.strokeDashArray
        );
        fabric.util.drawDashedLine(
          ctx,
          0,
          -heightBy2,
          widthBy2,
          heightBy2,
          this.strokeDashArray
        );
        fabric.util.drawDashedLine(
          ctx,
          widthBy2,
          heightBy2,
          -widthBy2,
          heightBy2,
          this.strokeDashArray
        );
        ctx.closePath();
      },

      /* _TO_SVG_START_ */
      /**
       * Returns SVG representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var markup = this._createBaseSVGMarkup(),
          widthBy2 = this.width / 2,
          heightBy2 = this.height / 2,
          points = [
            -widthBy2 + " " + heightBy2,
            "0 " + -heightBy2,
            widthBy2 + " " + heightBy2
          ].join(",");

        markup.push(
          "<polygon ",
          'points="',
          points,
          '" style="',
          this.getSvgStyles(),
          '" transform="',
          this.getSvgTransform(),
          '"/>'
        );

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * Returns complexity of an instance
       * @return {Number} complexity of this instance
       */
      complexity: function () {
        return 1;
      }
    }
  );

  /**
   * Returns fabric.Triangle instance from an object representation
   * @static
   * @memberOf fabric.Triangle
   * @param {Object} object Object to create an instance from
   * @return {Object} instance of Canvas.Triangle
   */
  fabric.Triangle.fromObject = function (object) {
    return new fabric.Triangle(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    piBy2 = Math.PI * 2,
    extend = fabric.util.object.extend;

  if (fabric.Ellipse) {
    fabric.warn("fabric.Ellipse is already defined.");
    return;
  }

  /**
   * Ellipse class
   * @class fabric.Ellipse
   * @extends fabric.Object
   * @return {fabric.Ellipse} thisArg
   * @see {@link fabric.Ellipse#initialize} for constructor definition
   */
  fabric.Ellipse = fabric.util.createClass(
    fabric.Object,
    /** @lends fabric.Ellipse.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "ellipse",

      /**
       * Horizontal radius
       * @type Number
       * @default
       */
      rx: 0,

      /**
       * Vertical radius
       * @type Number
       * @default
       */
      ry: 0,

      /**
       * Constructor
       * @param {Object} [options] Options object
       * @return {fabric.Ellipse} thisArg
       */
      initialize: function (options) {
        options = options || {};

        this.callSuper("initialize", options);

        this.set("rx", options.rx || 0);
        this.set("ry", options.ry || 0);

        this.set("width", this.get("rx") * 2);
        this.set("height", this.get("ry") * 2);
      },

      /**
       * Returns object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        return extend(this.callSuper("toObject", propertiesToInclude), {
          rx: this.get("rx"),
          ry: this.get("ry")
        });
      },

      /* _TO_SVG_START_ */
      /**
       * Returns svg representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var markup = this._createBaseSVGMarkup();

        markup.push(
          "<ellipse ",
          'rx="',
          this.get("rx"),
          '" ry="',
          this.get("ry"),
          '" style="',
          this.getSvgStyles(),
          '" transform="',
          this.getSvgTransform(),
          '"/>'
        );

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * Renders this instance on a given context
       * @param {CanvasRenderingContext2D} ctx context to render on
       * @param {Boolean} [noTransform] When true, context is not transformed
       */
      render: function (ctx, noTransform) {
        // do not use `get` for perf. reasons
        if (this.rx === 0 || this.ry === 0) return;
        return this.callSuper("render", ctx, noTransform);
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx context to render on
       * @param {Boolean} [noTransform] When true, context is not transformed
       */
      _render: function (ctx, noTransform) {
        ctx.beginPath();
        ctx.globalAlpha = this.group
          ? ctx.globalAlpha * this.opacity
          : this.opacity;
        ctx.save();
        ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0);
        ctx.arc(
          noTransform ? this.left : 0,
          noTransform ? (this.top * this.rx) / this.ry : 0,
          this.rx,
          0,
          piBy2,
          false
        );
        ctx.restore();
        this._renderFill(ctx);
        this._renderStroke(ctx);
      },

      /**
       * Returns complexity of an instance
       * @return {Number} complexity
       */
      complexity: function () {
        return 1;
      }
    }
  );

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement})
   * @static
   * @memberOf fabric.Ellipse
   * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement
   */
  fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(
    "cx cy rx ry".split(" ")
  );

  /**
   * Returns {@link fabric.Ellipse} instance from an SVG element
   * @static
   * @memberOf fabric.Ellipse
   * @param {SVGElement} element Element to parse
   * @param {Object} [options] Options object
   * @return {fabric.Ellipse}
   */
  fabric.Ellipse.fromElement = function (element, options) {
    options || (options = {});

    var parsedAttributes = fabric.parseAttributes(
      element,
      fabric.Ellipse.ATTRIBUTE_NAMES
    );

    if (!("left" in parsedAttributes)) {
      parsedAttributes.left = 0;
    }
    if (!("top" in parsedAttributes)) {
      parsedAttributes.top = 0;
    }
    if (!("transformMatrix" in parsedAttributes)) {
      parsedAttributes.left -= options.width ? options.width / 2 : 0;
      parsedAttributes.top -= options.height ? options.height / 2 : 0;
    }
    var ellipse = new fabric.Ellipse(extend(parsedAttributes, options));

    ellipse.cx = parseFloat(element.getAttribute("cx")) || 0;
    ellipse.cy = parseFloat(element.getAttribute("cy")) || 0;

    return ellipse;
  };
  /* _FROM_SVG_END_ */

  /**
   * Returns {@link fabric.Ellipse} instance from an object representation
   * @static
   * @memberOf fabric.Ellipse
   * @param {Object} object Object to create an instance from
   * @return {fabric.Ellipse}
   */
  fabric.Ellipse.fromObject = function (object) {
    return new fabric.Ellipse(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  if (fabric.Rect) {
    console.warn("fabric.Rect is already defined");
    return;
  }

  var stateProperties = fabric.Object.prototype.stateProperties.concat();
  stateProperties.push("rx", "ry", "x", "y");

  /**
   * Rectangle class
   * @class fabric.Rect
   * @extends fabric.Object
   * @return {fabric.Rect} thisArg
   * @see {@link fabric.Rect#initialize} for constructor definition
   */
  fabric.Rect = fabric.util.createClass(
    fabric.Object,
    /** @lends fabric.Rect.prototype */ {
      /**
       * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged})
       * as well as for history (undo/redo) purposes
       * @type Array
       */
      stateProperties: stateProperties,

      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "rect",

      /**
       * Horizontal border radius
       * @type Number
       * @default
       */
      rx: 0,

      /**
       * Vertical border radius
       * @type Number
       * @default
       */
      ry: 0,

      /**
       * @type Number
       * @default
       */
      x: 0,

      /**
       * @type Number
       * @default
       */
      y: 0,

      /**
       * Used to specify dash pattern for stroke on this object
       * @type Array
       */
      strokeDashArray: null,

      /**
       * Constructor
       * @param {Object} [options] Options object
       * @return {Object} thisArg
       */
      initialize: function (options) {
        options = options || {};

        this.callSuper("initialize", options);
        this._initRxRy();

        this.x = options.x || 0;
        this.y = options.y || 0;
      },

      /**
       * Initializes rx/ry attributes
       * @private
       */
      _initRxRy: function () {
        if (this.rx && !this.ry) {
          this.ry = this.rx;
        } else if (this.ry && !this.rx) {
          this.rx = this.ry;
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function (ctx) {
        // optimize 1x1 case (used in spray brush)
        if (this.width === 1 && this.height === 1) {
          ctx.fillRect(0, 0, 1, 1);
          return;
        }

        var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0,
          ry = this.ry ? Math.min(this.ry, this.height / 2) : 0,
          w = this.width,
          h = this.height,
          x = -w / 2,
          y = -h / 2,
          isInPathGroup = this.group && this.group.type === "path-group",
          isRounded = rx !== 0 || ry !== 0,
          k =
            1 -
            0.5522847498; /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */

        ctx.beginPath();
        ctx.globalAlpha = isInPathGroup
          ? ctx.globalAlpha * this.opacity
          : this.opacity;

        if (this.transformMatrix && isInPathGroup) {
          ctx.translate(this.width / 2 + this.x, this.height / 2 + this.y);
        }
        if (!this.transformMatrix && isInPathGroup) {
          ctx.translate(
            -this.group.width / 2 + this.width / 2 + this.x,
            -this.group.height / 2 + this.height / 2 + this.y
          );
        }

        ctx.moveTo(x + rx, y);

        ctx.lineTo(x + w - rx, y);
        isRounded &&
          ctx.bezierCurveTo(
            x + w - k * rx,
            y,
            x + w,
            y + k * ry,
            x + w,
            y + ry
          );

        ctx.lineTo(x + w, y + h - ry);
        isRounded &&
          ctx.bezierCurveTo(
            x + w,
            y + h - k * ry,
            x + w - k * rx,
            y + h,
            x + w - rx,
            y + h
          );

        ctx.lineTo(x + rx, y + h);
        isRounded &&
          ctx.bezierCurveTo(
            x + k * rx,
            y + h,
            x,
            y + h - k * ry,
            x,
            y + h - ry
          );

        ctx.lineTo(x, y + ry);
        isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y);

        ctx.closePath();

        this._renderFill(ctx);
        this._renderStroke(ctx);
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderDashedStroke: function (ctx) {
        var x = -this.width / 2,
          y = -this.height / 2,
          w = this.width,
          h = this.height;

        ctx.beginPath();
        fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);
        fabric.util.drawDashedLine(
          ctx,
          x + w,
          y,
          x + w,
          y + h,
          this.strokeDashArray
        );
        fabric.util.drawDashedLine(
          ctx,
          x + w,
          y + h,
          x,
          y + h,
          this.strokeDashArray
        );
        fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);
        ctx.closePath();
      },

      /**
       * Since coordinate system differs from that of SVG
       * @private
       */
      _normalizeLeftTopProperties: function (parsedAttributes) {
        if ("left" in parsedAttributes) {
          this.set("left", parsedAttributes.left + this.getWidth() / 2);
        }
        this.set("x", parsedAttributes.left || 0);
        if ("top" in parsedAttributes) {
          this.set("top", parsedAttributes.top + this.getHeight() / 2);
        }
        this.set("y", parsedAttributes.top || 0);
        return this;
      },

      /**
       * Returns object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        var object = extend(this.callSuper("toObject", propertiesToInclude), {
          rx: this.get("rx") || 0,
          ry: this.get("ry") || 0,
          x: this.get("x"),
          y: this.get("y")
        });
        if (!this.includeDefaultValues) {
          this._removeDefaultValues(object);
        }
        return object;
      },

      /* _TO_SVG_START_ */
      /**
       * Returns svg representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var markup = this._createBaseSVGMarkup();

        markup.push(
          "<rect ",
          'x="',
          (-1 * this.width) / 2,
          '" y="',
          (-1 * this.height) / 2,
          '" rx="',
          this.get("rx"),
          '" ry="',
          this.get("ry"),
          '" width="',
          this.width,
          '" height="',
          this.height,
          '" style="',
          this.getSvgStyles(),
          '" transform="',
          this.getSvgTransform(),
          '"/>'
        );

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * Returns complexity of an instance
       * @return {Number} complexity
       */
      complexity: function () {
        return 1;
      }
    }
  );

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`)
   * @static
   * @memberOf fabric.Rect
   * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement
   */
  fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(
    "x y rx ry width height".split(" ")
  );

  /**
   * @private
   */
  function _setDefaultLeftTopValues(attributes) {
    attributes.left = attributes.left || 0;
    attributes.top = attributes.top || 0;
    return attributes;
  }

  /**
   * Returns {@link fabric.Rect} instance from an SVG element
   * @static
   * @memberOf fabric.Rect
   * @param {SVGElement} element Element to parse
   * @param {Object} [options] Options object
   * @return {fabric.Rect} Instance of fabric.Rect
   */
  fabric.Rect.fromElement = function (element, options) {
    if (!element) {
      return null;
    }

    var parsedAttributes = fabric.parseAttributes(
      element,
      fabric.Rect.ATTRIBUTE_NAMES
    );
    parsedAttributes = _setDefaultLeftTopValues(parsedAttributes);

    var rect = new fabric.Rect(
      extend(options ? fabric.util.object.clone(options) : {}, parsedAttributes)
    );
    rect._normalizeLeftTopProperties(parsedAttributes);

    return rect;
  };
  /* _FROM_SVG_END_ */

  /**
   * Returns {@link fabric.Rect} instance from an object representation
   * @static
   * @memberOf fabric.Rect
   * @param {Object} object Object to create an instance from
   * @return {Object} instance of fabric.Rect
   */
  fabric.Rect.fromObject = function (object) {
    return new fabric.Rect(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    toFixed = fabric.util.toFixed;

  if (fabric.Polyline) {
    fabric.warn("fabric.Polyline is already defined");
    return;
  }

  /**
   * Polyline class
   * @class fabric.Polyline
   * @extends fabric.Object
   * @see {@link fabric.Polyline#initialize} for constructor definition
   */
  fabric.Polyline = fabric.util.createClass(
    fabric.Object,
    /** @lends fabric.Polyline.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "polyline",

      /**
       * Points array
       * @type Array
       * @default
       */
      points: null,

      /**
       * Constructor
       * @param {Array} points Array of points (where each point is an object with x and y)
       * @param {Object} [options] Options object
       * @param {Boolean} [skipOffset] Whether points offsetting should be skipped
       * @return {fabric.Polyline} thisArg
       * @example
       * var poly = new fabric.Polyline([
       *     { x: 10, y: 10 },
       *     { x: 50, y: 30 },
       *     { x: 40, y: 70 },
       *     { x: 60, y: 50 },
       *     { x: 100, y: 150 },
       *     { x: 40, y: 100 }
       *   ], {
       *   stroke: 'red',
       *   left: 100,
       *   top: 100
       * });
       */
      initialize: function (points, options, skipOffset) {
        options = options || {};
        this.set("points", points);
        this.callSuper("initialize", options);
        this._calcDimensions(skipOffset);
      },

      /**
       * @private
       * @param {Boolean} [skipOffset] Whether points offsetting should be skipped
       */
      _calcDimensions: function (skipOffset) {
        return fabric.Polygon.prototype._calcDimensions.call(this, skipOffset);
      },

      /**
       * Returns object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} Object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        return fabric.Polygon.prototype.toObject.call(
          this,
          propertiesToInclude
        );
      },

      /* _TO_SVG_START_ */
      /**
       * Returns SVG representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var points = [],
          markup = this._createBaseSVGMarkup();

        for (var i = 0, len = this.points.length; i < len; i++) {
          points.push(
            toFixed(this.points[i].x, 2),
            ",",
            toFixed(this.points[i].y, 2),
            " "
          );
        }

        markup.push(
          "<polyline ",
          'points="',
          points.join(""),
          '" style="',
          this.getSvgStyles(),
          '" transform="',
          this.getSvgTransform(),
          '"/>'
        );

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function (ctx) {
        var point;
        ctx.beginPath();
        ctx.moveTo(this.points[0].x, this.points[0].y);
        for (var i = 0, len = this.points.length; i < len; i++) {
          point = this.points[i];
          ctx.lineTo(point.x, point.y);
        }

        this._renderFill(ctx);
        this._renderStroke(ctx);
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderDashedStroke: function (ctx) {
        var p1, p2;

        ctx.beginPath();
        for (var i = 0, len = this.points.length; i < len; i++) {
          p1 = this.points[i];
          p2 = this.points[i + 1] || p1;
          fabric.util.drawDashedLine(
            ctx,
            p1.x,
            p1.y,
            p2.x,
            p2.y,
            this.strokeDashArray
          );
        }
      },

      /**
       * Returns complexity of an instance
       * @return {Number} complexity of this instance
       */
      complexity: function () {
        return this.get("points").length;
      }
    }
  );

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement})
   * @static
   * @memberOf fabric.Polyline
   * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement
   */
  fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();

  /**
   * Returns fabric.Polyline instance from an SVG element
   * @static
   * @memberOf fabric.Polyline
   * @param {SVGElement} element Element to parse
   * @param {Object} [options] Options object
   * @return {fabric.Polyline} Instance of fabric.Polyline
   */
  fabric.Polyline.fromElement = function (element, options) {
    if (!element) {
      return null;
    }
    options || (options = {});

    var points = fabric.parsePointsAttribute(element.getAttribute("points")),
      parsedAttributes = fabric.parseAttributes(
        element,
        fabric.Polyline.ATTRIBUTE_NAMES
      );

    if (!("transformMatrix" in parsedAttributes)) {
      fabric.util.normalizePoints(points, options);
    }
    return new fabric.Polyline(
      points,
      fabric.util.object.extend(parsedAttributes, options),
      true
    );
  };
  /* _FROM_SVG_END_ */

  /**
   * Returns fabric.Polyline instance from an object representation
   * @static
   * @memberOf fabric.Polyline
   * @param {Object} object Object to create an instance from
   * @return {fabric.Polyline} Instance of fabric.Polyline
   */
  fabric.Polyline.fromObject = function (object) {
    var points = object.points;
    return new fabric.Polyline(points, object, true);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend,
    min = fabric.util.array.min,
    max = fabric.util.array.max,
    toFixed = fabric.util.toFixed;

  if (fabric.Polygon) {
    fabric.warn("fabric.Polygon is already defined");
    return;
  }

  /**
   * Polygon class
   * @class fabric.Polygon
   * @extends fabric.Object
   * @see {@link fabric.Polygon#initialize} for constructor definition
   */
  fabric.Polygon = fabric.util.createClass(
    fabric.Object,
    /** @lends fabric.Polygon.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "polygon",

      /**
       * Points array
       * @type Array
       * @default
       */
      points: null,

      /**
       * Constructor
       * @param {Array} points Array of points
       * @param {Object} [options] Options object
       * @param {Boolean} [skipOffset] Whether points offsetting should be skipped
       * @return {fabric.Polygon} thisArg
       */
      initialize: function (points, options, skipOffset) {
        options = options || {};
        this.points = points;
        this.callSuper("initialize", options);
        this._calcDimensions(skipOffset);
      },

      /**
       * @private
       * @param {Boolean} [skipOffset] Whether points offsetting should be skipped
       */
      _calcDimensions: function (skipOffset) {
        var points = this.points,
          minX = min(points, "x"),
          minY = min(points, "y"),
          maxX = max(points, "x"),
          maxY = max(points, "y");

        this.width = maxX - minX || 1;
        this.height = maxY - minY || 1;

        this.minX = minX;
        this.minY = minY;

        if (skipOffset) return;

        var halfWidth = this.width / 2 + this.minX,
          halfHeight = this.height / 2 + this.minY;

        // change points to offset polygon into a bounding box
        this.points.forEach(function (p) {
          p.x -= halfWidth;
          p.y -= halfHeight;
        }, this);
      },

      /**
       * Returns object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} Object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        return extend(this.callSuper("toObject", propertiesToInclude), {
          points: this.points.concat()
        });
      },

      /* _TO_SVG_START_ */
      /**
       * Returns svg representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var points = [],
          markup = this._createBaseSVGMarkup();

        for (var i = 0, len = this.points.length; i < len; i++) {
          points.push(
            toFixed(this.points[i].x, 2),
            ",",
            toFixed(this.points[i].y, 2),
            " "
          );
        }

        markup.push(
          "<polygon ",
          'points="',
          points.join(""),
          '" style="',
          this.getSvgStyles(),
          '" transform="',
          this.getSvgTransform(),
          '"/>'
        );

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function (ctx) {
        var point;
        ctx.beginPath();
        ctx.globalAlpha = this.group
          ? ctx.globalAlpha * this.opacity
          : this.opacity;
        ctx.moveTo(this.points[0].x, this.points[0].y);
        for (var i = 0, len = this.points.length; i < len; i++) {
          point = this.points[i];
          ctx.lineTo(point.x, point.y);
        }
        this._renderFill(ctx);
        if (this.stroke || this.strokeDashArray) {
          ctx.closePath();
          this._renderStroke(ctx);
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderDashedStroke: function (ctx) {
        var p1, p2;

        ctx.beginPath();
        for (var i = 0, len = this.points.length; i < len; i++) {
          p1 = this.points[i];
          p2 = this.points[i + 1] || this.points[0];
          fabric.util.drawDashedLine(
            ctx,
            p1.x,
            p1.y,
            p2.x,
            p2.y,
            this.strokeDashArray
          );
        }
        ctx.closePath();
      },

      /**
       * Returns complexity of an instance
       * @return {Number} complexity of this instance
       */
      complexity: function () {
        return this.points.length;
      }
    }
  );

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`)
   * @static
   * @memberOf fabric.Polygon
   * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement
   */
  fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();

  /**
   * Returns {@link fabric.Polygon} instance from an SVG element
   * @static
   * @memberOf fabric.Polygon
   * @param {SVGElement} element Element to parse
   * @param {Object} [options] Options object
   * @return {fabric.Polygon} Instance of fabric.Polygon
   */
  fabric.Polygon.fromElement = function (element, options) {
    if (!element) {
      return null;
    }
    options || (options = {});

    var points = fabric.parsePointsAttribute(element.getAttribute("points")),
      parsedAttributes = fabric.parseAttributes(
        element,
        fabric.Polygon.ATTRIBUTE_NAMES
      );

    if (!("transformMatrix" in parsedAttributes)) {
      fabric.util.normalizePoints(points, options);
    }
    return new fabric.Polygon(points, extend(parsedAttributes, options), true);
  };
  /* _FROM_SVG_END_ */

  /**
   * Returns fabric.Polygon instance from an object representation
   * @static
   * @memberOf fabric.Polygon
   * @param {Object} object Object to create an instance from
   * @return {fabric.Polygon} Instance of fabric.Polygon
   */
  fabric.Polygon.fromObject = function (object) {
    return new fabric.Polygon(object.points, object, true);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    min = fabric.util.array.min,
    max = fabric.util.array.max,
    extend = fabric.util.object.extend,
    _toString = Object.prototype.toString,
    drawArc = fabric.util.drawArc,
    commandLengths = {
      m: 2,
      l: 2,
      h: 1,
      v: 1,
      c: 6,
      s: 4,
      q: 4,
      t: 2,
      a: 7
    },
    repeatedCommands = {
      m: "l",
      M: "L"
    };

  if (fabric.Path) {
    fabric.warn("fabric.Path is already defined");
    return;
  }

  /**
   * @private
   */
  function getX(item) {
    if (item[0] === "H") {
      return item[1];
    }
    return item[item.length - 2];
  }

  /**
   * @private
   */
  function getY(item) {
    if (item[0] === "V") {
      return item[1];
    }
    return item[item.length - 1];
  }

  /**
   * Path class
   * @class fabric.Path
   * @extends fabric.Object
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup}
   * @see {@link fabric.Path#initialize} for constructor definition
   */
  fabric.Path = fabric.util.createClass(
    fabric.Object,
    /** @lends fabric.Path.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "path",

      /**
       * Array of path points
       * @type Array
       * @default
       */
      path: null,

      /**
       * Constructor
       * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)
       * @param {Object} [options] Options object
       * @return {fabric.Path} thisArg
       */
      initialize: function (path, options) {
        options = options || {};

        this.setOptions(options);

        if (!path) {
          throw new Error("`path` argument is required");
        }

        var fromArray = _toString.call(path) === "[object Array]";

        this.path = fromArray
          ? path
          : // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values)
            path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi);

        if (!this.path) return;

        if (!fromArray) {
          this.path = this._parsePath();
        }
        this._initializePath(options);

        if (options.sourcePath) {
          this.setSourcePath(options.sourcePath);
        }
      },

      /**
       * @private
       * @param {Object} [options] Options object
       */
      _initializePath: function (options) {
        var isWidthSet = "width" in options && options.width != null,
          isHeightSet = "height" in options && options.width != null,
          isLeftSet = "left" in options,
          isTopSet = "top" in options,
          origLeft = isLeftSet ? this.left : 0,
          origTop = isTopSet ? this.top : 0;

        if (!isWidthSet || !isHeightSet) {
          extend(this, this._parseDimensions());
          if (isWidthSet) {
            this.width = options.width;
          }
          if (isHeightSet) {
            this.height = options.height;
          }
        } else {
          //Set center location relative to given height/width if not specified
          if (!isTopSet) {
            this.top = this.height / 2;
          }
          if (!isLeftSet) {
            this.left = this.width / 2;
          }
        }
        this.pathOffset =
          this.pathOffset ||
          // Save top-left coords as offset
          this._calculatePathOffset(origLeft, origTop);
      },

      /**
       * @private
       * @param {Number} origLeft Original left position
       * @param {Number} origTop  Original top position
       */
      _calculatePathOffset: function (origLeft, origTop) {
        return {
          x: this.left - origLeft - this.width / 2,
          y: this.top - origTop - this.height / 2
        };
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx context to render path on
       */
      _render: function (ctx, noTransform) {
        var current, // current instruction
          previous = null,
          subpathStartX = 0,
          subpathStartY = 0,
          x = 0, // current x
          y = 0, // current y
          controlX = 0, // current control point x
          controlY = 0, // current control point y
          tempX,
          tempY,
          tempControlX,
          tempControlY,
          l = -(this.width / 2 + this.pathOffset.x),
          t = -(this.height / 2 + this.pathOffset.y);

        if (noTransform) {
          l += this.width / 2;
          t += this.height / 2;
        }

        for (var i = 0, len = this.path.length; i < len; ++i) {
          current = this.path[i];

          switch (
            current[0] // first letter
          ) {
            case "l": // lineto, relative
              x += current[1];
              y += current[2];
              ctx.lineTo(x + l, y + t);
              break;

            case "L": // lineto, absolute
              x = current[1];
              y = current[2];
              ctx.lineTo(x + l, y + t);
              break;

            case "h": // horizontal lineto, relative
              x += current[1];
              ctx.lineTo(x + l, y + t);
              break;

            case "H": // horizontal lineto, absolute
              x = current[1];
              ctx.lineTo(x + l, y + t);
              break;

            case "v": // vertical lineto, relative
              y += current[1];
              ctx.lineTo(x + l, y + t);
              break;

            case "V": // verical lineto, absolute
              y = current[1];
              ctx.lineTo(x + l, y + t);
              break;

            case "m": // moveTo, relative
              x += current[1];
              y += current[2];
              subpathStartX = x;
              subpathStartY = y;
              ctx.moveTo(x + l, y + t);
              break;

            case "M": // moveTo, absolute
              x = current[1];
              y = current[2];
              subpathStartX = x;
              subpathStartY = y;
              ctx.moveTo(x + l, y + t);
              break;

            case "c": // bezierCurveTo, relative
              tempX = x + current[5];
              tempY = y + current[6];
              controlX = x + current[3];
              controlY = y + current[4];
              ctx.bezierCurveTo(
                x + current[1] + l, // x1
                y + current[2] + t, // y1
                controlX + l, // x2
                controlY + t, // y2
                tempX + l,
                tempY + t
              );
              x = tempX;
              y = tempY;
              break;

            case "C": // bezierCurveTo, absolute
              x = current[5];
              y = current[6];
              controlX = current[3];
              controlY = current[4];
              ctx.bezierCurveTo(
                current[1] + l,
                current[2] + t,
                controlX + l,
                controlY + t,
                x + l,
                y + t
              );
              break;

            case "s": // shorthand cubic bezierCurveTo, relative
              // transform to absolute x,y
              tempX = x + current[3];
              tempY = y + current[4];

              // calculate reflection of previous control points
              controlX = controlX ? 2 * x - controlX : x;
              controlY = controlY ? 2 * y - controlY : y;

              ctx.bezierCurveTo(
                controlX + l,
                controlY + t,
                x + current[1] + l,
                y + current[2] + t,
                tempX + l,
                tempY + t
              );
              // set control point to 2nd one of this command
              // "... the first control point is assumed to be
              // the reflection of the second control point on
              // the previous command relative to the current point."
              controlX = x + current[1];
              controlY = y + current[2];

              x = tempX;
              y = tempY;
              break;

            case "S": // shorthand cubic bezierCurveTo, absolute
              tempX = current[3];
              tempY = current[4];
              // calculate reflection of previous control points
              controlX = 2 * x - controlX;
              controlY = 2 * y - controlY;
              ctx.bezierCurveTo(
                controlX + l,
                controlY + t,
                current[1] + l,
                current[2] + t,
                tempX + l,
                tempY + t
              );
              x = tempX;
              y = tempY;

              // set control point to 2nd one of this command
              // "... the first control point is assumed to be
              // the reflection of the second control point on
              // the previous command relative to the current point."
              controlX = current[1];
              controlY = current[2];

              break;

            case "q": // quadraticCurveTo, relative
              // transform to absolute x,y
              tempX = x + current[3];
              tempY = y + current[4];

              controlX = x + current[1];
              controlY = y + current[2];

              ctx.quadraticCurveTo(
                controlX + l,
                controlY + t,
                tempX + l,
                tempY + t
              );
              x = tempX;
              y = tempY;
              break;

            case "Q": // quadraticCurveTo, absolute
              tempX = current[3];
              tempY = current[4];

              ctx.quadraticCurveTo(
                current[1] + l,
                current[2] + t,
                tempX + l,
                tempY + t
              );
              x = tempX;
              y = tempY;
              controlX = current[1];
              controlY = current[2];
              break;

            case "t": // shorthand quadraticCurveTo, relative
              // transform to absolute x,y
              tempX = x + current[1];
              tempY = y + current[2];

              if (previous[0].match(/[QqTt]/) === null) {
                // If there is no previous command or if the previous command was not a Q, q, T or t,
                // assume the control point is coincident with the current point
                controlX = x;
                controlY = y;
              } else if (previous[0] === "t") {
                // calculate reflection of previous control points for t
                controlX = 2 * x - tempControlX;
                controlY = 2 * y - tempControlY;
              } else if (previous[0] === "q") {
                // calculate reflection of previous control points for q
                controlX = 2 * x - controlX;
                controlY = 2 * y - controlY;
              }

              tempControlX = controlX;
              tempControlY = controlY;

              ctx.quadraticCurveTo(
                controlX + l,
                controlY + t,
                tempX + l,
                tempY + t
              );
              x = tempX;
              y = tempY;
              controlX = x + current[1];
              controlY = y + current[2];
              break;

            case "T":
              tempX = current[1];
              tempY = current[2];

              // calculate reflection of previous control points
              controlX = 2 * x - controlX;
              controlY = 2 * y - controlY;
              ctx.quadraticCurveTo(
                controlX + l,
                controlY + t,
                tempX + l,
                tempY + t
              );
              x = tempX;
              y = tempY;
              break;

            case "a":
              // TODO: optimize this
              drawArc(ctx, x + l, y + t, [
                current[1],
                current[2],
                current[3],
                current[4],
                current[5],
                current[6] + x + l,
                current[7] + y + t
              ]);
              x += current[6];
              y += current[7];
              break;

            case "A":
              // TODO: optimize this
              drawArc(ctx, x + l, y + t, [
                current[1],
                current[2],
                current[3],
                current[4],
                current[5],
                current[6] + l,
                current[7] + t
              ]);
              x = current[6];
              y = current[7];
              break;

            case "z":
            case "Z":
              x = subpathStartX;
              y = subpathStartY;
              ctx.closePath();
              break;
          }
          previous = current;
        }
      },

      /**
       * Renders path on a specified context
       * @param {CanvasRenderingContext2D} ctx context to render path on
       * @param {Boolean} [noTransform] When true, context is not transformed
       */
      render: function (ctx, noTransform) {
        // do not render if object is not visible
        if (!this.visible) return;

        ctx.save();
        if (noTransform) {
          ctx.translate(-this.width / 2, -this.height / 2);
        }
        var m = this.transformMatrix;

        if (m) {
          ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
        }
        if (!noTransform) {
          this.transform(ctx);
        }
        this._setStrokeStyles(ctx);
        this._setFillStyles(ctx);
        this._setShadow(ctx);
        this.clipTo && fabric.util.clipContext(this, ctx);
        ctx.beginPath();
        ctx.globalAlpha = this.group
          ? ctx.globalAlpha * this.opacity
          : this.opacity;
        this._render(ctx, noTransform);
        this._renderFill(ctx);
        this._renderStroke(ctx);
        this.clipTo && ctx.restore();
        this._removeShadow(ctx);
        ctx.restore();
      },

      /**
       * Returns string representation of an instance
       * @return {String} string representation of an instance
       */
      toString: function () {
        return (
          "#<fabric.Path (" +
          this.complexity() +
          '): { "top": ' +
          this.top +
          ', "left": ' +
          this.left +
          " }>"
        );
      },

      /**
       * Returns object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        var o = extend(this.callSuper("toObject", propertiesToInclude), {
          path: this.path.map(function (item) {
            return item.slice();
          }),
          pathOffset: this.pathOffset
        });
        if (this.sourcePath) {
          o.sourcePath = this.sourcePath;
        }
        if (this.transformMatrix) {
          o.transformMatrix = this.transformMatrix;
        }
        return o;
      },

      /**
       * Returns dataless object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toDatalessObject: function (propertiesToInclude) {
        var o = this.toObject(propertiesToInclude);
        if (this.sourcePath) {
          o.path = this.sourcePath;
        }
        delete o.sourcePath;
        return o;
      },

      /* _TO_SVG_START_ */
      /**
       * Returns svg representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var chunks = [],
          markup = this._createBaseSVGMarkup();

        for (var i = 0, len = this.path.length; i < len; i++) {
          chunks.push(this.path[i].join(" "));
        }
        var path = chunks.join(" ");

        markup.push(
          '<g transform="',
          this.group ? "" : this.getSvgTransform(),
          '">',
          "<path ",
          'd="',
          path,
          '" style="',
          this.getSvgStyles(),
          '" transform="translate(',
          -this.width / 2,
          " ",
          -this.height / 2,
          ")",
          '" stroke-linecap="round" ',
          "/>",
          "</g>"
        );

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * Returns number representation of an instance complexity
       * @return {Number} complexity of this instance
       */
      complexity: function () {
        return this.path.length;
      },

      /**
       * @private
       */
      _parsePath: function () {
        var result = [],
          coords = [],
          currentPath,
          parsed,
          re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi,
          match,
          coordsStr;

        for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) {
          currentPath = this.path[i];

          coordsStr = currentPath.slice(1).trim();
          coords.length = 0;

          while ((match = re.exec(coordsStr))) {
            coords.push(match[0]);
          }

          coordsParsed = [currentPath.charAt(0)];

          for (var j = 0, jlen = coords.length; j < jlen; j++) {
            parsed = parseFloat(coords[j]);
            if (!isNaN(parsed)) {
              coordsParsed.push(parsed);
            }
          }

          var command = coordsParsed[0],
            commandLength = commandLengths[command.toLowerCase()],
            repeatedCommand = repeatedCommands[command] || command;

          if (coordsParsed.length - 1 > commandLength) {
            for (
              var k = 1, klen = coordsParsed.length;
              k < klen;
              k += commandLength
            ) {
              result.push(
                [command].concat(coordsParsed.slice(k, k + commandLength))
              );
              command = repeatedCommand;
            }
          } else {
            result.push(coordsParsed);
          }
        }

        return result;
      },

      /**
       * @private
       */
      _parseDimensions: function () {
        var aX = [],
          aY = [],
          previous = {};

        this.path.forEach(function (item, i) {
          this._getCoordsFromCommand(item, i, aX, aY, previous);
        }, this);

        var minX = min(aX),
          minY = min(aY),
          maxX = max(aX),
          maxY = max(aY),
          deltaX = maxX - minX,
          deltaY = maxY - minY,
          o = {
            left: this.left + (minX + deltaX / 2),
            top: this.top + (minY + deltaY / 2),
            width: deltaX,
            height: deltaY
          };

        return o;
      },

      _getCoordsFromCommand: function (item, i, aX, aY, previous) {
        var isLowerCase = false;

        if (item[0] !== "H") {
          previous.x = i === 0 ? getX(item) : getX(this.path[i - 1]);
        }
        if (item[0] !== "V") {
          previous.y = i === 0 ? getY(item) : getY(this.path[i - 1]);
        }

        // lowercased letter denotes relative position;
        // transform to absolute
        if (item[0] === item[0].toLowerCase()) {
          isLowerCase = true;
        }

        var xy = this._getXY(item, isLowerCase, previous),
          val;

        val = parseInt(xy.x, 10);
        if (!isNaN(val)) {
          aX.push(val);
        }

        val = parseInt(xy.y, 10);
        if (!isNaN(val)) {
          aY.push(val);
        }
      },

      _getXY: function (item, isLowerCase, previous) {
        // last 2 items in an array of coordinates are the actualy x/y (except H/V), collect them
        // TODO (kangax): support relative h/v commands

        var x = isLowerCase
            ? previous.x + getX(item)
            : item[0] === "V"
            ? previous.x
            : getX(item),
          y = isLowerCase
            ? previous.y + getY(item)
            : item[0] === "H"
            ? previous.y
            : getY(item);

        return { x: x, y: y };
      }
    }
  );

  /**
   * Creates an instance of fabric.Path from an object
   * @static
   * @memberOf fabric.Path
   * @param {Object} object
   * @param {Function} callback Callback to invoke when an fabric.Path instance is created
   */
  fabric.Path.fromObject = function (object, callback) {
    if (typeof object.path === "string") {
      fabric.loadSVGFromURL(object.path, function (elements) {
        var path = elements[0],
          pathUrl = object.path;

        delete object.path;

        fabric.util.object.extend(path, object);
        path.setSourcePath(pathUrl);

        callback(path);
      });
    } else {
      callback(new fabric.Path(object.path, object));
    }
  };

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`)
   * @static
   * @memberOf fabric.Path
   * @see http://www.w3.org/TR/SVG/paths.html#PathElement
   */
  fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(["d"]);

  /**
   * Creates an instance of fabric.Path from an SVG <path> element
   * @static
   * @memberOf fabric.Path
   * @param {SVGElement} element to parse
   * @param {Function} callback Callback to invoke when an fabric.Path instance is created
   * @param {Object} [options] Options object
   */
  fabric.Path.fromElement = function (element, callback, options) {
    var parsedAttributes = fabric.parseAttributes(
      element,
      fabric.Path.ATTRIBUTE_NAMES
    );
    callback &&
      callback(
        new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))
      );
  };
  /* _FROM_SVG_END_ */

  /**
   * Indicates that instances of this type are async
   * @static
   * @memberOf fabric.Path
   * @type Boolean
   * @default
   */
  fabric.Path.async = true;
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend,
    invoke = fabric.util.array.invoke,
    parentToObject = fabric.Object.prototype.toObject;

  if (fabric.PathGroup) {
    fabric.warn("fabric.PathGroup is already defined");
    return;
  }

  /**
   * Path group class
   * @class fabric.PathGroup
   * @extends fabric.Path
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup}
   * @see {@link fabric.PathGroup#initialize} for constructor definition
   */
  fabric.PathGroup = fabric.util.createClass(
    fabric.Path,
    /** @lends fabric.PathGroup.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "path-group",

      /**
       * Fill value
       * @type String
       * @default
       */
      fill: "",

      /**
       * Constructor
       * @param {Array} paths
       * @param {Object} [options] Options object
       * @return {fabric.PathGroup} thisArg
       */
      initialize: function (paths, options) {
        options = options || {};
        this.paths = paths || [];

        for (var i = this.paths.length; i--; ) {
          this.paths[i].group = this;
        }

        this.setOptions(options);

        if (options.widthAttr) {
          this.scaleX = options.widthAttr / options.width;
        }
        if (options.heightAttr) {
          this.scaleY = options.heightAttr / options.height;
        }

        this.setCoords();

        if (options.sourcePath) {
          this.setSourcePath(options.sourcePath);
        }
      },

      /**
       * Renders this group on a specified context
       * @param {CanvasRenderingContext2D} ctx Context to render this instance on
       */
      render: function (ctx) {
        // do not render if object is not visible
        if (!this.visible) return;

        ctx.save();

        var m = this.transformMatrix;

        if (m) {
          ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
        }
        this.transform(ctx);

        this._setShadow(ctx);
        this.clipTo && fabric.util.clipContext(this, ctx);
        for (var i = 0, l = this.paths.length; i < l; ++i) {
          this.paths[i].render(ctx, true);
        }
        this.clipTo && ctx.restore();
        this._removeShadow(ctx);
        ctx.restore();
      },

      /**
       * Sets certain property to a certain value
       * @param {String} prop
       * @param {Any} value
       * @return {fabric.PathGroup} thisArg
       */
      _set: function (prop, value) {
        if (prop === "fill" && value && this.isSameColor()) {
          var i = this.paths.length;
          while (i--) {
            this.paths[i]._set(prop, value);
          }
        }

        return this.callSuper("_set", prop, value);
      },

      /**
       * Returns object representation of this path group
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        var o = extend(parentToObject.call(this, propertiesToInclude), {
          paths: invoke(this.getObjects(), "toObject", propertiesToInclude)
        });
        if (this.sourcePath) {
          o.sourcePath = this.sourcePath;
        }
        return o;
      },

      /**
       * Returns dataless object representation of this path group
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} dataless object representation of an instance
       */
      toDatalessObject: function (propertiesToInclude) {
        var o = this.toObject(propertiesToInclude);
        if (this.sourcePath) {
          o.paths = this.sourcePath;
        }
        return o;
      },

      /* _TO_SVG_START_ */
      /**
       * Returns svg representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var objects = this.getObjects(),
          markup = [
            "<g ",
            'style="',
            this.getSvgStyles(),
            '" ',
            'transform="',
            this.getSvgTransform(),
            '" ',
            ">"
          ];

        for (var i = 0, len = objects.length; i < len; i++) {
          markup.push(objects[i].toSVG(reviver));
        }
        markup.push("</g>");

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * Returns a string representation of this path group
       * @return {String} string representation of an object
       */
      toString: function () {
        return (
          "#<fabric.PathGroup (" +
          this.complexity() +
          "): { top: " +
          this.top +
          ", left: " +
          this.left +
          " }>"
        );
      },

      /**
       * Returns true if all paths in this group are of same color
       * @return {Boolean} true if all paths are of the same color (`fill`)
       */
      isSameColor: function () {
        var firstPathFill = (
          this.getObjects()[0].get("fill") || ""
        ).toLowerCase();
        return this.getObjects().every(function (path) {
          return (path.get("fill") || "").toLowerCase() === firstPathFill;
        });
      },

      /**
       * Returns number representation of object's complexity
       * @return {Number} complexity
       */
      complexity: function () {
        return this.paths.reduce(function (total, path) {
          return total + (path && path.complexity ? path.complexity() : 0);
        }, 0);
      },

      /**
       * Returns all paths in this path group
       * @return {Array} array of path objects included in this path group
       */
      getObjects: function () {
        return this.paths;
      }
    }
  );

  /**
   * Creates fabric.PathGroup instance from an object representation
   * @static
   * @memberOf fabric.PathGroup
   * @param {Object} object Object to create an instance from
   * @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created
   */
  fabric.PathGroup.fromObject = function (object, callback) {
    if (typeof object.paths === "string") {
      fabric.loadSVGFromURL(object.paths, function (elements) {
        var pathUrl = object.paths;
        delete object.paths;

        var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl);

        callback(pathGroup);
      });
    } else {
      fabric.util.enlivenObjects(object.paths, function (enlivenedObjects) {
        delete object.paths;
        callback(new fabric.PathGroup(enlivenedObjects, object));
      });
    }
  };

  /**
   * Indicates that instances of this type are async
   * @static
   * @memberOf fabric.PathGroup
   * @type Boolean
   * @default
   */
  fabric.PathGroup.async = true;
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend,
    min = fabric.util.array.min,
    max = fabric.util.array.max,
    invoke = fabric.util.array.invoke;

  if (fabric.Group) {
    return;
  }

  // lock-related properties, for use in fabric.Group#get
  // to enable locking behavior on group
  // when one of its objects has lock-related properties set
  var _lockProperties = {
    lockMovementX: true,
    lockMovementY: true,
    lockRotation: true,
    lockScalingX: true,
    lockScalingY: true,
    lockUniScaling: true
  };

  /**
   * Group class
   * @class fabric.Group
   * @extends fabric.Object
   * @mixes fabric.Collection
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups}
   * @see {@link fabric.Group#initialize} for constructor definition
   */
  fabric.Group = fabric.util.createClass(
    fabric.Object,
    fabric.Collection,
    /** @lends fabric.Group.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "group",

      /**
       * Constructor
       * @param {Object} objects Group objects
       * @param {Object} [options] Options object
       * @return {Object} thisArg
       */
      initialize: function (objects, options) {
        options = options || {};

        this._objects = objects || [];
        for (var i = this._objects.length; i--; ) {
          this._objects[i].group = this;
        }

        this.originalState = {};
        this.callSuper("initialize");

        this._calcBounds();
        this._updateObjectsCoords();

        if (options) {
          extend(this, options);
        }
        this._setOpacityIfSame();

        this.setCoords();
        this.saveCoords();
      },

      /**
       * @private
       */
      _updateObjectsCoords: function () {
        this.forEachObject(this._updateObjectCoords, this);
      },

      /**
       * @private
       */
      _updateObjectCoords: function (object) {
        var objectLeft = object.getLeft(),
          objectTop = object.getTop();

        object.set({
          originalLeft: objectLeft,
          originalTop: objectTop,
          left: objectLeft - this.left,
          top: objectTop - this.top
        });

        object.setCoords();

        // do not display corners of objects enclosed in a group
        object.__origHasControls = object.hasControls;
        object.hasControls = false;
      },

      /**
       * Returns string represenation of a group
       * @return {String}
       */
      toString: function () {
        return "#<fabric.Group: (" + this.complexity() + ")>";
      },

      /**
       * Adds an object to a group; Then recalculates group's dimension, position.
       * @param {Object} object
       * @return {fabric.Group} thisArg
       * @chainable
       */
      addWithUpdate: function (object) {
        this._restoreObjectsState();
        if (object) {
          this._objects.push(object);
          object.group = this;
        }
        // since _restoreObjectsState set objects inactive
        this.forEachObject(this._setObjectActive, this);
        this._calcBounds();
        this._updateObjectsCoords();
        return this;
      },

      /**
       * @private
       */
      _setObjectActive: function (object) {
        object.set("active", true);
        object.group = this;
      },

      /**
       * Removes an object from a group; Then recalculates group's dimension, position.
       * @param {Object} object
       * @return {fabric.Group} thisArg
       * @chainable
       */
      removeWithUpdate: function (object) {
        this._moveFlippedObject(object);
        this._restoreObjectsState();

        // since _restoreObjectsState set objects inactive
        this.forEachObject(this._setObjectActive, this);

        this.remove(object);
        this._calcBounds();
        this._updateObjectsCoords();

        return this;
      },

      /**
       * @private
       */
      _onObjectAdded: function (object) {
        object.group = this;
      },

      /**
       * @private
       */
      _onObjectRemoved: function (object) {
        delete object.group;
        object.set("active", false);
      },

      /**
       * Properties that are delegated to group objects when reading/writing
       * @param {Object} delegatedProperties
       */
      delegatedProperties: {
        fill: true,
        opacity: true,
        fontFamily: true,
        fontWeight: true,
        fontSize: true,
        fontStyle: true,
        lineHeight: true,
        textDecoration: true,
        textAlign: true,
        backgroundColor: true
      },

      /**
       * @private
       */
      _set: function (key, value) {
        if (key in this.delegatedProperties) {
          var i = this._objects.length;
          this[key] = value;
          while (i--) {
            this._objects[i].set(key, value);
          }
        } else {
          this[key] = value;
        }
      },

      /**
       * Returns object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        return extend(this.callSuper("toObject", propertiesToInclude), {
          objects: invoke(this._objects, "toObject", propertiesToInclude)
        });
      },

      /**
       * Renders instance on a given context
       * @param {CanvasRenderingContext2D} ctx context to render instance on
       */
      render: function (ctx) {
        // do not render if object is not visible
        if (!this.visible) return;

        ctx.save();
        this.clipTo && fabric.util.clipContext(this, ctx);

        // the array is now sorted in order of highest first, so start from end
        for (var i = 0, len = this._objects.length; i < len; i++) {
          this._renderObject(this._objects[i], ctx);
        }

        this.clipTo && ctx.restore();

        ctx.restore();
      },

      /**
       * Renders controls and borders for the object
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Boolean} [noTransform] When true, context is not transformed
       */
      _renderControls: function (ctx, noTransform) {
        this.callSuper("_renderControls", ctx, noTransform);
        for (var i = 0, len = this._objects.length; i < len; i++) {
          this._objects[i]._renderControls(ctx);
        }
      },

      /**
       * @private
       */
      _renderObject: function (object, ctx) {
        var originalHasRotatingPoint = object.hasRotatingPoint;

        // do not render if object is not visible
        if (!object.visible) return;

        object.hasRotatingPoint = false;

        object.render(ctx);

        object.hasRotatingPoint = originalHasRotatingPoint;
      },

      /**
       * Retores original state of each of group objects (original state is that which was before group was created).
       * @private
       * @return {fabric.Group} thisArg
       * @chainable
       */
      _restoreObjectsState: function () {
        this._objects.forEach(this._restoreObjectState, this);
        return this;
      },

      /**
       * Moves a flipped object to the position where it's displayed
       * @private
       * @param {fabric.Object} object
       * @return {fabric.Group} thisArg
       */
      _moveFlippedObject: function (object) {
        var oldOriginX = object.get("originX"),
          oldOriginY = object.get("originY"),
          center = object.getCenterPoint();

        object.set({
          originX: "center",
          originY: "center",
          left: center.x,
          top: center.y
        });

        this._toggleFlipping(object);

        var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY);

        object.set({
          originX: oldOriginX,
          originY: oldOriginY,
          left: newOrigin.x,
          top: newOrigin.y
        });

        return this;
      },

      /**
       * @private
       */
      _toggleFlipping: function (object) {
        if (this.flipX) {
          object.toggle("flipX");
          object.set("left", -object.get("left"));
          object.setAngle(-object.getAngle());
        }
        if (this.flipY) {
          object.toggle("flipY");
          object.set("top", -object.get("top"));
          object.setAngle(-object.getAngle());
        }
      },

      /**
       * Restores original state of a specified object in group
       * @private
       * @param {fabric.Object} object
       * @return {fabric.Group} thisArg
       */
      _restoreObjectState: function (object) {
        this._setObjectPosition(object);

        object.setCoords();
        object.hasControls = object.__origHasControls;
        delete object.__origHasControls;
        object.set("active", false);
        object.setCoords();
        delete object.group;

        return this;
      },

      /**
       * @private
       */
      _setObjectPosition: function (object) {
        var groupLeft = this.getLeft(),
          groupTop = this.getTop(),
          rotated = this._getRotatedLeftTop(object);

        object.set({
          angle: object.getAngle() + this.getAngle(),
          left: groupLeft + rotated.left,
          top: groupTop + rotated.top,
          scaleX: object.get("scaleX") * this.get("scaleX"),
          scaleY: object.get("scaleY") * this.get("scaleY")
        });
      },

      /**
       * @private
       */
      _getRotatedLeftTop: function (object) {
        var groupAngle = this.getAngle() * (Math.PI / 180);
        return {
          left:
            -Math.sin(groupAngle) * object.getTop() * this.get("scaleY") +
            Math.cos(groupAngle) * object.getLeft() * this.get("scaleX"),

          top:
            Math.cos(groupAngle) * object.getTop() * this.get("scaleY") +
            Math.sin(groupAngle) * object.getLeft() * this.get("scaleX")
        };
      },

      /**
       * Destroys a group (restoring state of its objects)
       * @return {fabric.Group} thisArg
       * @chainable
       */
      destroy: function () {
        this._objects.forEach(this._moveFlippedObject, this);
        return this._restoreObjectsState();
      },

      /**
       * Saves coordinates of this instance (to be used together with `hasMoved`)
       * @saveCoords
       * @return {fabric.Group} thisArg
       * @chainable
       */
      saveCoords: function () {
        this._originalLeft = this.get("left");
        this._originalTop = this.get("top");
        return this;
      },

      /**
       * Checks whether this group was moved (since `saveCoords` was called last)
       * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)
       */
      hasMoved: function () {
        return (
          this._originalLeft !== this.get("left") ||
          this._originalTop !== this.get("top")
        );
      },

      /**
       * Sets coordinates of all group objects
       * @return {fabric.Group} thisArg
       * @chainable
       */
      setObjectsCoords: function () {
        this.forEachObject(function (object) {
          object.setCoords();
        });
        return this;
      },

      /**
       * @private
       */
      _setOpacityIfSame: function () {
        var objects = this.getObjects(),
          firstValue = objects[0] ? objects[0].get("opacity") : 1,
          isSameOpacity = objects.every(function (o) {
            return o.get("opacity") === firstValue;
          });

        if (isSameOpacity) {
          this.opacity = firstValue;
        }
      },

      /**
       * @private
       */
      _calcBounds: function (onlyWidthHeight) {
        var aX = [],
          aY = [],
          o;

        for (var i = 0, len = this._objects.length; i < len; ++i) {
          o = this._objects[i];
          o.setCoords();
          for (var prop in o.oCoords) {
            aX.push(o.oCoords[prop].x);
            aY.push(o.oCoords[prop].y);
          }
        }

        this.set(this._getBounds(aX, aY, onlyWidthHeight));
      },

      /**
       * @private
       */
      _getBounds: function (aX, aY, onlyWidthHeight) {
        var ivt = fabric.util.invertTransform(this.getViewportTransform()),
          minXY = fabric.util.transformPoint(
            new fabric.Point(min(aX), min(aY)),
            ivt
          ),
          maxXY = fabric.util.transformPoint(
            new fabric.Point(max(aX), max(aY)),
            ivt
          ),
          obj = {
            width: maxXY.x - minXY.x || 0,
            height: maxXY.y - minXY.y || 0
          };

        if (!onlyWidthHeight) {
          obj.left = (minXY.x + maxXY.x) / 2 || 0;
          obj.top = (minXY.y + maxXY.y) / 2 || 0;
        }
        return obj;
      },

      /* _TO_SVG_START_ */
      /**
       * Returns svg representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var markup = ["<g ", 'transform="', this.getSvgTransform(), '">'];

        for (var i = 0, len = this._objects.length; i < len; i++) {
          markup.push(this._objects[i].toSVG(reviver));
        }

        markup.push("</g>");

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * Returns requested property
       * @param {String} prop Property to get
       * @return {Any}
       */
      get: function (prop) {
        if (prop in _lockProperties) {
          if (this[prop]) {
            return this[prop];
          } else {
            for (var i = 0, len = this._objects.length; i < len; i++) {
              if (this._objects[i][prop]) {
                return true;
              }
            }
            return false;
          }
        } else {
          if (prop in this.delegatedProperties) {
            return this._objects[0] && this._objects[0].get(prop);
          }
          return this[prop];
        }
      }
    }
  );

  /**
   * Returns {@link fabric.Group} instance from an object representation
   * @static
   * @memberOf fabric.Group
   * @param {Object} object Object to create a group from
   * @param {Function} [callback] Callback to invoke when an group instance is created
   * @return {fabric.Group} An instance of fabric.Group
   */
  fabric.Group.fromObject = function (object, callback) {
    fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) {
      delete object.objects;
      callback && callback(new fabric.Group(enlivenedObjects, object));
    });
  };

  /**
   * Indicates that instances of this type are async
   * @static
   * @memberOf fabric.Group
   * @type Boolean
   * @default
   */
  fabric.Group.async = true;
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var extend = fabric.util.object.extend;

  if (!global.fabric) {
    global.fabric = {};
  }

  if (global.fabric.Image) {
    fabric.warn("fabric.Image is already defined.");
    return;
  }

  /**
   * Image class
   * @class fabric.Image
   * @extends fabric.Object
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images}
   * @see {@link fabric.Image#initialize} for constructor definition
   */
  fabric.Image = fabric.util.createClass(
    fabric.Object,
    /** @lends fabric.Image.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "image",

      /**
       * crossOrigin value (one of "", "anonymous", "allow-credentials")
       * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes
       * @type String
       * @default
       */
      crossOrigin: "",

      /**
       * Constructor
       * @param {HTMLImageElement | String} element Image element
       * @param {Object} [options] Options object
       * @return {fabric.Image} thisArg
       */
      initialize: function (element, options) {
        options || (options = {});

        this.filters = [];

        this.callSuper("initialize", options);

        this._initElement(element, options);
        this._initConfig(options);

        if (options.filters) {
          this.filters = options.filters;
          this.applyFilters();
        }
      },

      /**
       * Returns image element which this instance if based on
       * @return {HTMLImageElement} Image element
       */
      getElement: function () {
        return this._element;
      },

      /**
       * Sets image element for this instance to a specified one.
       * If filters defined they are applied to new image.
       * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area.
       * @param {HTMLImageElement} element
       * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated
       * @return {fabric.Image} thisArg
       * @chainable
       */
      setElement: function (element, callback) {
        this._element = element;
        this._originalElement = element;
        this._initConfig();

        if (this.filters.length !== 0) {
          this.applyFilters(callback);
        }

        return this;
      },

      /**
       * Sets crossOrigin value (on an instance and corresponding image element)
       * @return {fabric.Image} thisArg
       * @chainable
       */
      setCrossOrigin: function (value) {
        this.crossOrigin = value;
        this._element.crossOrigin = value;

        return this;
      },

      /**
       * Returns original size of an image
       * @return {Object} Object with "width" and "height" properties
       */
      getOriginalSize: function () {
        var element = this.getElement();
        return {
          width: element.width,
          height: element.height
        };
      },

      /**
       * Renders image on a specified context
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Boolean} [noTransform] When true, context is not transformed
       */
      render: function (ctx, noTransform) {
        // do not render if object is not visible
        if (!this.visible) return;

        ctx.save();
        var m = this.transformMatrix,
          isInPathGroup = this.group && this.group.type === "path-group";

        // this._resetWidthHeight();
        if (isInPathGroup) {
          ctx.translate(-this.group.width / 2, -this.group.height / 2);
        }
        if (m) {
          ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
        }
        if (!noTransform) {
          this.transform(ctx);
        }
        if (isInPathGroup) {
          ctx.translate(this.width / 2, this.height / 2);
        }

        this._setShadow(ctx);
        this.clipTo && fabric.util.clipContext(this, ctx);
        this._render(ctx);
        if (this.shadow && !this.shadow.affectStroke) {
          this._removeShadow(ctx);
        }
        this._renderStroke(ctx);
        this.clipTo && ctx.restore();
        ctx.restore();
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _stroke: function (ctx) {
        ctx.save();
        this._setStrokeStyles(ctx);
        ctx.beginPath();
        ctx.strokeRect(
          -this.width / 2,
          -this.height / 2,
          this.width,
          this.height
        );
        ctx.closePath();
        ctx.restore();
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderDashedStroke: function (ctx) {
        var x = -this.width / 2,
          y = -this.height / 2,
          w = this.width,
          h = this.height;

        ctx.save();
        this._setStrokeStyles(ctx);

        ctx.beginPath();
        fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);
        fabric.util.drawDashedLine(
          ctx,
          x + w,
          y,
          x + w,
          y + h,
          this.strokeDashArray
        );
        fabric.util.drawDashedLine(
          ctx,
          x + w,
          y + h,
          x,
          y + h,
          this.strokeDashArray
        );
        fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);
        ctx.closePath();
        ctx.restore();
      },

      /**
       * Returns object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} Object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        return extend(this.callSuper("toObject", propertiesToInclude), {
          src: this._originalElement.src || this._originalElement._src,
          filters: this.filters.map(function (filterObj) {
            return filterObj && filterObj.toObject();
          }),
          crossOrigin: this.crossOrigin
        });
      },

      /* _TO_SVG_START_ */
      /**
       * Returns SVG representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var markup = [];

        markup.push(
          '<g transform="',
          this.getSvgTransform(),
          '">',
          '<image xlink:href="',
          this.getSvgSrc(),
          '" style="',
          this.getSvgStyles(),
          // we're essentially moving origin of transformation from top/left corner to the center of the shape
          // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left
          // so that object's center aligns with container's left/top
          '" transform="translate(' +
            -this.width / 2 +
            " " +
            -this.height / 2 +
            ")",
          '" width="',
          this.width,
          '" height="',
          this.height,
          '" preserveAspectRatio="none"',
          "></image>"
        );

        if (this.stroke || this.strokeDashArray) {
          var origFill = this.fill;
          this.fill = null;
          markup.push(
            "<rect ",
            'x="',
            (-1 * this.width) / 2,
            '" y="',
            (-1 * this.height) / 2,
            '" width="',
            this.width,
            '" height="',
            this.height,
            '" style="',
            this.getSvgStyles(),
            '"/>'
          );
          this.fill = origFill;
        }

        markup.push("</g>");

        return reviver ? reviver(markup.join("")) : markup.join("");
      },
      /* _TO_SVG_END_ */

      /**
       * Returns source of an image
       * @return {String} Source of an image
       */
      getSrc: function () {
        if (this.getElement()) {
          return this.getElement().src || this.getElement()._src;
        }
      },

      /**
       * Returns string representation of an instance
       * @return {String} String representation of an instance
       */
      toString: function () {
        return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';
      },

      /**
       * Returns a clone of an instance
       * @param {Function} callback Callback is invoked with a clone as a first argument
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       */
      clone: function (callback, propertiesToInclude) {
        this.constructor.fromObject(
          this.toObject(propertiesToInclude),
          callback
        );
      },

      /**
       * Applies filters assigned to this image (from "filters" array)
       * @mthod applyFilters
       * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated
       * @return {fabric.Image} thisArg
       * @chainable
       */
      applyFilters: function (callback) {
        if (!this._originalElement) {
          return;
        }

        if (this.filters.length === 0) {
          this._element = this._originalElement;
          callback && callback();
          return;
        }

        var imgEl = this._originalElement,
          canvasEl = fabric.util.createCanvasElement(),
          replacement = fabric.util.createImage(),
          _this = this;

        canvasEl.width = imgEl.width;
        canvasEl.height = imgEl.height;

        canvasEl
          .getContext("2d")
          .drawImage(imgEl, 0, 0, imgEl.width, imgEl.height);

        this.filters.forEach(function (filter) {
          filter && filter.applyTo(canvasEl);
        });

        /** @ignore */

        replacement.width = imgEl.width;
        replacement.height = imgEl.height;

        if (fabric.isLikelyNode) {
          replacement.src = canvasEl.toBuffer(
            undefined,
            fabric.Image.pngCompression
          );

          // onload doesn't fire in some node versions, so we invoke callback manually
          _this._element = replacement;
          callback && callback();
        } else {
          replacement.onload = function () {
            _this._element = replacement;
            callback && callback();
            replacement.onload = canvasEl = imgEl = null;
          };
          replacement.src = canvasEl.toDataURL("image/png");
        }

        return this;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function (ctx) {
        this._element &&
          ctx.drawImage(
            this._element,
            -this.width / 2,
            -this.height / 2,
            this.width,
            this.height
          );
      },

      /**
       * @private
       */
      _resetWidthHeight: function () {
        var element = this.getElement();

        this.set("width", element.width);
        this.set("height", element.height);
      },

      /**
       * The Image class's initialization method. This method is automatically
       * called by the constructor.
       * @private
       * @param {HTMLImageElement|String} element The element representing the image
       */
      _initElement: function (element) {
        this.setElement(fabric.util.getById(element));
        fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS);
      },

      /**
       * @private
       * @param {Object} [options] Options object
       */
      _initConfig: function (options) {
        options || (options = {});
        this.setOptions(options);
        this._setWidthHeight(options);
        if (this._element && this.crossOrigin) {
          this._element.crossOrigin = this.crossOrigin;
        }
      },

      /**
       * @private
       * @param {Object} object Object with filters property
       * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created
       */
      _initFilters: function (object, callback) {
        if (object.filters && object.filters.length) {
          fabric.util.enlivenObjects(
            object.filters,
            function (enlivenedObjects) {
              callback && callback(enlivenedObjects);
            },
            "fabric.Image.filters"
          );
        } else {
          callback && callback();
        }
      },

      /**
       * @private
       * @param {Object} [options] Object with width/height properties
       */
      _setWidthHeight: function (options) {
        this.width =
          "width" in options
            ? options.width
            : this.getElement()
            ? this.getElement().width || 0
            : 0;

        this.height =
          "height" in options
            ? options.height
            : this.getElement()
            ? this.getElement().height || 0
            : 0;
      },

      /**
       * Returns complexity of an instance
       * @return {Number} complexity of this instance
       */
      complexity: function () {
        return 1;
      }
    }
  );

  /**
   * Default CSS class name for canvas
   * @static
   * @type String
   * @default
   */
  fabric.Image.CSS_CANVAS = "canvas-img";

  /**
   * Alias for getSrc
   * @static
   */
  fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc;

  /**
   * Creates an instance of fabric.Image from its object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @param {Function} [callback] Callback to invoke when an image instance is created
   */
  fabric.Image.fromObject = function (object, callback) {
    fabric.util.loadImage(
      object.src,
      function (img) {
        fabric.Image.prototype._initFilters.call(
          object,
          object,
          function (filters) {
            object.filters = filters || [];
            var instance = new fabric.Image(img, object);
            callback && callback(instance);
          }
        );
      },
      null,
      object.crossOrigin
    );
  };

  /**
   * Creates an instance of fabric.Image from an URL string
   * @static
   * @param {String} url URL to create an image from
   * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument)
   * @param {Object} [imgOptions] Options object
   */
  fabric.Image.fromURL = function (url, callback, imgOptions) {
    fabric.util.loadImage(
      url,
      function (img) {
        callback(new fabric.Image(img, imgOptions));
      },
      null,
      imgOptions && imgOptions.crossOrigin
    );
  };

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement})
   * @static
   * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement}
   */
  fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(
    "x y width height xlink:href".split(" ")
  );

  /**
   * Returns {@link fabric.Image} instance from an SVG element
   * @static
   * @param {SVGElement} element Element to parse
   * @param {Function} callback Callback to execute when fabric.Image object is created
   * @param {Object} [options] Options object
   * @return {fabric.Image} Instance of fabric.Image
   */
  fabric.Image.fromElement = function (element, callback, options) {
    var parsedAttributes = fabric.parseAttributes(
      element,
      fabric.Image.ATTRIBUTE_NAMES
    );

    fabric.Image.fromURL(
      parsedAttributes["xlink:href"],
      callback,
      extend(options ? fabric.util.object.clone(options) : {}, parsedAttributes)
    );
  };
  /* _FROM_SVG_END_ */

  /**
   * Indicates that instances of this type are async
   * @static
   * @type Boolean
   * @default
   */
  fabric.Image.async = true;

  /**
   * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9
   * @static
   * @type Number
   * @default
   */
  fabric.Image.pngCompression = 1;
})(typeof exports !== "undefined" ? exports : this);

fabric.util.object.extend(
  fabric.Object.prototype,
  /** @lends fabric.Object.prototype */ {
    /**
     * @private
     * @return {Number} angle value
     */
    _getAngleValueForStraighten: function () {
      var angle = this.getAngle() % 360;
      if (angle > 0) {
        return Math.round((angle - 1) / 90) * 90;
      }
      return Math.round(angle / 90) * 90;
    },

    /**
     * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer)
     * @return {fabric.Object} thisArg
     * @chainable
     */
    straighten: function () {
      this.setAngle(this._getAngleValueForStraighten());
      return this;
    },

    /**
     * Same as {@link fabric.Object.prototype.straighten} but with animation
     * @param {Object} callbacks Object with callback functions
     * @param {Function} [callbacks.onComplete] Invoked on completion
     * @param {Function} [callbacks.onChange] Invoked on every step of animation
     * @return {fabric.Object} thisArg
     * @chainable
     */
    fxStraighten: function (callbacks) {
      callbacks = callbacks || {};

      var empty = function () {},
        onComplete = callbacks.onComplete || empty,
        onChange = callbacks.onChange || empty,
        _this = this;

      fabric.util.animate({
        startValue: this.get("angle"),
        endValue: this._getAngleValueForStraighten(),
        duration: this.FX_DURATION,
        onChange: function (value) {
          _this.setAngle(value);
          onChange();
        },
        onComplete: function () {
          _this.setCoords();
          onComplete();
        },
        onStart: function () {
          _this.set("active", false);
        }
      });

      return this;
    }
  }
);

fabric.util.object.extend(
  fabric.StaticCanvas.prototype,
  /** @lends fabric.StaticCanvas.prototype */ {
    /**
     * Straightens object, then rerenders canvas
     * @param {fabric.Object} object Object to straighten
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    straightenObject: function (object) {
      object.straighten();
      this.renderAll();
      return this;
    },

    /**
     * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated
     * @param {fabric.Object} object Object to straighten
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    fxStraightenObject: function (object) {
      object.fxStraighten({
        onChange: this.renderAll.bind(this)
      });
      return this;
    }
  }
);

/**
 * @namespace fabric.Image.filters
 * @memberOf fabric.Image
 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters}
 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
 */
fabric.Image.filters = fabric.Image.filters || {};

/**
 * Root filter class from which all filter classes inherit from
 * @class fabric.Image.filters.BaseFilter
 * @memberOf fabric.Image.filters
 */
fabric.Image.filters.BaseFilter = fabric.util.createClass(
  /** @lends fabric.Image.filters.BaseFilter.prototype */ {
    /**
     * Filter type
     * @param {String} type
     * @default
     */
    type: "BaseFilter",

    /**
     * Returns object representation of an instance
     * @return {Object} Object representation of an instance
     */
    toObject: function () {
      return { type: this.type };
    },

    /**
     * Returns a JSON representation of an instance
     * @return {Object} JSON
     */
    toJSON: function () {
      // delegate, not alias
      return this.toObject();
    }
  }
);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * Brightness filter class
   * @class fabric.Image.filters.Brightness
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.Brightness({
   *   brightness: 200
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.Brightness = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Brightness.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Brightness",

      /**
       * Constructor
       * @memberOf fabric.Image.filters.Brightness.prototype
       * @param {Object} [options] Options object
       * @param {Number} [options.brightness=0] Value to brighten the image up (0..255)
       */
      initialize: function (options) {
        options = options || {};
        this.brightness = options.brightness || 0;
      },

      /**
       * Applies filter to canvas element
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          brightness = this.brightness;

        for (var i = 0, len = data.length; i < len; i += 4) {
          data[i] += brightness;
          data[i + 1] += brightness;
          data[i + 2] += brightness;
        }

        context.putImageData(imageData, 0, 0);
      },

      /**
       * Returns object representation of an instance
       * @return {Object} Object representation of an instance
       */
      toObject: function () {
        return extend(this.callSuper("toObject"), {
          brightness: this.brightness
        });
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness
   */
  fabric.Image.filters.Brightness.fromObject = function (object) {
    return new fabric.Image.filters.Brightness(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * Adapted from <a href="http://www.html5rocks.com/en/tutorials/canvas/imagefilters/">html5rocks article</a>
   * @class fabric.Image.filters.Convolute
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example <caption>Sharpen filter</caption>
   * var filter = new fabric.Image.filters.Convolute({
   *   matrix: [ 0, -1,  0,
   *            -1,  5, -1,
   *             0, -1,  0 ]
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   * @example <caption>Blur filter</caption>
   * var filter = new fabric.Image.filters.Convolute({
   *   matrix: [ 1/9, 1/9, 1/9,
   *             1/9, 1/9, 1/9,
   *             1/9, 1/9, 1/9 ]
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   * @example <caption>Emboss filter</caption>
   * var filter = new fabric.Image.filters.Convolute({
   *   matrix: [ 1,   1,  1,
   *             1, 0.7, -1,
   *            -1,  -1, -1 ]
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   * @example <caption>Emboss filter with opaqueness</caption>
   * var filter = new fabric.Image.filters.Convolute({
   *   opaque: true,
   *   matrix: [ 1,   1,  1,
   *             1, 0.7, -1,
   *            -1,  -1, -1 ]
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.Convolute = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Convolute.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Convolute",

      /**
       * Constructor
       * @memberOf fabric.Image.filters.Convolute.prototype
       * @param {Object} [options] Options object
       * @param {Boolean} [options.opaque=false] Opaque value (true/false)
       * @param {Array} [options.matrix] Filter matrix
       */
      initialize: function (options) {
        options = options || {};

        this.opaque = options.opaque;
        this.matrix = options.matrix || [0, 0, 0, 0, 1, 0, 0, 0, 0];

        var canvasEl = fabric.util.createCanvasElement();
        this.tmpCtx = canvasEl.getContext("2d");
      },

      /**
       * @private
       */
      _createImageData: function (w, h) {
        return this.tmpCtx.createImageData(w, h);
      },

      /**
       * Applies filter to canvas element
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var weights = this.matrix,
          context = canvasEl.getContext("2d"),
          pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
          side = Math.round(Math.sqrt(weights.length)),
          halfSide = Math.floor(side / 2),
          src = pixels.data,
          sw = pixels.width,
          sh = pixels.height,
          // pad output by the convolution matrix
          w = sw,
          h = sh,
          output = this._createImageData(w, h),
          dst = output.data,
          // go through the destination image pixels
          alphaFac = this.opaque ? 1 : 0;

        for (var y = 0; y < h; y++) {
          for (var x = 0; x < w; x++) {
            var sy = y,
              sx = x,
              dstOff = (y * w + x) * 4,
              // calculate the weighed sum of the source image pixels that
              // fall under the convolution matrix
              r = 0,
              g = 0,
              b = 0,
              a = 0;

            for (var cy = 0; cy < side; cy++) {
              for (var cx = 0; cx < side; cx++) {
                var scy = sy + cy - halfSide,
                  scx = sx + cx - halfSide;

                /* jshint maxdepth:5 */
                if (scy < 0 || scy > sh || scx < 0 || scx > sw) continue;

                var srcOff = (scy * sw + scx) * 4,
                  wt = weights[cy * side + cx];

                r += src[srcOff] * wt;
                g += src[srcOff + 1] * wt;
                b += src[srcOff + 2] * wt;
                a += src[srcOff + 3] * wt;
              }
            }
            dst[dstOff] = r;
            dst[dstOff + 1] = g;
            dst[dstOff + 2] = b;
            dst[dstOff + 3] = a + alphaFac * (255 - a);
          }
        }

        context.putImageData(output, 0, 0);
      },

      /**
       * Returns object representation of an instance
       * @return {Object} Object representation of an instance
       */
      toObject: function () {
        return extend(this.callSuper("toObject"), {
          opaque: this.opaque,
          matrix: this.matrix
        });
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute
   */
  fabric.Image.filters.Convolute.fromObject = function (object) {
    return new fabric.Image.filters.Convolute(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * GradientTransparency filter class
   * @class fabric.Image.filters.GradientTransparency
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.GradientTransparency({
   *   threshold: 200
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.GradientTransparency = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.GradientTransparency.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "GradientTransparency",

      /**
       * Constructor
       * @memberOf fabric.Image.filters.GradientTransparency.prototype
       * @param {Object} [options] Options object
       * @param {Number} [options.threshold=100] Threshold value
       */
      initialize: function (options) {
        options = options || {};
        this.threshold = options.threshold || 100;
      },

      /**
       * Applies filter to canvas element
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          threshold = this.threshold,
          total = data.length;

        for (var i = 0, len = data.length; i < len; i += 4) {
          data[i + 3] = threshold + (255 * (total - i)) / total;
        }

        context.putImageData(imageData, 0, 0);
      },

      /**
       * Returns object representation of an instance
       * @return {Object} Object representation of an instance
       */
      toObject: function () {
        return extend(this.callSuper("toObject"), {
          threshold: this.threshold
        });
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency
   */
  fabric.Image.filters.GradientTransparency.fromObject = function (object) {
    return new fabric.Image.filters.GradientTransparency(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {});

  /**
   * Grayscale image filter class
   * @class fabric.Image.filters.Grayscale
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.Grayscale();
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.Grayscale = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Grayscale.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Grayscale",

      /**
       * Applies filter to canvas element
       * @memberOf fabric.Image.filters.Grayscale.prototype
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          len = imageData.width * imageData.height * 4,
          index = 0,
          average;

        while (index < len) {
          average = (data[index] + data[index + 1] + data[index + 2]) / 3;
          data[index] = average;
          data[index + 1] = average;
          data[index + 2] = average;
          index += 4;
        }

        context.putImageData(imageData, 0, 0);
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale
   */
  fabric.Image.filters.Grayscale.fromObject = function () {
    return new fabric.Image.filters.Grayscale();
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {});

  /**
   * Invert filter class
   * @class fabric.Image.filters.Invert
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.Invert();
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.Invert = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Invert.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Invert",

      /**
       * Applies filter to canvas element
       * @memberOf fabric.Image.filters.Invert.prototype
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          iLen = data.length,
          i;

        for (i = 0; i < iLen; i += 4) {
          data[i] = 255 - data[i];
          data[i + 1] = 255 - data[i + 1];
          data[i + 2] = 255 - data[i + 2];
        }

        context.putImageData(imageData, 0, 0);
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert
   */
  fabric.Image.filters.Invert.fromObject = function () {
    return new fabric.Image.filters.Invert();
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * Mask filter class
   * See http://resources.aleph-1.com/mask/
   * @class fabric.Image.filters.Mask
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition
   */
  fabric.Image.filters.Mask = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Mask.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Mask",

      /**
       * Constructor
       * @memberOf fabric.Image.filters.Mask.prototype
       * @param {Object} [options] Options object
       * @param {fabric.Image} [options.mask] Mask image object
       * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3)
       */
      initialize: function (options) {
        options = options || {};

        this.mask = options.mask;
        this.channel =
          [0, 1, 2, 3].indexOf(options.channel) > -1 ? options.channel : 0;
      },

      /**
       * Applies filter to canvas element
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        if (!this.mask) return;

        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          maskEl = this.mask.getElement(),
          maskCanvasEl = fabric.util.createCanvasElement(),
          channel = this.channel,
          i,
          iLen = imageData.width * imageData.height * 4;

        maskCanvasEl.width = maskEl.width;
        maskCanvasEl.height = maskEl.height;

        maskCanvasEl
          .getContext("2d")
          .drawImage(maskEl, 0, 0, maskEl.width, maskEl.height);

        var maskImageData = maskCanvasEl
            .getContext("2d")
            .getImageData(0, 0, maskEl.width, maskEl.height),
          maskData = maskImageData.data;

        for (i = 0; i < iLen; i += 4) {
          data[i + 3] = maskData[i + channel];
        }

        context.putImageData(imageData, 0, 0);
      },

      /**
       * Returns object representation of an instance
       * @return {Object} Object representation of an instance
       */
      toObject: function () {
        return extend(this.callSuper("toObject"), {
          mask: this.mask.toObject(),
          channel: this.channel
        });
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @param {Function} [callback] Callback to invoke when a mask filter instance is created
   */
  fabric.Image.filters.Mask.fromObject = function (object, callback) {
    fabric.util.loadImage(object.mask.src, function (img) {
      object.mask = new fabric.Image(img, object.mask);
      callback && callback(new fabric.Image.filters.Mask(object));
    });
  };

  /**
   * Indicates that instances of this type are async
   * @static
   * @type Boolean
   * @default
   */
  fabric.Image.filters.Mask.async = true;
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * Noise filter class
   * @class fabric.Image.filters.Noise
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.Noise({
   *   noise: 700
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.Noise = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Noise.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Noise",

      /**
       * Constructor
       * @memberOf fabric.Image.filters.Noise.prototype
       * @param {Object} [options] Options object
       * @param {Number} [options.noise=0] Noise value
       */
      initialize: function (options) {
        options = options || {};
        this.noise = options.noise || 0;
      },

      /**
       * Applies filter to canvas element
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          noise = this.noise,
          rand;

        for (var i = 0, len = data.length; i < len; i += 4) {
          rand = (0.5 - Math.random()) * noise;

          data[i] += rand;
          data[i + 1] += rand;
          data[i + 2] += rand;
        }

        context.putImageData(imageData, 0, 0);
      },

      /**
       * Returns object representation of an instance
       * @return {Object} Object representation of an instance
       */
      toObject: function () {
        return extend(this.callSuper("toObject"), {
          noise: this.noise
        });
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise
   */
  fabric.Image.filters.Noise.fromObject = function (object) {
    return new fabric.Image.filters.Noise(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * Pixelate filter class
   * @class fabric.Image.filters.Pixelate
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.Pixelate({
   *   blocksize: 8
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.Pixelate = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Pixelate.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Pixelate",

      /**
       * Constructor
       * @memberOf fabric.Image.filters.Pixelate.prototype
       * @param {Object} [options] Options object
       * @param {Number} [options.blocksize=4] Blocksize for pixelate
       */
      initialize: function (options) {
        options = options || {};
        this.blocksize = options.blocksize || 4;
      },

      /**
       * Applies filter to canvas element
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          iLen = imageData.height,
          jLen = imageData.width,
          index,
          i,
          j,
          r,
          g,
          b,
          a;

        for (i = 0; i < iLen; i += this.blocksize) {
          for (j = 0; j < jLen; j += this.blocksize) {
            index = i * 4 * jLen + j * 4;

            r = data[index];
            g = data[index + 1];
            b = data[index + 2];
            a = data[index + 3];

            /*
           blocksize: 4

           [1,x,x,x,1]
           [x,x,x,x,1]
           [x,x,x,x,1]
           [x,x,x,x,1]
           [1,1,1,1,1]
           */

            for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) {
              for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) {
                index = _i * 4 * jLen + _j * 4;
                data[index] = r;
                data[index + 1] = g;
                data[index + 2] = b;
                data[index + 3] = a;
              }
            }
          }
        }

        context.putImageData(imageData, 0, 0);
      },

      /**
       * Returns object representation of an instance
       * @return {Object} Object representation of an instance
       */
      toObject: function () {
        return extend(this.callSuper("toObject"), {
          blocksize: this.blocksize
        });
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate
   */
  fabric.Image.filters.Pixelate.fromObject = function (object) {
    return new fabric.Image.filters.Pixelate(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * Remove white filter class
   * @class fabric.Image.filters.RemoveWhite
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.RemoveWhite({
   *   threshold: 40,
   *   distance: 140
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.RemoveWhite = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.RemoveWhite.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "RemoveWhite",

      /**
       * Constructor
       * @memberOf fabric.Image.filters.RemoveWhite.prototype
       * @param {Object} [options] Options object
       * @param {Number} [options.threshold=30] Threshold value
       * @param {Number} [options.distance=20] Distance value
       */
      initialize: function (options) {
        options = options || {};
        this.threshold = options.threshold || 30;
        this.distance = options.distance || 20;
      },

      /**
       * Applies filter to canvas element
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          threshold = this.threshold,
          distance = this.distance,
          limit = 255 - threshold,
          abs = Math.abs,
          r,
          g,
          b;

        for (var i = 0, len = data.length; i < len; i += 4) {
          r = data[i];
          g = data[i + 1];
          b = data[i + 2];

          if (
            r > limit &&
            g > limit &&
            b > limit &&
            abs(r - g) < distance &&
            abs(r - b) < distance &&
            abs(g - b) < distance
          ) {
            data[i + 3] = 1;
          }
        }

        context.putImageData(imageData, 0, 0);
      },

      /**
       * Returns object representation of an instance
       * @return {Object} Object representation of an instance
       */
      toObject: function () {
        return extend(this.callSuper("toObject"), {
          threshold: this.threshold,
          distance: this.distance
        });
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite
   */
  fabric.Image.filters.RemoveWhite.fromObject = function (object) {
    return new fabric.Image.filters.RemoveWhite(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {});

  /**
   * Sepia filter class
   * @class fabric.Image.filters.Sepia
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.Sepia();
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.Sepia = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Sepia.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Sepia",

      /**
       * Applies filter to canvas element
       * @memberOf fabric.Image.filters.Sepia.prototype
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          iLen = data.length,
          i,
          avg;

        for (i = 0; i < iLen; i += 4) {
          avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2];
          data[i] = avg + 100;
          data[i + 1] = avg + 50;
          data[i + 2] = avg + 255;
        }

        context.putImageData(imageData, 0, 0);
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia
   */
  fabric.Image.filters.Sepia.fromObject = function () {
    return new fabric.Image.filters.Sepia();
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {});

  /**
   * Sepia2 filter class
   * @class fabric.Image.filters.Sepia2
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.Sepia2();
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.Sepia2 = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Sepia2.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Sepia2",

      /**
       * Applies filter to canvas element
       * @memberOf fabric.Image.filters.Sepia.prototype
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          iLen = data.length,
          i,
          r,
          g,
          b;

        for (i = 0; i < iLen; i += 4) {
          r = data[i];
          g = data[i + 1];
          b = data[i + 2];

          data[i] = (r * 0.393 + g * 0.769 + b * 0.189) / 1.351;
          data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168) / 1.203;
          data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131) / 2.14;
        }

        context.putImageData(imageData, 0, 0);
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2
   */
  fabric.Image.filters.Sepia2.fromObject = function () {
    return new fabric.Image.filters.Sepia2();
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * Tint filter class
   * Adapted from <a href="https://github.com/mezzoblue/PaintbrushJS">https://github.com/mezzoblue/PaintbrushJS</a>
   * @class fabric.Image.filters.Tint
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
   * @example <caption>Tint filter with hex color and opacity</caption>
   * var filter = new fabric.Image.filters.Tint({
   *   color: '#3513B0',
   *   opacity: 0.5
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   * @example <caption>Tint filter with rgba color</caption>
   * var filter = new fabric.Image.filters.Tint({
   *   color: 'rgba(53, 21, 176, 0.5)'
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.Tint = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Tint.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Tint",

      /**
       * Constructor
       * @memberOf fabric.Image.filters.Tint.prototype
       * @param {Object} [options] Options object
       * @param {String} [options.color=#000000] Color to tint the image with
       * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1)
       */
      initialize: function (options) {
        options = options || {};

        this.color = options.color || "#000000";
        this.opacity =
          typeof options.opacity !== "undefined"
            ? options.opacity
            : new fabric.Color(this.color).getAlpha();
      },

      /**
       * Applies filter to canvas element
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          iLen = data.length,
          i,
          tintR,
          tintG,
          tintB,
          r,
          g,
          b,
          alpha1,
          source;

        source = new fabric.Color(this.color).getSource();

        tintR = source[0] * this.opacity;
        tintG = source[1] * this.opacity;
        tintB = source[2] * this.opacity;

        alpha1 = 1 - this.opacity;

        for (i = 0; i < iLen; i += 4) {
          r = data[i];
          g = data[i + 1];
          b = data[i + 2];

          // alpha compositing
          data[i] = tintR + r * alpha1;
          data[i + 1] = tintG + g * alpha1;
          data[i + 2] = tintB + b * alpha1;
        }

        context.putImageData(imageData, 0, 0);
      },

      /**
       * Returns object representation of an instance
       * @return {Object} Object representation of an instance
       */
      toObject: function () {
        return extend(this.callSuper("toObject"), {
          color: this.color,
          opacity: this.opacity
        });
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint
   */
  fabric.Image.filters.Tint.fromObject = function (object) {
    return new fabric.Image.filters.Tint(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * Multiply filter class
   * Adapted from <a href="http://www.laurenscorijn.com/articles/colormath-basics">http://www.laurenscorijn.com/articles/colormath-basics</a>
   * @class fabric.Image.filters.Multiply
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @example <caption>Multiply filter with hex color</caption>
   * var filter = new fabric.Image.filters.Multiply({
   *   color: '#F0F'
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   * @example <caption>Multiply filter with rgb color</caption>
   * var filter = new fabric.Image.filters.Multiply({
   *   color: 'rgb(53, 21, 176)'
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  fabric.Image.filters.Multiply = fabric.util.createClass(
    fabric.Image.filters.BaseFilter,
    /** @lends fabric.Image.filters.Multiply.prototype */ {
      /**
       * Filter type
       * @param {String} type
       * @default
       */
      type: "Multiply",

      /**
       * Constructor
       * @memberOf fabric.Image.filters.Multiply.prototype
       * @param {Object} [options] Options object
       * @param {String} [options.color=#000000] Color to multiply the image pixels with
       */
      initialize: function (options) {
        options = options || {};

        this.color = options.color || "#000000";
      },

      /**
       * Applies filter to canvas element
       * @param {Object} canvasEl Canvas element to apply filter to
       */
      applyTo: function (canvasEl) {
        var context = canvasEl.getContext("2d"),
          imageData = context.getImageData(
            0,
            0,
            canvasEl.width,
            canvasEl.height
          ),
          data = imageData.data,
          iLen = data.length,
          i,
          source;

        source = new fabric.Color(this.color).getSource();

        for (i = 0; i < iLen; i += 4) {
          data[i] *= source[0] / 255;
          data[i + 1] *= source[1] / 255;
          data[i + 2] *= source[2] / 255;
        }

        context.putImageData(imageData, 0, 0);
      },

      /**
       * Returns object representation of an instance
       * @return {Object} Object representation of an instance
       */
      toObject: function () {
        return extend(this.callSuper("toObject"), {
          color: this.color
        });
      }
    }
  );

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply
   */
  fabric.Image.filters.Multiply.fromObject = function (object) {
    return new fabric.Image.filters.Multiply(object);
  };
})(typeof exports !== "undefined" ? exports : this);

(function (global) {
  "use strict";

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend,
    clone = fabric.util.object.clone,
    toFixed = fabric.util.toFixed,
    supportsLineDash = fabric.StaticCanvas.supports("setLineDash");

  if (fabric.Text) {
    fabric.warn("fabric.Text is already defined");
    return;
  }

  var stateProperties = fabric.Object.prototype.stateProperties.concat();
  stateProperties.push(
    "fontFamily",
    "fontWeight",
    "fontSize",
    "text",
    "textDecoration",
    "textAlign",
    "fontStyle",
    "lineHeight",
    "textBackgroundColor",
    "useNative",
    "path"
  );

  /**
   * Text class
   * @class fabric.Text
   * @extends fabric.Object
   * @return {fabric.Text} thisArg
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text}
   * @see {@link fabric.Text#initialize} for constructor definition
   */
  fabric.Text = fabric.util.createClass(
    fabric.Object,
    /** @lends fabric.Text.prototype */ {
      /**
       * Properties which when set cause object to change dimensions
       * @type Object
       * @private
       */
      _dimensionAffectingProps: {
        fontSize: true,
        fontWeight: true,
        fontFamily: true,
        textDecoration: true,
        fontStyle: true,
        lineHeight: true,
        stroke: true,
        strokeWidth: true,
        text: true
      },

      /**
       * @private
       */
      _reNewline: /\r?\n/,

      /**
       * Retrieves object's fontSize
       * @method getFontSize
       * @memberOf fabric.Text.prototype
       * @return {String} Font size (in pixels)
       */

      /**
       * Sets object's fontSize
       * @method setFontSize
       * @memberOf fabric.Text.prototype
       * @param {Number} fontSize Font size (in pixels)
       * @return {fabric.Text}
       * @chainable
       */

      /**
       * Retrieves object's fontWeight
       * @method getFontWeight
       * @memberOf fabric.Text.prototype
       * @return {(String|Number)} Font weight
       */

      /**
       * Sets object's fontWeight
       * @method setFontWeight
       * @memberOf fabric.Text.prototype
       * @param {(Number|String)} fontWeight Font weight
       * @return {fabric.Text}
       * @chainable
       */

      /**
       * Retrieves object's fontFamily
       * @method getFontFamily
       * @memberOf fabric.Text.prototype
       * @return {String} Font family
       */

      /**
       * Sets object's fontFamily
       * @method setFontFamily
       * @memberOf fabric.Text.prototype
       * @param {String} fontFamily Font family
       * @return {fabric.Text}
       * @chainable
       */

      /**
       * Retrieves object's text
       * @method getText
       * @memberOf fabric.Text.prototype
       * @return {String} text
       */

      /**
       * Sets object's text
       * @method setText
       * @memberOf fabric.Text.prototype
       * @param {String} text Text
       * @return {fabric.Text}
       * @chainable
       */

      /**
       * Retrieves object's textDecoration
       * @method getTextDecoration
       * @memberOf fabric.Text.prototype
       * @return {String} Text decoration
       */

      /**
       * Sets object's textDecoration
       * @method setTextDecoration
       * @memberOf fabric.Text.prototype
       * @param {String} textDecoration Text decoration
       * @return {fabric.Text}
       * @chainable
       */

      /**
       * Retrieves object's fontStyle
       * @method getFontStyle
       * @memberOf fabric.Text.prototype
       * @return {String} Font style
       */

      /**
       * Sets object's fontStyle
       * @method setFontStyle
       * @memberOf fabric.Text.prototype
       * @param {String} fontStyle Font style
       * @return {fabric.Text}
       * @chainable
       */

      /**
       * Retrieves object's lineHeight
       * @method getLineHeight
       * @memberOf fabric.Text.prototype
       * @return {Number} Line height
       */

      /**
       * Sets object's lineHeight
       * @method setLineHeight
       * @memberOf fabric.Text.prototype
       * @param {Number} lineHeight Line height
       * @return {fabric.Text}
       * @chainable
       */

      /**
       * Retrieves object's textAlign
       * @method getTextAlign
       * @memberOf fabric.Text.prototype
       * @return {String} Text alignment
       */

      /**
       * Sets object's textAlign
       * @method setTextAlign
       * @memberOf fabric.Text.prototype
       * @param {String} textAlign Text alignment
       * @return {fabric.Text}
       * @chainable
       */

      /**
       * Retrieves object's textBackgroundColor
       * @method getTextBackgroundColor
       * @memberOf fabric.Text.prototype
       * @return {String} Text background color
       */

      /**
       * Sets object's textBackgroundColor
       * @method setTextBackgroundColor
       * @memberOf fabric.Text.prototype
       * @param {String} textBackgroundColor Text background color
       * @return {fabric.Text}
       * @chainable
       */

      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "text",

      /**
       * Font size (in pixels)
       * @type Number
       * @default
       */
      fontSize: 40,

      /**
       * Font weight (e.g. bold, normal, 400, 600, 800)
       * @type {(Number|String)}
       * @default
       */
      fontWeight: "normal",

      /**
       * Font family
       * @type String
       * @default
       */
      fontFamily: "Times New Roman",

      /**
       * Text decoration Possible values: "", "underline", "overline" or "line-through".
       * @type String
       * @default
       */
      textDecoration: "",

      /**
       * Text alignment. Possible values: "left", "center", or "right".
       * @type String
       * @default
       */
      textAlign: "left",

      /**
       * Font style . Possible values: "", "normal", "italic" or "oblique".
       * @type String
       * @default
       */
      fontStyle: "",

      /**
       * Line height
       * @type Number
       * @default
       */
      lineHeight: 1.3,

      /**
       * Background color of text lines
       * @type String
       * @default
       */
      textBackgroundColor: "",

      /**
       * URL of a font file, when using Cufon
       * @type String | null
       * @default
       */
      path: null,

      /**
       * Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used)
       * @type Boolean
       * @default
       */
      useNative: true,

      /**
       * List of properties to consider when checking if
       * state of an object is changed ({@link fabric.Object#hasStateChanged})
       * as well as for history (undo/redo) purposes
       * @type Array
       */
      stateProperties: stateProperties,

      /**
       * When defined, an object is rendered via stroke and this property specifies its color.
       * <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6
       * @type String
       * @default
       */
      stroke: null,

      /**
       * Shadow object representing shadow of this shape.
       * <b>Backwards incompatibility note:</b> This property was named "textShadow" (String) until v1.2.11
       * @type fabric.Shadow
       * @default
       */
      shadow: null,

      /**
       * Constructor
       * @param {String} text Text string
       * @param {Object} [options] Options object
       * @return {fabric.Text} thisArg
       */
      initialize: function (text, options) {
        options = options || {};

        this.text = text;
        this.__skipDimension = true;
        this.setOptions(options);
        this.__skipDimension = false;
        this._initDimensions();
      },

      /**
       * Renders text object on offscreen canvas, so that it would get dimensions
       * @private
       */
      _initDimensions: function () {
        if (this.__skipDimension) return;
        var canvasEl = fabric.util.createCanvasElement();
        this._render(canvasEl.getContext("2d"));
      },

      /**
       * Returns string representation of an instance
       * @return {String} String representation of text object
       */
      toString: function () {
        return (
          "#<fabric.Text (" +
          this.complexity() +
          '): { "text": "' +
          this.text +
          '", "fontFamily": "' +
          this.fontFamily +
          '" }>'
        );
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function (ctx) {
        if (typeof Cufon === "undefined" || this.useNative === true) {
          this._renderViaNative(ctx);
        } else {
          this._renderViaCufon(ctx);
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderViaNative: function (ctx) {
        var textLines = this.text.split(this._reNewline);

        this._setTextStyles(ctx);

        this.width = this._getTextWidth(ctx, textLines);
        this.height = this._getTextHeight(ctx, textLines);

        this.clipTo && fabric.util.clipContext(this, ctx);

        this._renderTextBackground(ctx, textLines);
        this._translateForTextAlign(ctx);
        this._renderText(ctx, textLines);

        if (this.textAlign !== "left" && this.textAlign !== "justify") {
          ctx.restore();
        }

        this._renderTextDecoration(ctx, textLines);
        this.clipTo && ctx.restore();

        this._setBoundaries(ctx, textLines);
        this._totalLineHeight = 0;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderText: function (ctx, textLines) {
        ctx.save();
        this._setShadow(ctx);
        this._renderTextFill(ctx, textLines);
        this._renderTextStroke(ctx, textLines);
        this._removeShadow(ctx);
        ctx.restore();
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _translateForTextAlign: function (ctx) {
        if (this.textAlign !== "left" && this.textAlign !== "justify") {
          ctx.save();
          ctx.translate(
            this.textAlign === "center" ? this.width / 2 : this.width,
            0
          );
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines Array of all text lines
       */
      _setBoundaries: function (ctx, textLines) {
        this._boundaries = [];

        for (var i = 0, len = textLines.length; i < len; i++) {
          var lineWidth = this._getLineWidth(ctx, textLines[i]),
            lineLeftOffset = this._getLineLeftOffset(lineWidth);

          this._boundaries.push({
            height: this.fontSize * this.lineHeight,
            width: lineWidth,
            left: lineLeftOffset
          });
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _setTextStyles: function (ctx) {
        this._setFillStyles(ctx);
        this._setStrokeStyles(ctx);
        ctx.textBaseline = "alphabetic";
        if (!this.skipTextAlign) {
          ctx.textAlign = this.textAlign;
        }
        ctx.font = this._getFontDeclaration();
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines Array of all text lines
       * @return {Number} Height of fabric.Text object
       */
      _getTextHeight: function (ctx, textLines) {
        return this.fontSize * textLines.length * this.lineHeight;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines Array of all text lines
       * @return {Number} Maximum width of fabric.Text object
       */
      _getTextWidth: function (ctx, textLines) {
        var maxWidth = ctx.measureText(textLines[0] || "|").width;

        for (var i = 1, len = textLines.length; i < len; i++) {
          var currentLineWidth = ctx.measureText(textLines[i]).width;
          if (currentLineWidth > maxWidth) {
            maxWidth = currentLineWidth;
          }
        }
        return maxWidth;
      },

      /**
       * @private
       * @param {String} method Method name ("fillText" or "strokeText")
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {String} chars Chars to render
       * @param {Number} left Left position of text
       * @param {Number} top Top position of text
       */
      _renderChars: function (method, ctx, chars, left, top) {
        ctx[method](chars, left, top);
      },

      /**
       * @private
       * @param {String} method Method name ("fillText" or "strokeText")
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {String} line Text to render
       * @param {Number} left Left position of text
       * @param {Number} top Top position of text
       * @param {Number} lineIndex Index of a line in a text
       */
      _renderTextLine: function (method, ctx, line, left, top, lineIndex) {
        // lift the line by quarter of fontSize
        top -= this.fontSize / 4;

        // short-circuit
        if (this.textAlign !== "justify") {
          this._renderChars(method, ctx, line, left, top, lineIndex);
          return;
        }

        var lineWidth = ctx.measureText(line).width,
          totalWidth = this.width;

        if (totalWidth > lineWidth) {
          // stretch the line
          var words = line.split(/\s+/),
            wordsWidth = ctx.measureText(line.replace(/\s+/g, "")).width,
            widthDiff = totalWidth - wordsWidth,
            numSpaces = words.length - 1,
            spaceWidth = widthDiff / numSpaces,
            leftOffset = 0;

          for (var i = 0, len = words.length; i < len; i++) {
            this._renderChars(
              method,
              ctx,
              words[i],
              left + leftOffset,
              top,
              lineIndex
            );
            leftOffset += ctx.measureText(words[i]).width + spaceWidth;
          }
        } else {
          this._renderChars(method, ctx, line, left, top, lineIndex);
        }
      },

      /**
       * @private
       * @return {Number} Left offset
       */
      _getLeftOffset: function () {
        if (fabric.isLikelyNode) {
          return 0;
        }
        return -this.width / 2;
      },

      /**
       * @private
       * @return {Number} Top offset
       */
      _getTopOffset: function () {
        return -this.height / 2;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines Array of all text lines
       */
      _renderTextFill: function (ctx, textLines) {
        if (!this.fill && !this._skipFillStrokeCheck) return;

        this._boundaries = [];
        var lineHeights = 0;

        for (var i = 0, len = textLines.length; i < len; i++) {
          var heightOfLine = this._getHeightOfLine(ctx, i, textLines);
          lineHeights += heightOfLine;

          this._renderTextLine(
            "fillText",
            ctx,
            textLines[i],
            this._getLeftOffset(),
            this._getTopOffset() + lineHeights,
            i
          );
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines Array of all text lines
       */
      _renderTextStroke: function (ctx, textLines) {
        if (
          (!this.stroke || this.strokeWidth === 0) &&
          !this._skipFillStrokeCheck
        )
          return;

        var lineHeights = 0;

        ctx.save();
        if (this.strokeDashArray) {
          // Spec requires the concatenation of two copies the dash list when the number of elements is odd
          if (1 & this.strokeDashArray.length) {
            this.strokeDashArray.push.apply(
              this.strokeDashArray,
              this.strokeDashArray
            );
          }
          supportsLineDash && ctx.setLineDash(this.strokeDashArray);
        }

        ctx.beginPath();
        for (var i = 0, len = textLines.length; i < len; i++) {
          var heightOfLine = this._getHeightOfLine(ctx, i, textLines);
          lineHeights += heightOfLine;

          this._renderTextLine(
            "strokeText",
            ctx,
            textLines[i],
            this._getLeftOffset(),
            this._getTopOffset() + lineHeights,
            i
          );
        }
        ctx.closePath();
        ctx.restore();
      },

      _getHeightOfLine: function () {
        return this.fontSize * this.lineHeight;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines Array of all text lines
       */
      _renderTextBackground: function (ctx, textLines) {
        this._renderTextBoxBackground(ctx);
        this._renderTextLinesBackground(ctx, textLines);
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderTextBoxBackground: function (ctx) {
        if (!this.backgroundColor) return;

        ctx.save();
        ctx.fillStyle = this.backgroundColor;

        ctx.fillRect(
          this._getLeftOffset(),
          this._getTopOffset(),
          this.width,
          this.height
        );

        ctx.restore();
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines Array of all text lines
       */
      _renderTextLinesBackground: function (ctx, textLines) {
        if (!this.textBackgroundColor) return;

        ctx.save();
        ctx.fillStyle = this.textBackgroundColor;

        for (var i = 0, len = textLines.length; i < len; i++) {
          if (textLines[i] !== "") {
            var lineWidth = this._getLineWidth(ctx, textLines[i]),
              lineLeftOffset = this._getLineLeftOffset(lineWidth);

            ctx.fillRect(
              this._getLeftOffset() + lineLeftOffset,
              this._getTopOffset() + i * this.fontSize * this.lineHeight,
              lineWidth,
              this.fontSize * this.lineHeight
            );
          }
        }
        ctx.restore();
      },

      /**
       * @private
       * @param {Number} lineWidth Width of text line
       * @return {Number} Line left offset
       */
      _getLineLeftOffset: function (lineWidth) {
        if (this.textAlign === "center") {
          return (this.width - lineWidth) / 2;
        }
        if (this.textAlign === "right") {
          return this.width - lineWidth;
        }
        return 0;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {String} line Text line
       * @return {Number} Line width
       */
      _getLineWidth: function (ctx, line) {
        return this.textAlign === "justify"
          ? this.width
          : ctx.measureText(line).width;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines Array of all text lines
       */
      _renderTextDecoration: function (ctx, textLines) {
        if (!this.textDecoration) return;

        // var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2;
        var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2,
          _this = this;

        /** @ignore */
        function renderLinesAtOffset(offset) {
          for (var i = 0, len = textLines.length; i < len; i++) {
            var lineWidth = _this._getLineWidth(ctx, textLines[i]),
              lineLeftOffset = _this._getLineLeftOffset(lineWidth);

            ctx.fillRect(
              _this._getLeftOffset() + lineLeftOffset,
              ~~(
                offset +
                i * _this._getHeightOfLine(ctx, i, textLines) -
                halfOfVerticalBox
              ),
              lineWidth,
              1
            );
          }
        }

        if (this.textDecoration.indexOf("underline") > -1) {
          renderLinesAtOffset(this.fontSize * this.lineHeight);
        }
        if (this.textDecoration.indexOf("line-through") > -1) {
          renderLinesAtOffset(
            this.fontSize * this.lineHeight - this.fontSize / 2
          );
        }
        if (this.textDecoration.indexOf("overline") > -1) {
          renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize);
        }
      },

      /**
       * @private
       */
      _getFontDeclaration: function () {
        return [
          // node-canvas needs "weight style", while browsers need "style weight"
          fabric.isLikelyNode ? this.fontWeight : this.fontStyle,
          fabric.isLikelyNode ? this.fontStyle : this.fontWeight,
          this.fontSize + "px",
          fabric.isLikelyNode ? '"' + this.fontFamily + '"' : this.fontFamily
        ].join(" ");
      },

      /**
       * Renders text instance on a specified context
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      render: function (ctx, noTransform) {
        // do not render if object is not visible
        if (!this.visible) return;

        ctx.save();
        this._transform(ctx, noTransform);

        var m = this.transformMatrix;
        var isInPathGroup = this.group && this.group.type === "path-group";
        if (isInPathGroup) {
          ctx.translate(-this.group.width / 2, -this.group.height / 2);
        }
        if (m) {
          ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
        }
        if (isInPathGroup) {
          ctx.translate(this.left, this.top);
        }
        this._render(ctx);
        ctx.restore();
      },

      /**
       * Returns object representation of an instance
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} Object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        var object = extend(this.callSuper("toObject", propertiesToInclude), {
          text: this.text,
          fontSize: this.fontSize,
          fontWeight: this.fontWeight,
          fontFamily: this.fontFamily,
          fontStyle: this.fontStyle,
          lineHeight: this.lineHeight,
          textDecoration: this.textDecoration,
          textAlign: this.textAlign,
          path: this.path,
          textBackgroundColor: this.textBackgroundColor,
          useNative: this.useNative
        });
        if (!this.includeDefaultValues) {
          this._removeDefaultValues(object);
        }
        return object;
      },

      /* _TO_SVG_START_ */
      /**
       * Returns SVG representation of an instance
       * @param {Function} [reviver] Method for further parsing of svg representation.
       * @return {String} svg representation of an instance
       */
      toSVG: function (reviver) {
        var markup = [],
          textLines = this.text.split(this._reNewline),
          offsets = this._getSVGLeftTopOffsets(textLines),
          textAndBg = this._getSVGTextAndBg(
            offsets.lineTop,
            offsets.textLeft,
            textLines
          ),
          shadowSpans = this._getSVGShadows(offsets.lineTop, textLines);

        // move top offset by an ascent
        offsets.textTop += this._fontAscent
          ? (this._fontAscent / 5) * this.lineHeight
          : 0;

        this._wrapSVGTextAndBg(markup, textAndBg, shadowSpans, offsets);

        return reviver ? reviver(markup.join("")) : markup.join("");
      },

      /**
       * @private
       */
      _getSVGLeftTopOffsets: function (textLines) {
        var lineTop = this.useNative
            ? this.fontSize * this.lineHeight
            : -this._fontAscent - (this._fontAscent / 5) * this.lineHeight,
          textLeft = -(this.width / 2),
          textTop = this.useNative
            ? this.fontSize - 1
            : this.height / 2 -
              textLines.length * this.fontSize -
              this._totalLineHeight;

        return {
          textLeft: textLeft,
          textTop: textTop,
          lineTop: lineTop
        };
      },

      /**
       * @private
       */
      _wrapSVGTextAndBg: function (markup, textAndBg, shadowSpans, offsets) {
        markup.push(
          '<g transform="',
          this.getSvgTransform(),
          '">',
          textAndBg.textBgRects.join(""),
          "<text ",
          this.fontFamily
            ? 'font-family="' + this.fontFamily.replace(/"/g, "'") + '" '
            : "",
          this.fontSize ? 'font-size="' + this.fontSize + '" ' : "",
          this.fontStyle ? 'font-style="' + this.fontStyle + '" ' : "",
          this.fontWeight ? 'font-weight="' + this.fontWeight + '" ' : "",
          this.textDecoration
            ? 'text-decoration="' + this.textDecoration + '" '
            : "",
          'style="',
          this.getSvgStyles(),
          '" ',
          /* svg starts from left/bottom corner so we normalize height */
          'transform="translate(',
          toFixed(offsets.textLeft, 2),
          " ",
          toFixed(offsets.textTop, 2),
          ')">',
          shadowSpans.join(""),
          textAndBg.textSpans.join(""),
          "</text>",
          "</g>"
        );
      },

      /**
       * @private
       * @param {Number} lineHeight
       * @param {Array} textLines Array of all text lines
       * @return {Array}
       */
      _getSVGShadows: function (lineHeight, textLines) {
        var shadowSpans = [],
          i,
          len,
          lineTopOffsetMultiplier = 1;

        if (!this.shadow || !this._boundaries) {
          return shadowSpans;
        }

        for (i = 0, len = textLines.length; i < len; i++) {
          if (textLines[i] !== "") {
            var lineLeftOffset =
              this._boundaries && this._boundaries[i]
                ? this._boundaries[i].left
                : 0;
            shadowSpans.push(
              '<tspan x="',
              toFixed(
                lineLeftOffset + lineTopOffsetMultiplier + this.shadow.offsetX,
                2
              ),
              i === 0 || this.useNative ? '" y' : '" dy',
              '="',
              toFixed(
                this.useNative
                  ? lineHeight * i - this.height / 2 + this.shadow.offsetY
                  : lineHeight + (i === 0 ? this.shadow.offsetY : 0),
                2
              ),
              '" ',
              this._getFillAttributes(this.shadow.color),
              ">",
              fabric.util.string.escapeXml(textLines[i]),
              "</tspan>"
            );
            lineTopOffsetMultiplier = 1;
          } else {
            // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier
            // prevents empty tspans
            lineTopOffsetMultiplier++;
          }
        }

        return shadowSpans;
      },

      /**
       * @private
       * @param {Number} lineHeight
       * @param {Number} textLeftOffset Text left offset
       * @param {Array} textLines Array of all text lines
       * @return {Object}
       */
      _getSVGTextAndBg: function (lineHeight, textLeftOffset, textLines) {
        var textSpans = [],
          textBgRects = [],
          lineTopOffsetMultiplier = 1;

        // bounding-box background
        this._setSVGBg(textBgRects);

        // text and text-background
        for (var i = 0, len = textLines.length; i < len; i++) {
          if (textLines[i] !== "") {
            this._setSVGTextLineText(
              textLines[i],
              i,
              textSpans,
              lineHeight,
              lineTopOffsetMultiplier,
              textBgRects
            );
            lineTopOffsetMultiplier = 1;
          } else {
            // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier
            // prevents empty tspans
            lineTopOffsetMultiplier++;
          }

          if (!this.textBackgroundColor || !this._boundaries) continue;

          this._setSVGTextLineBg(textBgRects, i, textLeftOffset, lineHeight);
        }

        return {
          textSpans: textSpans,
          textBgRects: textBgRects
        };
      },

      _setSVGTextLineText: function (
        textLine,
        i,
        textSpans,
        lineHeight,
        lineTopOffsetMultiplier
      ) {
        var lineLeftOffset =
          this._boundaries && this._boundaries[i]
            ? toFixed(this._boundaries[i].left, 2)
            : 0;

        textSpans.push(
          '<tspan x="',
          lineLeftOffset,
          '" ',
          i === 0 || this.useNative ? "y" : "dy",
          '="',
          toFixed(
            this.useNative
              ? lineHeight * i - this.height / 2
              : lineHeight * lineTopOffsetMultiplier,
            2
          ),
          '" ',
          // doing this on <tspan> elements since setting opacity
          // on containing <text> one doesn't work in Illustrator
          this._getFillAttributes(this.fill),
          ">",
          fabric.util.string.escapeXml(textLine),
          "</tspan>"
        );
      },

      _setSVGTextLineBg: function (textBgRects, i, textLeftOffset, lineHeight) {
        textBgRects.push(
          "<rect ",
          this._getFillAttributes(this.textBackgroundColor),
          ' x="',
          toFixed(textLeftOffset + this._boundaries[i].left, 2),
          '" y="',
          /* an offset that seems to straighten things out */
          toFixed(lineHeight * i - this.height / 2, 2),
          '" width="',
          toFixed(this._boundaries[i].width, 2),
          '" height="',
          toFixed(this._boundaries[i].height, 2),
          '"></rect>'
        );
      },

      _setSVGBg: function (textBgRects) {
        if (this.backgroundColor && this._boundaries) {
          textBgRects.push(
            "<rect ",
            this._getFillAttributes(this.backgroundColor),
            ' x="',
            toFixed(-this.width / 2, 2),
            '" y="',
            toFixed(-this.height / 2, 2),
            '" width="',
            toFixed(this.width, 2),
            '" height="',
            toFixed(this.height, 2),
            '"></rect>'
          );
        }
      },

      /**
       * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values
       * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1
       *
       * @private
       * @param {Any} value
       * @return {String}
       */
      _getFillAttributes: function (value) {
        var fillColor =
          value && typeof value === "string" ? new fabric.Color(value) : "";
        if (
          !fillColor ||
          !fillColor.getSource() ||
          fillColor.getAlpha() === 1
        ) {
          return 'fill="' + value + '"';
        }
        return (
          'opacity="' +
          fillColor.getAlpha() +
          '" fill="' +
          fillColor.setAlpha(1).toRgb() +
          '"'
        );
      },
      /* _TO_SVG_END_ */

      /**
       * Sets specified property to a specified value
       * @param {String} key
       * @param {Any} value
       * @return {fabric.Text} thisArg
       * @chainable
       */
      _set: function (key, value) {
        if (key === "fontFamily" && this.path) {
          this.path = this.path.replace(
            /(.*?)([^\/]*)(\.font\.js)/,
            "$1" + value + "$3"
          );
        }
        this.callSuper("_set", key, value);

        if (key in this._dimensionAffectingProps) {
          this._initDimensions();
          this.setCoords();
        }
      },

      /**
       * Returns complexity of an instance
       * @return {Number} complexity
       */
      complexity: function () {
        return 1;
      }
    }
  );

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement})
   * @static
   * @memberOf fabric.Text
   * @see: http://www.w3.org/TR/SVG/text.html#TextElement
   */
  fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(
    "x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(
      " "
    )
  );

  /**
   * Default SVG font size
   * @static
   * @memberOf fabric.Text
   */
  fabric.Text.DEFAULT_SVG_FONT_SIZE = 16;

  /**
   * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>)
   * @static
   * @memberOf fabric.Text
   * @param {SVGElement} element Element to parse
   * @param {Object} [options] Options object
   * @return {fabric.Text} Instance of fabric.Text
   */
  fabric.Text.fromElement = function (element, options) {
    if (!element) {
      return null;
    }

    var parsedAttributes = fabric.parseAttributes(
      element,
      fabric.Text.ATTRIBUTE_NAMES
    );
    options = fabric.util.object.extend(
      options ? fabric.util.object.clone(options) : {},
      parsedAttributes
    );

    if ("dx" in parsedAttributes) {
      options.left += parsedAttributes.dx;
    }
    if ("dy" in parsedAttributes) {
      options.top += parsedAttributes.dy;
    }
    if (!("fontSize" in options)) {
      options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
    }

    if (!options.originX) {
      options.originX = "left";
    }

    var text = new fabric.Text(element.textContent, options);

    /*
      Adjust positioning:
        x/y attributes in SVG correspond to the bottom-left corner of text bounding box
        top/left properties in Fabric correspond to center point of text bounding box
    */
    var offX = 0;
    if (text.originX === "left") {
      offX = text.getWidth() / 2;
    }
    if (text.originX === "right") {
      offX = -text.getWidth() / 2;
    }
    text.set({
      left: text.getLeft() + offX,
      top: text.getTop() - text.getHeight() / 2
    });

    return text;
  };
  /* _FROM_SVG_END_ */

  /**
   * Returns fabric.Text instance from an object representation
   * @static
   * @memberOf fabric.Text
   * @param {Object} object Object to create an instance from
   * @return {fabric.Text} Instance of fabric.Text
   */
  fabric.Text.fromObject = function (object) {
    return new fabric.Text(object.text, clone(object));
  };

  fabric.util.createAccessors(fabric.Text);
})(typeof exports !== "undefined" ? exports : this);

(function () {
  var clone = fabric.util.object.clone;

  /**
   * IText class (introduced in <b>v1.4</b>) Events are also fired with "text:"
   * prefix when observing canvas.
   * @class fabric.IText
   * @extends fabric.Text
   * @mixes fabric.Observable
   *
   * @fires changed
   * @fires selection:changed
   * @fires editing:entered
   * @fires editing:exited
   *
   * @return {fabric.IText} thisArg
   * @see {@link fabric.IText#initialize} for constructor definition
   *
   * <p>Supported key combinations:</p>
   * <pre>
   *   Move cursor:                    left, right, up, down
   *   Select character:               shift + left, shift + right
   *   Select text vertically:         shift + up, shift + down
   *   Move cursor by word:            alt + left, alt + right
   *   Select words:                   shift + alt + left, shift + alt + right
   *   Move cursor to line start/end:  cmd + left, cmd + right
   *   Select till start/end of line:  cmd + shift + left, cmd + shift + right
   *   Jump to start/end of text:      cmd + up, cmd + down
   *   Select till start/end of text:  cmd + shift + up, cmd + shift + down
   *   Delete character:               backspace
   *   Delete word:                    alt + backspace
   *   Delete line:                    cmd + backspace
   *   Forward delete:                 delete
   *   Copy text:                      ctrl/cmd + c
   *   Paste text:                     ctrl/cmd + v
   *   Cut text:                       ctrl/cmd + x
   *   Select entire text:             ctrl/cmd + a
   * </pre>
   *
   * <p>Supported mouse/touch combination</p>
   * <pre>
   *   Position cursor:                click/touch
   *   Create selection:               click/touch & drag
   *   Create selection:               click & shift + click
   *   Select word:                    double click
   *   Select line:                    triple click
   * </pre>
   */
  fabric.IText = fabric.util.createClass(
    fabric.Text,
    fabric.Observable,
    /** @lends fabric.IText.prototype */ {
      /**
       * Type of an object
       * @type String
       * @default
       */
      type: "i-text",

      /**
       * Index where text selection starts (or where cursor is when there is no selection)
       * @type Nubmer
       * @default
       */
      selectionStart: 0,

      /**
       * Index where text selection ends
       * @type Nubmer
       * @default
       */
      selectionEnd: 0,

      /**
       * Color of text selection
       * @type String
       * @default
       */
      selectionColor: "rgba(17,119,255,0.3)",

      /**
       * Indicates whether text is in editing mode
       * @type Boolean
       * @default
       */
      isEditing: false,

      /**
       * Indicates whether a text can be edited
       * @type Boolean
       * @default
       */
      editable: true,

      /**
       * Border color of text object while it's in editing mode
       * @type String
       * @default
       */
      editingBorderColor: "rgba(102,153,255,0.25)",

      /**
       * Width of cursor (in px)
       * @type Number
       * @default
       */
      cursorWidth: 2,

      /**
       * Color of default cursor (when not overwritten by character style)
       * @type String
       * @default
       */
      cursorColor: "#333",

      /**
       * Delay between cursor blink (in ms)
       * @type Number
       * @default
       */
      cursorDelay: 1000,

      /**
       * Duration of cursor fadein (in ms)
       * @type Number
       * @default
       */
      cursorDuration: 600,

      /**
       * Object containing character styles
       * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line)
       * @type Object
       * @default
       */
      styles: null,

      /**
       * Indicates whether internal text char widths can be cached
       * @type Boolean
       * @default
       */
      caching: true,

      /**
       * @private
       * @type Boolean
       * @default
       */
      _skipFillStrokeCheck: true,

      /**
       * @private
       */
      _reSpace: /\s|\n/,

      /**
       * @private
       */
      _fontSizeFraction: 4,

      /**
       * @private
       */
      _currentCursorOpacity: 0,

      /**
       * @private
       */
      _selectionDirection: null,

      /**
       * @private
       */
      _abortCursorAnimation: false,

      /**
       * @private
       */
      _charWidthsCache: {},

      /**
       * Constructor
       * @param {String} text Text string
       * @param {Object} [options] Options object
       * @return {fabric.IText} thisArg
       */
      initialize: function (text, options) {
        this.styles = options ? options.styles || {} : {};
        this.callSuper("initialize", text, options);
        this.initBehavior();

        fabric.IText.instances.push(this);

        // caching
        this.__lineWidths = {};
        this.__lineHeights = {};
        this.__lineOffsets = {};
      },

      /**
       * Returns true if object has no styling
       */
      isEmptyStyles: function () {
        if (!this.styles) return true;
        var obj = this.styles;

        for (var p1 in obj) {
          for (var p2 in obj[p1]) {
            /*jshint unused:false */
            for (var p3 in obj[p1][p2]) {
              return false;
            }
          }
        }
        return true;
      },

      /**
       * Sets selection start (left boundary of a selection)
       * @param {Number} index Index to set selection start to
       */
      setSelectionStart: function (index) {
        if (this.selectionStart !== index) {
          this.fire("selection:changed");
          this.canvas &&
            this.canvas.fire("text:selection:changed", { target: this });
        }
        this.selectionStart = index;
        this.hiddenTextarea && (this.hiddenTextarea.selectionStart = index);
      },

      /**
       * Sets selection end (right boundary of a selection)
       * @param {Number} index Index to set selection end to
       */
      setSelectionEnd: function (index) {
        if (this.selectionEnd !== index) {
          this.fire("selection:changed");
          this.canvas &&
            this.canvas.fire("text:selection:changed", { target: this });
        }
        this.selectionEnd = index;
        this.hiddenTextarea && (this.hiddenTextarea.selectionEnd = index);
      },

      /**
       * Gets style of a current selection/cursor (at the start position)
       * @param {Number} [startIndex] Start index to get styles at
       * @param {Number} [endIndex] End index to get styles at
       * @return {Object} styles Style object at a specified (or current) index
       */
      getSelectionStyles: function (startIndex, endIndex) {
        if (arguments.length === 2) {
          var styles = [];
          for (var i = startIndex; i < endIndex; i++) {
            styles.push(this.getSelectionStyles(i));
          }
          return styles;
        }

        var loc = this.get2DCursorLocation(startIndex);
        if (this.styles[loc.lineIndex]) {
          return this.styles[loc.lineIndex][loc.charIndex] || {};
        }

        return {};
      },

      /**
       * Sets style of a current selection
       * @param {Object} [styles] Styles object
       * @return {fabric.IText} thisArg
       * @chainable
       */
      setSelectionStyles: function (styles) {
        if (this.selectionStart === this.selectionEnd) {
          this._extendStyles(this.selectionStart, styles);
        } else {
          for (var i = this.selectionStart; i < this.selectionEnd; i++) {
            this._extendStyles(i, styles);
          }
        }
        return this;
      },

      /**
       * @private
       */
      _extendStyles: function (index, styles) {
        var loc = this.get2DCursorLocation(index);

        if (!this.styles[loc.lineIndex]) {
          this.styles[loc.lineIndex] = {};
        }
        if (!this.styles[loc.lineIndex][loc.charIndex]) {
          this.styles[loc.lineIndex][loc.charIndex] = {};
        }

        fabric.util.object.extend(
          this.styles[loc.lineIndex][loc.charIndex],
          styles
        );
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function (ctx) {
        this.callSuper("_render", ctx);
        this.ctx = ctx;
        this.isEditing && this.renderCursorOrSelection();
      },

      /**
       * Renders cursor or selection (depending on what exists)
       */
      renderCursorOrSelection: function () {
        if (!this.active) return;

        var chars = this.text.split(""),
          boundaries;

        if (this.selectionStart === this.selectionEnd) {
          boundaries = this._getCursorBoundaries(chars, "cursor");
          this.renderCursor(boundaries);
        } else {
          boundaries = this._getCursorBoundaries(chars, "selection");
          this.renderSelection(chars, boundaries);
        }
      },

      /**
       * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start)
       * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used.
       */
      get2DCursorLocation: function (selectionStart) {
        if (typeof selectionStart === "undefined") {
          selectionStart = this.selectionStart;
        }
        var textBeforeCursor = this.text.slice(0, selectionStart),
          linesBeforeCursor = textBeforeCursor.split(this._reNewline);

        return {
          lineIndex: linesBeforeCursor.length - 1,
          charIndex: linesBeforeCursor[linesBeforeCursor.length - 1].length
        };
      },

      /**
       * Returns complete style of char at the current cursor
       * @param {Number} lineIndex Line index
       * @param {Number} charIndex Char index
       * @return {Object} Character style
       */
      getCurrentCharStyle: function (lineIndex, charIndex) {
        var style =
          this.styles[lineIndex] &&
          this.styles[lineIndex][charIndex === 0 ? 0 : charIndex - 1];

        return {
          fontSize: (style && style.fontSize) || this.fontSize,
          fill: (style && style.fill) || this.fill,
          textBackgroundColor:
            (style && style.textBackgroundColor) || this.textBackgroundColor,
          textDecoration:
            (style && style.textDecoration) || this.textDecoration,
          fontFamily: (style && style.fontFamily) || this.fontFamily,
          fontWeight: (style && style.fontWeight) || this.fontWeight,
          fontStyle: (style && style.fontStyle) || this.fontStyle,
          stroke: (style && style.stroke) || this.stroke,
          strokeWidth: (style && style.strokeWidth) || this.strokeWidth
        };
      },

      /**
       * Returns fontSize of char at the current cursor
       * @param {Number} lineIndex Line index
       * @param {Number} charIndex Char index
       * @return {Number} Character font size
       */
      getCurrentCharFontSize: function (lineIndex, charIndex) {
        return (
          (this.styles[lineIndex] &&
            this.styles[lineIndex][charIndex === 0 ? 0 : charIndex - 1] &&
            this.styles[lineIndex][charIndex === 0 ? 0 : charIndex - 1]
              .fontSize) ||
          this.fontSize
        );
      },

      /**
       * Returns color (fill) of char at the current cursor
       * @param {Number} lineIndex Line index
       * @param {Number} charIndex Char index
       * @return {String} Character color (fill)
       */
      getCurrentCharColor: function (lineIndex, charIndex) {
        return (
          (this.styles[lineIndex] &&
            this.styles[lineIndex][charIndex === 0 ? 0 : charIndex - 1] &&
            this.styles[lineIndex][charIndex === 0 ? 0 : charIndex - 1].fill) ||
          this.cursorColor
        );
      },

      /**
       * Returns cursor boundaries (left, top, leftOffset, topOffset)
       * @private
       * @param {Array} chars Array of characters
       * @param {String} typeOfBoundaries
       */
      _getCursorBoundaries: function (chars, typeOfBoundaries) {
        var cursorLocation = this.get2DCursorLocation(),
          textLines = this.text.split(this._reNewline),
          // left/top are left/top of entire text box
          // leftOffset/topOffset are offset from that left/top point of a text box

          left = Math.round(this._getLeftOffset()),
          top = -this.height / 2,
          offsets = this._getCursorBoundariesOffsets(
            chars,
            typeOfBoundaries,
            cursorLocation,
            textLines
          );

        return {
          left: left,
          top: top,
          leftOffset: offsets.left + offsets.lineLeft,
          topOffset: offsets.top
        };
      },

      /**
       * @private
       */
      _getCursorBoundariesOffsets: function (
        chars,
        typeOfBoundaries,
        cursorLocation,
        textLines
      ) {
        var lineLeftOffset = 0,
          lineIndex = 0,
          charIndex = 0,
          leftOffset = 0,
          topOffset =
            typeOfBoundaries === "cursor"
              ? // selection starts at the very top of the line,
                // whereas cursor starts at the padding created by line height
                this._getHeightOfLine(this.ctx, 0) -
                this.getCurrentCharFontSize(
                  cursorLocation.lineIndex,
                  cursorLocation.charIndex
                )
              : 0;

        for (var i = 0; i < this.selectionStart; i++) {
          if (chars[i] === "\n") {
            leftOffset = 0;
            var index = lineIndex + (typeOfBoundaries === "cursor" ? 1 : 0);
            topOffset += this._getCachedLineHeight(index);

            lineIndex++;
            charIndex = 0;
          } else {
            leftOffset += this._getWidthOfChar(
              this.ctx,
              chars[i],
              lineIndex,
              charIndex
            );
            charIndex++;
          }

          lineLeftOffset = this._getCachedLineOffset(lineIndex, textLines);
        }

        this._clearCache();

        return {
          top: topOffset,
          left: leftOffset,
          lineLeft: lineLeftOffset
        };
      },

      /**
       * @private
       */
      _clearCache: function () {
        this.__lineWidths = {};
        this.__lineHeights = {};
        this.__lineOffsets = {};
      },

      /**
       * @private
       */
      _getCachedLineHeight: function (index) {
        return (
          this.__lineHeights[index] ||
          (this.__lineHeights[index] = this._getHeightOfLine(this.ctx, index))
        );
      },

      /**
       * @private
       */
      _getCachedLineWidth: function (lineIndex, textLines) {
        return (
          this.__lineWidths[lineIndex] ||
          (this.__lineWidths[lineIndex] = this._getWidthOfLine(
            this.ctx,
            lineIndex,
            textLines
          ))
        );
      },

      /**
       * @private
       */
      _getCachedLineOffset: function (lineIndex, textLines) {
        var widthOfLine = this._getCachedLineWidth(lineIndex, textLines);

        return (
          this.__lineOffsets[lineIndex] ||
          (this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine))
        );
      },

      /**
       * Renders cursor
       * @param {Object} boundaries
       */
      renderCursor: function (boundaries) {
        var ctx = this.ctx;

        ctx.save();

        var cursorLocation = this.get2DCursorLocation(),
          lineIndex = cursorLocation.lineIndex,
          charIndex = cursorLocation.charIndex,
          charHeight = this.getCurrentCharFontSize(lineIndex, charIndex),
          leftOffset =
            lineIndex === 0 && charIndex === 0
              ? this._getCachedLineOffset(
                  lineIndex,
                  this.text.split(this._reNewline)
                )
              : boundaries.leftOffset;

        ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex);
        ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity;

        ctx.fillRect(
          boundaries.left + leftOffset,
          boundaries.top + boundaries.topOffset,
          this.cursorWidth / this.scaleX,
          charHeight
        );

        ctx.restore();
      },

      /**
       * Renders text selection
       * @param {Array} chars Array of characters
       * @param {Object} boundaries Object with left/top/leftOffset/topOffset
       */
      renderSelection: function (chars, boundaries) {
        var ctx = this.ctx;

        ctx.save();

        ctx.fillStyle = this.selectionColor;

        var start = this.get2DCursorLocation(this.selectionStart),
          end = this.get2DCursorLocation(this.selectionEnd),
          startLine = start.lineIndex,
          endLine = end.lineIndex,
          textLines = this.text.split(this._reNewline);

        for (var i = startLine; i <= endLine; i++) {
          var lineOffset = this._getCachedLineOffset(i, textLines) || 0,
            lineHeight = this._getCachedLineHeight(i),
            boxWidth = 0;

          if (i === startLine) {
            for (var j = 0, len = textLines[i].length; j < len; j++) {
              if (
                j >= start.charIndex &&
                (i !== endLine || j < end.charIndex)
              ) {
                boxWidth += this._getWidthOfChar(ctx, textLines[i][j], i, j);
              }
              if (j < start.charIndex) {
                lineOffset += this._getWidthOfChar(ctx, textLines[i][j], i, j);
              }
            }
          } else if (i > startLine && i < endLine) {
            boxWidth += this._getCachedLineWidth(i, textLines) || 5;
          } else if (i === endLine) {
            for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) {
              boxWidth += this._getWidthOfChar(ctx, textLines[i][j2], i, j2);
            }
          }

          ctx.fillRect(
            boundaries.left + lineOffset,
            boundaries.top + boundaries.topOffset,
            boxWidth,
            lineHeight
          );

          boundaries.topOffset += lineHeight;
        }
        ctx.restore();
      },

      /**
       * @private
       * @param {String} method
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderChars: function (method, ctx, line, left, top, lineIndex) {
        if (this.isEmptyStyles()) {
          return this._renderCharsFast(method, ctx, line, left, top);
        }

        this.skipTextAlign = true;

        // set proper box offset
        left -=
          this.textAlign === "center"
            ? this.width / 2
            : this.textAlign === "right"
            ? this.width
            : 0;

        // set proper line offset
        var textLines = this.text.split(this._reNewline),
          lineWidth = this._getWidthOfLine(ctx, lineIndex, textLines),
          lineHeight = this._getHeightOfLine(ctx, lineIndex, textLines),
          lineLeftOffset = this._getLineLeftOffset(lineWidth),
          chars = line.split(""),
          prevStyle,
          charsToRender = "";

        left += lineLeftOffset || 0;

        ctx.save();

        for (var i = 0, len = chars.length; i <= len; i++) {
          prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i);
          var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1);

          if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) {
            this._renderChar(
              method,
              ctx,
              lineIndex,
              i - 1,
              charsToRender,
              left,
              top,
              lineHeight
            );
            charsToRender = "";
            prevStyle = thisStyle;
          }
          charsToRender += chars[i];
        }

        ctx.restore();
      },

      /**
       * @private
       * @param {String} method
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {String} line Content of the line
       * @param {Number} left Left coordinate
       * @param {Number} top Top coordinate
       */
      _renderCharsFast: function (method, ctx, line, left, top) {
        this.skipTextAlign = false;

        if (method === "fillText" && this.fill) {
          this.callSuper("_renderChars", method, ctx, line, left, top);
        }
        if (method === "strokeText" && this.stroke) {
          this.callSuper("_renderChars", method, ctx, line, left, top);
        }
      },

      /**
       * @private
       * @param {String} method
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Number} lineIndex
       * @param {Number} i
       * @param {String} _char
       * @param {Number} left Left coordinate
       * @param {Number} top Top coordinate
       * @param {Number} lineHeight Height of the line
       */
      _renderChar: function (
        method,
        ctx,
        lineIndex,
        i,
        _char,
        left,
        top,
        lineHeight
      ) {
        var decl, charWidth, charHeight;

        if (
          this.styles &&
          this.styles[lineIndex] &&
          (decl = this.styles[lineIndex][i])
        ) {
          var shouldStroke = decl.stroke || this.stroke,
            shouldFill = decl.fill || this.fill;

          ctx.save();
          charWidth = this._applyCharStylesGetWidth(
            ctx,
            _char,
            lineIndex,
            i,
            decl
          );
          charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i);

          if (shouldFill) {
            ctx.fillText(_char, left, top);
          }
          if (shouldStroke) {
            ctx.strokeText(_char, left, top);
          }

          this._renderCharDecoration(
            ctx,
            decl,
            left,
            top,
            charWidth,
            lineHeight,
            charHeight
          );
          ctx.restore();

          ctx.translate(charWidth, 0);
        } else {
          if (method === "strokeText" && this.stroke) {
            ctx[method](_char, left, top);
          }
          if (method === "fillText" && this.fill) {
            ctx[method](_char, left, top);
          }
          charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i);
          this._renderCharDecoration(
            ctx,
            null,
            left,
            top,
            charWidth,
            lineHeight
          );

          ctx.translate(ctx.measureText(_char).width, 0);
        }
      },

      /**
       * @private
       * @param {Object} prevStyle
       * @param {Object} thisStyle
       */
      _hasStyleChanged: function (prevStyle, thisStyle) {
        return (
          prevStyle.fill !== thisStyle.fill ||
          prevStyle.fontSize !== thisStyle.fontSize ||
          prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor ||
          prevStyle.textDecoration !== thisStyle.textDecoration ||
          prevStyle.fontFamily !== thisStyle.fontFamily ||
          prevStyle.fontWeight !== thisStyle.fontWeight ||
          prevStyle.fontStyle !== thisStyle.fontStyle ||
          prevStyle.stroke !== thisStyle.stroke ||
          prevStyle.strokeWidth !== thisStyle.strokeWidth
        );
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderCharDecoration: function (
        ctx,
        styleDeclaration,
        left,
        top,
        charWidth,
        lineHeight,
        charHeight
      ) {
        var textDecoration = styleDeclaration
            ? styleDeclaration.textDecoration || this.textDecoration
            : this.textDecoration,
          fontSize =
            (styleDeclaration ? styleDeclaration.fontSize : null) ||
            this.fontSize;

        if (!textDecoration) return;

        if (textDecoration.indexOf("underline") > -1) {
          this._renderCharDecorationAtOffset(
            ctx,
            left,
            top + this.fontSize / this._fontSizeFraction,
            charWidth,
            0,
            this.fontSize / 20
          );
        }
        if (textDecoration.indexOf("line-through") > -1) {
          this._renderCharDecorationAtOffset(
            ctx,
            left,
            top + this.fontSize / this._fontSizeFraction,
            charWidth,
            charHeight / 2,
            fontSize / 20
          );
        }
        if (textDecoration.indexOf("overline") > -1) {
          this._renderCharDecorationAtOffset(
            ctx,
            left,
            top,
            charWidth,
            lineHeight - this.fontSize / this._fontSizeFraction,
            this.fontSize / 20
          );
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _renderCharDecorationAtOffset: function (
        ctx,
        left,
        top,
        charWidth,
        offset,
        thickness
      ) {
        ctx.fillRect(left, top - offset, charWidth, thickness);
      },

      /**
       * @private
       * @param {String} method
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {String} line
       */
      _renderTextLine: function (method, ctx, line, left, top, lineIndex) {
        // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine
        top += this.fontSize / 4;
        this.callSuper(
          "_renderTextLine",
          method,
          ctx,
          line,
          left,
          top,
          lineIndex
        );
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines
       */
      _renderTextDecoration: function (ctx, textLines) {
        if (this.isEmptyStyles()) {
          return this.callSuper("_renderTextDecoration", ctx, textLines);
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines Array of all text lines
       */
      _renderTextLinesBackground: function (ctx, textLines) {
        if (!this.textBackgroundColor && !this.styles) return;

        ctx.save();

        if (this.textBackgroundColor) {
          ctx.fillStyle = this.textBackgroundColor;
        }

        var lineHeights = 0,
          fractionOfFontSize = this.fontSize / this._fontSizeFraction;

        for (var i = 0, len = textLines.length; i < len; i++) {
          var heightOfLine = this._getHeightOfLine(ctx, i, textLines);
          if (textLines[i] === "") {
            lineHeights += heightOfLine;
            continue;
          }

          var lineWidth = this._getWidthOfLine(ctx, i, textLines),
            lineLeftOffset = this._getLineLeftOffset(lineWidth);

          if (this.textBackgroundColor) {
            ctx.fillStyle = this.textBackgroundColor;

            ctx.fillRect(
              this._getLeftOffset() + lineLeftOffset,
              this._getTopOffset() + lineHeights + fractionOfFontSize,
              lineWidth,
              heightOfLine
            );
          }
          if (this.styles[i]) {
            for (var j = 0, jlen = textLines[i].length; j < jlen; j++) {
              if (
                this.styles[i] &&
                this.styles[i][j] &&
                this.styles[i][j].textBackgroundColor
              ) {
                var _char = textLines[i][j];

                ctx.fillStyle = this.styles[i][j].textBackgroundColor;

                ctx.fillRect(
                  this._getLeftOffset() +
                    lineLeftOffset +
                    this._getWidthOfCharsAt(ctx, i, j, textLines),
                  this._getTopOffset() + lineHeights + fractionOfFontSize,
                  this._getWidthOfChar(ctx, _char, i, j, textLines) + 1,
                  heightOfLine
                );
              }
            }
          }
          lineHeights += heightOfLine;
        }
        ctx.restore();
      },

      /**
       * @private
       */
      _getCacheProp: function (_char, styleDeclaration) {
        return (
          _char +
          styleDeclaration.fontFamily +
          styleDeclaration.fontSize +
          styleDeclaration.fontWeight +
          styleDeclaration.fontStyle +
          styleDeclaration.shadow
        );
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {String} _char
       * @param {Number} lineIndex
       * @param {Number} charIndex
       * @param {Object} [decl]
       */
      _applyCharStylesGetWidth: function (
        ctx,
        _char,
        lineIndex,
        charIndex,
        decl
      ) {
        var styleDeclaration =
          decl || (this.styles[lineIndex] && this.styles[lineIndex][charIndex]);

        if (styleDeclaration) {
          // cloning so that original style object is not polluted with following font declarations
          styleDeclaration = clone(styleDeclaration);
        } else {
          styleDeclaration = {};
        }

        this._applyFontStyles(styleDeclaration);

        var cacheProp = this._getCacheProp(_char, styleDeclaration);

        // short-circuit if no styles
        if (
          this.isEmptyStyles() &&
          this._charWidthsCache[cacheProp] &&
          this.caching
        ) {
          return this._charWidthsCache[cacheProp];
        }

        if (typeof styleDeclaration.shadow === "string") {
          styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow);
        }

        var fill = styleDeclaration.fill || this.fill;
        ctx.fillStyle = fill.toLive ? fill.toLive(ctx) : fill;

        if (styleDeclaration.stroke) {
          ctx.strokeStyle =
            styleDeclaration.stroke && styleDeclaration.stroke.toLive
              ? styleDeclaration.stroke.toLive(ctx)
              : styleDeclaration.stroke;
        }

        ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth;
        ctx.font = this._getFontDeclaration.call(styleDeclaration);
        this._setShadow.call(styleDeclaration, ctx);

        if (!this.caching) {
          return ctx.measureText(_char).width;
        }

        if (!this._charWidthsCache[cacheProp]) {
          this._charWidthsCache[cacheProp] = ctx.measureText(_char).width;
        }

        return this._charWidthsCache[cacheProp];
      },

      /**
       * @private
       * @param {Object} styleDeclaration
       */
      _applyFontStyles: function (styleDeclaration) {
        if (!styleDeclaration.fontFamily) {
          styleDeclaration.fontFamily = this.fontFamily;
        }
        if (!styleDeclaration.fontSize) {
          styleDeclaration.fontSize = this.fontSize;
        }
        if (!styleDeclaration.fontWeight) {
          styleDeclaration.fontWeight = this.fontWeight;
        }
        if (!styleDeclaration.fontStyle) {
          styleDeclaration.fontStyle = this.fontStyle;
        }
      },

      /**
       * @private
       * @param {Number} lineIndex
       * @param {Number} charIndex
       */
      _getStyleDeclaration: function (lineIndex, charIndex) {
        return this.styles[lineIndex] && this.styles[lineIndex][charIndex]
          ? clone(this.styles[lineIndex][charIndex])
          : {};
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _getWidthOfChar: function (ctx, _char, lineIndex, charIndex) {
        var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex);
        this._applyFontStyles(styleDeclaration);
        var cacheProp = this._getCacheProp(_char, styleDeclaration);

        if (this._charWidthsCache[cacheProp] && this.caching) {
          return this._charWidthsCache[cacheProp];
        } else if (ctx) {
          ctx.save();
          var width = this._applyCharStylesGetWidth(
            ctx,
            _char,
            lineIndex,
            charIndex
          );
          ctx.restore();
          return width;
        }
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _getHeightOfChar: function (ctx, _char, lineIndex, charIndex) {
        if (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) {
          return this.styles[lineIndex][charIndex].fontSize || this.fontSize;
        }
        return this.fontSize;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _getWidthOfCharAt: function (ctx, lineIndex, charIndex, lines) {
        lines = lines || this.text.split(this._reNewline);
        var _char = lines[lineIndex].split("")[charIndex];
        return this._getWidthOfChar(ctx, _char, lineIndex, charIndex);
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _getHeightOfCharAt: function (ctx, lineIndex, charIndex, lines) {
        lines = lines || this.text.split(this._reNewline);
        var _char = lines[lineIndex].split("")[charIndex];
        return this._getHeightOfChar(ctx, _char, lineIndex, charIndex);
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _getWidthOfCharsAt: function (ctx, lineIndex, charIndex, lines) {
        var width = 0;
        for (var i = 0; i < charIndex; i++) {
          width += this._getWidthOfCharAt(ctx, lineIndex, i, lines);
        }
        return width;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _getWidthOfLine: function (ctx, lineIndex, textLines) {
        // if (!this.styles[lineIndex]) {
        //   return this.callSuper('_getLineWidth', ctx, textLines[lineIndex]);
        // }
        return this._getWidthOfCharsAt(
          ctx,
          lineIndex,
          textLines[lineIndex].length,
          textLines
        );
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _getTextWidth: function (ctx, textLines) {
        if (this.isEmptyStyles()) {
          return this.callSuper("_getTextWidth", ctx, textLines);
        }

        var maxWidth = this._getWidthOfLine(ctx, 0, textLines);

        for (var i = 1, len = textLines.length; i < len; i++) {
          var currentLineWidth = this._getWidthOfLine(ctx, i, textLines);
          if (currentLineWidth > maxWidth) {
            maxWidth = currentLineWidth;
          }
        }
        return maxWidth;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _getHeightOfLine: function (ctx, lineIndex, textLines) {
        textLines = textLines || this.text.split(this._reNewline);

        var maxHeight = this._getHeightOfChar(
            ctx,
            textLines[lineIndex][0],
            lineIndex,
            0
          ),
          line = textLines[lineIndex],
          chars = line.split("");

        for (var i = 1, len = chars.length; i < len; i++) {
          var currentCharHeight = this._getHeightOfChar(
            ctx,
            chars[i],
            lineIndex,
            i
          );
          if (currentCharHeight > maxHeight) {
            maxHeight = currentCharHeight;
          }
        }

        return maxHeight * this.lineHeight;
      },

      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       * @param {Array} textLines Array of all text lines
       */
      _getTextHeight: function (ctx, textLines) {
        var height = 0;
        for (var i = 0, len = textLines.length; i < len; i++) {
          height += this._getHeightOfLine(ctx, i, textLines);
        }
        return height;
      },

      /**
       * @private
       */
      _getTopOffset: function () {
        var topOffset = fabric.Text.prototype._getTopOffset.call(this);
        return topOffset - this.fontSize / this._fontSizeFraction;
      },

      /**
       * This method is overwritten to account for different top offset
       * @private
       */
      _renderTextBoxBackground: function (ctx) {
        if (!this.backgroundColor) return;

        ctx.save();
        ctx.fillStyle = this.backgroundColor;

        ctx.fillRect(
          this._getLeftOffset(),
          this._getTopOffset() + this.fontSize / this._fontSizeFraction,
          this.width,
          this.height
        );

        ctx.restore();
      },

      /**
       * Returns object representation of an instance
       * @method toObject
       * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
       * @return {Object} object representation of an instance
       */
      toObject: function (propertiesToInclude) {
        return fabric.util.object.extend(
          this.callSuper("toObject", propertiesToInclude),
          {
            styles: clone(this.styles)
          }
        );
      }
    }
  );

  /**
   * Returns fabric.IText instance from an object representation
   * @static
   * @memberOf fabric.IText
   * @param {Object} object Object to create an instance from
   * @return {fabric.IText} instance of fabric.IText
   */
  fabric.IText.fromObject = function (object) {
    return new fabric.IText(object.text, clone(object));
  };

  /**
   * Contains all fabric.IText objects that have been created
   * @static
   * @memberof fabric.IText
   * @type Array
   */
  fabric.IText.instances = [];
})();

(function () {
  var clone = fabric.util.object.clone;

  fabric.util.object.extend(
    fabric.IText.prototype,
    /** @lends fabric.IText.prototype */ {
      /**
       * Initializes all the interactive behavior of IText
       */
      initBehavior: function () {
        this.initAddedHandler();
        this.initCursorSelectionHandlers();
        this.initDoubleClickSimulation();
      },

      /**
       * Initializes "selected" event handler
       */
      initSelectedHandler: function () {
        this.on("selected", function () {
          var _this = this;
          setTimeout(function () {
            _this.selected = true;
          }, 100);
        });
      },

      /**
       * Initializes "added" event handler
       */
      initAddedHandler: function () {
        this.on("added", function () {
          if (this.canvas && !this.canvas._hasITextHandlers) {
            this.canvas._hasITextHandlers = true;
            this._initCanvasHandlers();
          }
        });
      },

      /**
       * @private
       */
      _initCanvasHandlers: function () {
        this.canvas.on("selection:cleared", function () {
          fabric.IText.prototype.exitEditingOnOthers.call();
        });

        this.canvas.on("mouse:up", function () {
          fabric.IText.instances.forEach(function (obj) {
            obj.__isMousedown = false;
          });
        });

        this.canvas.on("object:selected", function (options) {
          fabric.IText.prototype.exitEditingOnOthers.call(options.target);
        });
      },

      /**
       * @private
       */
      _tick: function () {
        var _this = this;

        if (this._abortCursorAnimation) return;

        this.animate("_currentCursorOpacity", 1, {
          duration: this.cursorDuration,

          onComplete: function () {
            _this._onTickComplete();
          },

          onChange: function () {
            _this.canvas && _this.canvas.renderAll();
          },

          abort: function () {
            return _this._abortCursorAnimation;
          }
        });
      },

      /**
       * @private
       */
      _onTickComplete: function () {
        if (this._abortCursorAnimation) return;

        var _this = this;
        if (this._cursorTimeout1) {
          clearTimeout(this._cursorTimeout1);
        }
        this._cursorTimeout1 = setTimeout(function () {
          _this.animate("_currentCursorOpacity", 0, {
            duration: this.cursorDuration / 2,
            onComplete: function () {
              _this._tick();
            },
            onChange: function () {
              _this.canvas && _this.canvas.renderAll();
            },
            abort: function () {
              return _this._abortCursorAnimation;
            }
          });
        }, 100);
      },

      /**
       * Initializes delayed cursor
       */
      initDelayedCursor: function (restart) {
        var _this = this,
          delay = restart ? 0 : this.cursorDelay;

        if (restart) {
          this._abortCursorAnimation = true;
          clearTimeout(this._cursorTimeout1);
          this._currentCursorOpacity = 1;
          this.canvas && this.canvas.renderAll();
        }
        if (this._cursorTimeout2) {
          clearTimeout(this._cursorTimeout2);
        }
        this._cursorTimeout2 = setTimeout(function () {
          _this._abortCursorAnimation = false;
          _this._tick();
        }, delay);
      },

      /**
       * Aborts cursor animation and clears all timeouts
       */
      abortCursorAnimation: function () {
        this._abortCursorAnimation = true;

        clearTimeout(this._cursorTimeout1);
        clearTimeout(this._cursorTimeout2);

        this._currentCursorOpacity = 0;
        this.canvas && this.canvas.renderAll();

        var _this = this;
        setTimeout(function () {
          _this._abortCursorAnimation = false;
        }, 10);
      },

      /**
       * Selects entire text
       */
      selectAll: function () {
        this.selectionStart = 0;
        this.selectionEnd = this.text.length;
        this.fire("selection:changed");
        this.canvas &&
          this.canvas.fire("text:selection:changed", { target: this });
      },

      /**
       * Returns selected text
       * @return {String}
       */
      getSelectedText: function () {
        return this.text.slice(this.selectionStart, this.selectionEnd);
      },

      /**
       * Find new selection index representing start of current word according to current selection index
       * @param {Number} startFrom Surrent selection index
       * @return {Number} New selection index
       */
      findWordBoundaryLeft: function (startFrom) {
        var offset = 0,
          index = startFrom - 1;

        // remove space before cursor first
        if (this._reSpace.test(this.text.charAt(index))) {
          while (this._reSpace.test(this.text.charAt(index))) {
            offset++;
            index--;
          }
        }
        while (/\S/.test(this.text.charAt(index)) && index > -1) {
          offset++;
          index--;
        }

        return startFrom - offset;
      },

      /**
       * Find new selection index representing end of current word according to current selection index
       * @param {Number} startFrom Current selection index
       * @return {Number} New selection index
       */
      findWordBoundaryRight: function (startFrom) {
        var offset = 0,
          index = startFrom;

        // remove space after cursor first
        if (this._reSpace.test(this.text.charAt(index))) {
          while (this._reSpace.test(this.text.charAt(index))) {
            offset++;
            index++;
          }
        }
        while (/\S/.test(this.text.charAt(index)) && index < this.text.length) {
          offset++;
          index++;
        }

        return startFrom + offset;
      },

      /**
       * Find new selection index representing start of current line according to current selection index
       * @param {Number} startFrom Current selection index
       * @return {Number} New selection index
       */
      findLineBoundaryLeft: function (startFrom) {
        var offset = 0,
          index = startFrom - 1;

        while (!/\n/.test(this.text.charAt(index)) && index > -1) {
          offset++;
          index--;
        }

        return startFrom - offset;
      },

      /**
       * Find new selection index representing end of current line according to current selection index
       * @param {Number} startFrom Current selection index
       * @return {Number} New selection index
       */
      findLineBoundaryRight: function (startFrom) {
        var offset = 0,
          index = startFrom;

        while (
          !/\n/.test(this.text.charAt(index)) &&
          index < this.text.length
        ) {
          offset++;
          index++;
        }

        return startFrom + offset;
      },

      /**
       * Returns number of newlines in selected text
       * @return {Number} Number of newlines in selected text
       */
      getNumNewLinesInSelectedText: function () {
        var selectedText = this.getSelectedText(),
          numNewLines = 0;

        for (
          var i = 0, chars = selectedText.split(""), len = chars.length;
          i < len;
          i++
        ) {
          if (chars[i] === "\n") {
            numNewLines++;
          }
        }
        return numNewLines;
      },

      /**
       * Finds index corresponding to beginning or end of a word
       * @param {Number} selectionStart Index of a character
       * @param {Number} direction: 1 or -1
       * @return {Number} Index of the beginning or end of a word
       */
      searchWordBoundary: function (selectionStart, direction) {
        var index = this._reSpace.test(this.text.charAt(selectionStart))
            ? selectionStart - 1
            : selectionStart,
          _char = this.text.charAt(index),
          reNonWord = /[ \n\.,;!\?\-]/;

        while (
          !reNonWord.test(_char) &&
          index > 0 &&
          index < this.text.length
        ) {
          index += direction;
          _char = this.text.charAt(index);
        }
        if (reNonWord.test(_char) && _char !== "\n") {
          index += direction === 1 ? 0 : 1;
        }
        return index;
      },

      /**
       * Selects a word based on the index
       * @param {Number} selectionStart Index of a character
       */
      selectWord: function (selectionStart) {
        var newSelectionStart = this.searchWordBoundary(
            selectionStart,
            -1
          ) /* search backwards */,
          newSelectionEnd = this.searchWordBoundary(
            selectionStart,
            1
          ); /* search forward */

        this.setSelectionStart(newSelectionStart);
        this.setSelectionEnd(newSelectionEnd);
        this.initDelayedCursor(true);
      },

      /**
       * Selects a line based on the index
       * @param {Number} selectionStart Index of a character
       */
      selectLine: function (selectionStart) {
        var newSelectionStart = this.findLineBoundaryLeft(selectionStart),
          newSelectionEnd = this.findLineBoundaryRight(selectionStart);

        this.setSelectionStart(newSelectionStart);
        this.setSelectionEnd(newSelectionEnd);
        this.initDelayedCursor(true);
      },

      /**
       * Enters editing state
       * @return {fabric.IText} thisArg
       * @chainable
       */
      enterEditing: function () {
        if (this.isEditing || !this.editable) return;

        this.exitEditingOnOthers();

        this.isEditing = true;

        this.initHiddenTextarea();
        this._updateTextarea();
        this._saveEditingProps();
        this._setEditingProps();

        this._tick();
        this.canvas && this.canvas.renderAll();

        this.fire("editing:entered");
        this.canvas &&
          this.canvas.fire("text:editing:entered", { target: this });

        return this;
      },

      exitEditingOnOthers: function () {
        fabric.IText.instances.forEach(function (obj) {
          obj.selected = false;
          if (obj.isEditing) {
            obj.exitEditing();
          }
        }, this);
      },

      /**
       * @private
       */
      _setEditingProps: function () {
        this.hoverCursor = "text";

        if (this.canvas) {
          this.canvas.defaultCursor = this.canvas.moveCursor = "text";
        }

        this.borderColor = this.editingBorderColor;

        this.hasControls = this.selectable = false;
        this.lockMovementX = this.lockMovementY = true;
      },

      /**
       * @private
       */
      _updateTextarea: function () {
        if (!this.hiddenTextarea) return;

        this.hiddenTextarea.value = this.text;
        this.hiddenTextarea.selectionStart = this.selectionStart;
      },

      /**
       * @private
       */
      _saveEditingProps: function () {
        this._savedProps = {
          hasControls: this.hasControls,
          borderColor: this.borderColor,
          lockMovementX: this.lockMovementX,
          lockMovementY: this.lockMovementY,
          hoverCursor: this.hoverCursor,
          defaultCursor: this.canvas && this.canvas.defaultCursor,
          moveCursor: this.canvas && this.canvas.moveCursor
        };
      },

      /**
       * @private
       */
      _restoreEditingProps: function () {
        if (!this._savedProps) return;

        this.hoverCursor = this._savedProps.overCursor;
        this.hasControls = this._savedProps.hasControls;
        this.borderColor = this._savedProps.borderColor;
        this.lockMovementX = this._savedProps.lockMovementX;
        this.lockMovementY = this._savedProps.lockMovementY;

        if (this.canvas) {
          this.canvas.defaultCursor = this._savedProps.defaultCursor;
          this.canvas.moveCursor = this._savedProps.moveCursor;
        }
      },

      /**
       * Exits from editing state
       * @return {fabric.IText} thisArg
       * @chainable
       */
      exitEditing: function () {
        this.selected = false;
        this.isEditing = false;
        this.selectable = true;

        this.selectionEnd = this.selectionStart;
        this.hiddenTextarea &&
          this.canvas &&
          this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea);
        this.hiddenTextarea = null;

        this.abortCursorAnimation();
        this._restoreEditingProps();
        this._currentCursorOpacity = 0;

        this.fire("editing:exited");
        this.canvas &&
          this.canvas.fire("text:editing:exited", { target: this });

        return this;
      },

      /**
       * @private
       */
      _removeExtraneousStyles: function () {
        var textLines = this.text.split(this._reNewline);
        for (var prop in this.styles) {
          if (!textLines[prop]) {
            delete this.styles[prop];
          }
        }
      },

      /**
       * @private
       */
      _removeCharsFromTo: function (start, end) {
        var i = end;
        while (i !== start) {
          var prevIndex = this.get2DCursorLocation(i).charIndex;
          i--;

          var index = this.get2DCursorLocation(i).charIndex,
            isNewline = index > prevIndex;

          if (isNewline) {
            this.removeStyleObject(isNewline, i + 1);
          } else {
            this.removeStyleObject(
              this.get2DCursorLocation(i).charIndex === 0,
              i
            );
          }
        }

        this.text = this.text.slice(0, start) + this.text.slice(end);
      },

      /**
       * Inserts a character where cursor is (replacing selection if one exists)
       * @param {String} _chars Characters to insert
       */
      insertChars: function (_chars) {
        var isEndOfLine =
          this.text.slice(this.selectionStart, this.selectionStart + 1) ===
          "\n";

        this.text =
          this.text.slice(0, this.selectionStart) +
          _chars +
          this.text.slice(this.selectionEnd);

        if (this.selectionStart === this.selectionEnd) {
          this.insertStyleObjects(_chars, isEndOfLine, this.copiedStyles);
        }
        // else if (this.selectionEnd - this.selectionStart > 1) {
        // TODO: replace styles properly
        // console.log('replacing MORE than 1 char');
        // }

        this.selectionStart += _chars.length;
        this.selectionEnd = this.selectionStart;

        if (this.canvas) {
          // TODO: double renderAll gets rid of text box shift happenning sometimes
          // need to find out what exactly causes it and fix it
          this.canvas.renderAll().renderAll();
        }

        this.setCoords();
        this.fire("changed");
        this.canvas && this.canvas.fire("text:changed", { target: this });
      },

      /**
       * Inserts new style object
       * @param {Number} lineIndex Index of a line
       * @param {Number} charIndex Index of a char
       * @param {Boolean} isEndOfLine True if it's end of line
       */
      insertNewlineStyleObject: function (lineIndex, charIndex, isEndOfLine) {
        this.shiftLineStyles(lineIndex, +1);

        if (!this.styles[lineIndex + 1]) {
          this.styles[lineIndex + 1] = {};
        }

        var currentCharStyle = this.styles[lineIndex][charIndex - 1],
          newLineStyles = {};

        // if there's nothing after cursor,
        // we clone current char style onto the next (otherwise empty) line
        if (isEndOfLine) {
          newLineStyles[0] = clone(currentCharStyle);
          this.styles[lineIndex + 1] = newLineStyles;
        }
        // otherwise we clone styles of all chars
        // after cursor onto the next line, from the beginning
        else {
          for (var index in this.styles[lineIndex]) {
            if (parseInt(index, 10) >= charIndex) {
              newLineStyles[parseInt(index, 10) - charIndex] = this.styles[
                lineIndex
              ][index];
              // remove lines from the previous line since they're on a new line now
              delete this.styles[lineIndex][index];
            }
          }
          this.styles[lineIndex + 1] = newLineStyles;
        }
      },

      /**
       * Inserts style object for a given line/char index
       * @param {Number} lineIndex Index of a line
       * @param {Number} charIndex Index of a char
       * @param {Object} [style] Style object to insert, if given
       */
      insertCharStyleObject: function (lineIndex, charIndex, style) {
        var currentLineStyles = this.styles[lineIndex],
          currentLineStylesCloned = clone(currentLineStyles);

        if (charIndex === 0 && !style) {
          charIndex = 1;
        }

        // shift all char styles by 1 forward
        // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4
        for (var index in currentLineStylesCloned) {
          var numericIndex = parseInt(index, 10);
          if (numericIndex >= charIndex) {
            currentLineStyles[numericIndex + 1] =
              currentLineStylesCloned[numericIndex];
            //delete currentLineStyles[index];
          }
        }

        this.styles[lineIndex][charIndex] =
          style || clone(currentLineStyles[charIndex - 1]);
      },

      /**
       * Inserts style object(s)
       * @param {String} _chars Characters at the location where style is inserted
       * @param {Boolean} isEndOfLine True if it's end of line
       * @param {Array} [styles] Styles to insert
       */
      insertStyleObjects: function (_chars, isEndOfLine, styles) {
        // short-circuit
        if (this.isEmptyStyles()) return;

        var cursorLocation = this.get2DCursorLocation(),
          lineIndex = cursorLocation.lineIndex,
          charIndex = cursorLocation.charIndex;

        if (!this.styles[lineIndex]) {
          this.styles[lineIndex] = {};
        }

        if (_chars === "\n") {
          this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine);
        } else {
          if (styles) {
            this._insertStyles(styles);
          } else {
            // TODO: support multiple style insertion if _chars.length > 1
            this.insertCharStyleObject(lineIndex, charIndex);
          }
        }
      },

      /**
       * @private
       */
      _insertStyles: function (styles) {
        for (var i = 0, len = styles.length; i < len; i++) {
          var cursorLocation = this.get2DCursorLocation(
              this.selectionStart + i
            ),
            lineIndex = cursorLocation.lineIndex,
            charIndex = cursorLocation.charIndex;

          this.insertCharStyleObject(lineIndex, charIndex, styles[i]);
        }
      },

      /**
       * Shifts line styles up or down
       * @param {Number} lineIndex Index of a line
       * @param {Number} offset Can be -1 or +1
       */
      shiftLineStyles: function (lineIndex, offset) {
        // shift all line styles by 1 upward
        var clonedStyles = clone(this.styles);
        for (var line in this.styles) {
          var numericLine = parseInt(line, 10);
          if (numericLine > lineIndex) {
            this.styles[numericLine + offset] = clonedStyles[numericLine];
          }
        }
      },

      /**
       * Removes style object
       * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line
       * @param {Number} [index] Optional index. When not given, current selectionStart is used.
       */
      removeStyleObject: function (isBeginningOfLine, index) {
        var cursorLocation = this.get2DCursorLocation(index),
          lineIndex = cursorLocation.lineIndex,
          charIndex = cursorLocation.charIndex;

        if (isBeginningOfLine) {
          var textLines = this.text.split(this._reNewline),
            textOnPreviousLine = textLines[lineIndex - 1],
            newCharIndexOnPrevLine = textOnPreviousLine
              ? textOnPreviousLine.length
              : 0;

          if (!this.styles[lineIndex - 1]) {
            this.styles[lineIndex - 1] = {};
          }

          for (charIndex in this.styles[lineIndex]) {
            this.styles[lineIndex - 1][
              parseInt(charIndex, 10) + newCharIndexOnPrevLine
            ] = this.styles[lineIndex][charIndex];
          }

          this.shiftLineStyles(lineIndex, -1);
        } else {
          var currentLineStyles = this.styles[lineIndex];

          if (currentLineStyles) {
            var offset = this.selectionStart === this.selectionEnd ? -1 : 0;
            delete currentLineStyles[charIndex + offset];
            // console.log('deleting', lineIndex, charIndex + offset);
          }

          var currentLineStylesCloned = clone(currentLineStyles);

          // shift all styles by 1 backwards
          for (var i in currentLineStylesCloned) {
            var numericIndex = parseInt(i, 10);
            if (numericIndex >= charIndex && numericIndex !== 0) {
              currentLineStyles[numericIndex - 1] =
                currentLineStylesCloned[numericIndex];
              delete currentLineStyles[numericIndex];
            }
          }
        }
      },

      /**
       * Inserts new line
       */
      insertNewline: function () {
        this.insertChars("\n");
      }
    }
  );
})();

fabric.util.object.extend(
  fabric.IText.prototype,
  /** @lends fabric.IText.prototype */ {
    /**
     * Initializes "dbclick" event handler
     */
    initDoubleClickSimulation: function () {
      // for double click
      this.__lastClickTime = +new Date();

      // for triple click
      this.__lastLastClickTime = +new Date();

      this.__lastPointer = {};

      this.on("mousedown", this.onMouseDown.bind(this));
    },

    onMouseDown: function (options) {
      this.__newClickTime = +new Date();
      var newPointer = this.canvas.getPointer(options.e);

      if (this.isTripleClick(newPointer)) {
        this.fire("tripleclick", options);
        this._stopEvent(options.e);
      } else if (this.isDoubleClick(newPointer)) {
        this.fire("dblclick", options);
        this._stopEvent(options.e);
      }

      this.__lastLastClickTime = this.__lastClickTime;
      this.__lastClickTime = this.__newClickTime;
      this.__lastPointer = newPointer;
      this.__lastIsEditing = this.isEditing;
      this.__lastSelected = this.selected;
    },

    isDoubleClick: function (newPointer) {
      return (
        this.__newClickTime - this.__lastClickTime < 500 &&
        this.__lastPointer.x === newPointer.x &&
        this.__lastPointer.y === newPointer.y &&
        this.__lastIsEditing
      );
    },

    isTripleClick: function (newPointer) {
      return (
        this.__newClickTime - this.__lastClickTime < 500 &&
        this.__lastClickTime - this.__lastLastClickTime < 500 &&
        this.__lastPointer.x === newPointer.x &&
        this.__lastPointer.y === newPointer.y
      );
    },

    /**
     * @private
     */
    _stopEvent: function (e) {
      e.preventDefault && e.preventDefault();
      e.stopPropagation && e.stopPropagation();
    },

    /**
     * Initializes event handlers related to cursor or selection
     */
    initCursorSelectionHandlers: function () {
      this.initSelectedHandler();
      this.initMousedownHandler();
      this.initMousemoveHandler();
      this.initMouseupHandler();
      this.initClicks();
    },

    /**
     * Initializes double and triple click event handlers
     */
    initClicks: function () {
      this.on("dblclick", function (options) {
        this.selectWord(this.getSelectionStartFromPointer(options.e));
      });
      this.on("tripleclick", function (options) {
        this.selectLine(this.getSelectionStartFromPointer(options.e));
      });
    },

    /**
     * Initializes "mousedown" event handler
     */
    initMousedownHandler: function () {
      this.on("mousedown", function (options) {
        var pointer = this.canvas.getPointer(options.e);

        this.__mousedownX = pointer.x;
        this.__mousedownY = pointer.y;
        this.__isMousedown = true;

        if (this.hiddenTextarea && this.canvas) {
          this.canvas.wrapperEl.appendChild(this.hiddenTextarea);
        }

        if (this.selected) {
          this.setCursorByClick(options.e);
        }

        if (this.isEditing) {
          this.__selectionStartOnMouseDown = this.selectionStart;
          this.initDelayedCursor(true);
        }
      });
    },

    /**
     * Initializes "mousemove" event handler
     */
    initMousemoveHandler: function () {
      this.on("mousemove", function (options) {
        if (!this.__isMousedown || !this.isEditing) return;

        var newSelectionStart = this.getSelectionStartFromPointer(options.e);

        if (newSelectionStart >= this.__selectionStartOnMouseDown) {
          this.setSelectionStart(this.__selectionStartOnMouseDown);
          this.setSelectionEnd(newSelectionStart);
        } else {
          this.setSelectionStart(newSelectionStart);
          this.setSelectionEnd(this.__selectionStartOnMouseDown);
        }
      });
    },

    /**
     * @private
     */
    _isObjectMoved: function (e) {
      var pointer = this.canvas.getPointer(e);

      return this.__mousedownX !== pointer.x || this.__mousedownY !== pointer.y;
    },

    /**
     * Initializes "mouseup" event handler
     */
    initMouseupHandler: function () {
      this.on("mouseup", function (options) {
        this.__isMousedown = false;
        if (this._isObjectMoved(options.e)) return;

        if (this.__lastSelected) {
          this.enterEditing();
          this.initDelayedCursor(true);
        }
        this.selected = true;
      });
    },

    /**
     * Changes cursor location in a text depending on passed pointer (x/y) object
     * @param {Event} e Event object
     */
    setCursorByClick: function (e) {
      var newSelectionStart = this.getSelectionStartFromPointer(e);

      if (e.shiftKey) {
        if (newSelectionStart < this.selectionStart) {
          this.setSelectionEnd(this.selectionStart);
          this.setSelectionStart(newSelectionStart);
        } else {
          this.setSelectionEnd(newSelectionStart);
        }
      } else {
        this.setSelectionStart(newSelectionStart);
        this.setSelectionEnd(newSelectionStart);
      }
    },

    /**
     * @private
     * @param {Event} e Event object
     * @return {Object} Coordinates of a pointer (x, y)
     */
    _getLocalRotatedPointer: function (e) {
      var pointer = this.canvas.getPointer(e),
        pClicked = new fabric.Point(pointer.x, pointer.y),
        pLeftTop = new fabric.Point(this.left, this.top),
        rotated = fabric.util.rotatePoint(
          pClicked,
          pLeftTop,
          fabric.util.degreesToRadians(-this.angle)
        );

      return this.getLocalPointer(e, rotated);
    },

    /**
     * Returns index of a character corresponding to where an object was clicked
     * @param {Event} e Event object
     * @return {Number} Index of a character
     */
    getSelectionStartFromPointer: function (e) {
      var mouseOffset = this._getLocalRotatedPointer(e),
        textLines = this.text.split(this._reNewline),
        prevWidth = 0,
        width = 0,
        height = 0,
        charIndex = 0,
        newSelectionStart;

      for (var i = 0, len = textLines.length; i < len; i++) {
        height += this._getHeightOfLine(this.ctx, i) * this.scaleY;

        var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines),
          lineLeftOffset = this._getLineLeftOffset(widthOfLine);

        width = lineLeftOffset * this.scaleX;

        if (this.flipX) {
          // when oject is horizontally flipped we reverse chars
          textLines[i] = textLines[i].split("").reverse().join("");
        }

        for (var j = 0, jlen = textLines[i].length; j < jlen; j++) {
          var _char = textLines[i][j];
          prevWidth = width;

          width +=
            this._getWidthOfChar(
              this.ctx,
              _char,
              i,
              this.flipX ? jlen - j : j
            ) * this.scaleX;

          if (height <= mouseOffset.y || width <= mouseOffset.x) {
            charIndex++;
            continue;
          }

          return this._getNewSelectionStartFromOffset(
            mouseOffset,
            prevWidth,
            width,
            charIndex + i,
            jlen
          );
        }

        if (mouseOffset.y < height) {
          return this._getNewSelectionStartFromOffset(
            mouseOffset,
            prevWidth,
            width,
            charIndex + i,
            jlen,
            j
          );
        }
      }

      // clicked somewhere after all chars, so set at the end
      if (typeof newSelectionStart === "undefined") {
        return this.text.length;
      }
    },

    /**
     * @private
     */
    _getNewSelectionStartFromOffset: function (
      mouseOffset,
      prevWidth,
      width,
      index,
      jlen,
      j
    ) {
      var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth,
        distanceBtwNextCharAndCursor = width - mouseOffset.x,
        offset =
          distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1,
        newSelectionStart = index + offset;

      // if object is horizontally flipped, mirror cursor location from the end
      if (this.flipX) {
        newSelectionStart = jlen - newSelectionStart;
      }

      if (newSelectionStart > this.text.length) {
        newSelectionStart = this.text.length;
      }

      if (j === jlen) {
        newSelectionStart--;
      }

      return newSelectionStart;
    }
  }
);

fabric.util.object.extend(
  fabric.IText.prototype,
  /** @lends fabric.IText.prototype */ {
    /**
     * Initializes hidden textarea (needed to bring up keyboard in iOS)
     */
    initHiddenTextarea: function () {
      this.hiddenTextarea = fabric.document.createElement("textarea");

      this.hiddenTextarea.setAttribute("autocapitalize", "off");
      this.hiddenTextarea.style.cssText =
        "position: absolute; top: 0; left: -9999px";

      fabric.document.body.appendChild(this.hiddenTextarea);

      fabric.util.addListener(
        this.hiddenTextarea,
        "keydown",
        this.onKeyDown.bind(this)
      );
      fabric.util.addListener(
        this.hiddenTextarea,
        "keypress",
        this.onKeyPress.bind(this)
      );
      fabric.util.addListener(
        this.hiddenTextarea,
        "copy",
        this.copy.bind(this)
      );
      fabric.util.addListener(
        this.hiddenTextarea,
        "paste",
        this.paste.bind(this)
      );

      if (!this._clickHandlerInitialized && this.canvas) {
        fabric.util.addListener(
          this.canvas.upperCanvasEl,
          "click",
          this.onClick.bind(this)
        );
        this._clickHandlerInitialized = true;
      }
    },

    /**
     * @private
     */
    _keysMap: {
      8: "removeChars",
      13: "insertNewline",
      37: "moveCursorLeft",
      38: "moveCursorUp",
      39: "moveCursorRight",
      40: "moveCursorDown",
      46: "forwardDelete"
    },

    /**
     * @private
     */
    _ctrlKeysMap: {
      65: "selectAll",
      88: "cut"
    },

    onClick: function () {
      // No need to trigger click event here, focus is enough to have the keyboard appear on Android
      this.hiddenTextarea && this.hiddenTextarea.focus();
    },

    /**
     * Handles keyup event
     * @param {Event} e Event object
     */
    onKeyDown: function (e) {
      if (!this.isEditing) return;

      if (e.keyCode in this._keysMap) {
        this[this._keysMap[e.keyCode]](e);
      } else if (e.keyCode in this._ctrlKeysMap && (e.ctrlKey || e.metaKey)) {
        this[this._ctrlKeysMap[e.keyCode]](e);
      } else {
        return;
      }

      e.stopPropagation();

      this.canvas && this.canvas.renderAll();
    },

    /**
     * Forward delete
     */
    forwardDelete: function (e) {
      if (this.selectionStart === this.selectionEnd) {
        this.moveCursorRight(e);
      }
      this.removeChars(e);
    },

    /**
     * Copies selected text
     * @param {Event} e Event object
     */
    copy: function (e) {
      var selectedText = this.getSelectedText(),
        clipboardData = this._getClipboardData(e);

      // Check for backward compatibility with old browsers
      if (clipboardData) {
        clipboardData.setData("text", selectedText);
      }

      this.copiedText = selectedText;
      this.copiedStyles = this.getSelectionStyles(
        this.selectionStart,
        this.selectionEnd
      );
    },

    /**
     * Pastes text
     * @param {Event} e Event object
     */
    paste: function (e) {
      var copiedText = null,
        clipboardData = this._getClipboardData(e);

      // Check for backward compatibility with old browsers
      if (clipboardData) {
        copiedText = clipboardData.getData("text");
      } else {
        copiedText = this.copiedText;
      }

      if (copiedText) {
        this.insertChars(copiedText);
      }
    },

    /**
     * Cuts text
     * @param {Event} e Event object
     */
    cut: function (e) {
      if (this.selectionStart === this.selectionEnd) {
        return;
      }

      this.copy();
      this.removeChars(e);
    },

    /**
     * @private
     * @param {Event} e Event object
     * @return {Object} Clipboard data object
     */
    _getClipboardData: function (e) {
      return e && (e.clipboardData || fabric.window.clipboardData);
    },

    /**
     * Handles keypress event
     * @param {Event} e Event object
     */
    onKeyPress: function (e) {
      if (
        !this.isEditing ||
        e.metaKey ||
        e.ctrlKey ||
        e.keyCode === 8 ||
        e.keyCode === 13
      ) {
        return;
      }

      this.insertChars(String.fromCharCode(e.which));

      e.stopPropagation();
    },

    /**
     * Gets start offset of a selection
     * @param {Event} e Event object
     * @param {Boolean} isRight
     * @return {Number}
     */
    getDownCursorOffset: function (e, isRight) {
      var selectionProp = isRight ? this.selectionEnd : this.selectionStart,
        textLines = this.text.split(this._reNewline),
        _char,
        lineLeftOffset,
        textBeforeCursor = this.text.slice(0, selectionProp),
        textAfterCursor = this.text.slice(selectionProp),
        textOnSameLineBeforeCursor = textBeforeCursor.slice(
          textBeforeCursor.lastIndexOf("\n") + 1
        ),
        textOnSameLineAfterCursor = textAfterCursor.match(/(.*)\n?/)[1],
        textOnNextLine = (textAfterCursor.match(/.*\n(.*)\n?/) || {})[1] || "",
        cursorLocation = this.get2DCursorLocation(selectionProp);

      // if on last line, down cursor goes to end of line
      if (cursorLocation.lineIndex === textLines.length - 1 || e.metaKey) {
        // move to the end of a text
        return this.text.length - selectionProp;
      }

      var widthOfSameLineBeforeCursor = this._getWidthOfLine(
        this.ctx,
        cursorLocation.lineIndex,
        textLines
      );
      lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor);

      var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset,
        lineIndex = cursorLocation.lineIndex;

      for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) {
        _char = textOnSameLineBeforeCursor[i];
        widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(
          this.ctx,
          _char,
          lineIndex,
          i
        );
      }

      var indexOnNextLine = this._getIndexOnNextLine(
        cursorLocation,
        textOnNextLine,
        widthOfCharsOnSameLineBeforeCursor,
        textLines
      );

      return textOnSameLineAfterCursor.length + 1 + indexOnNextLine;
    },

    /**
     * @private
     */
    _getIndexOnNextLine: function (
      cursorLocation,
      textOnNextLine,
      widthOfCharsOnSameLineBeforeCursor,
      textLines
    ) {
      var lineIndex = cursorLocation.lineIndex + 1,
        widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines),
        lineLeftOffset = this._getLineLeftOffset(widthOfNextLine),
        widthOfCharsOnNextLine = lineLeftOffset,
        indexOnNextLine = 0,
        foundMatch;

      for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) {
        var _char = textOnNextLine[j],
          widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);

        widthOfCharsOnNextLine += widthOfChar;

        if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) {
          foundMatch = true;

          var leftEdge = widthOfCharsOnNextLine - widthOfChar,
            rightEdge = widthOfCharsOnNextLine,
            offsetFromLeftEdge = Math.abs(
              leftEdge - widthOfCharsOnSameLineBeforeCursor
            ),
            offsetFromRightEdge = Math.abs(
              rightEdge - widthOfCharsOnSameLineBeforeCursor
            );

          indexOnNextLine =
            offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j;

          break;
        }
      }

      // reached end
      if (!foundMatch) {
        indexOnNextLine = textOnNextLine.length;
      }

      return indexOnNextLine;
    },

    /**
     * Moves cursor down
     * @param {Event} e Event object
     */
    moveCursorDown: function (e) {
      this.abortCursorAnimation();
      this._currentCursorOpacity = 1;

      var offset = this.getDownCursorOffset(
        e,
        this._selectionDirection === "right"
      );

      if (e.shiftKey) {
        this.moveCursorDownWithShift(offset);
      } else {
        this.moveCursorDownWithoutShift(offset);
      }

      this.initDelayedCursor();
    },

    /**
     * Moves cursor down without keeping selection
     * @param {Number} offset
     */
    moveCursorDownWithoutShift: function (offset) {
      this._selectionDirection = "right";
      this.selectionStart += offset;

      if (this.selectionStart > this.text.length) {
        this.selectionStart = this.text.length;
      }
      this.selectionEnd = this.selectionStart;
    },

    /**
     * Moves cursor down while keeping selection
     * @param {Number} offset
     */
    moveCursorDownWithShift: function (offset) {
      if (
        this._selectionDirection === "left" &&
        this.selectionStart !== this.selectionEnd
      ) {
        this.selectionStart += offset;
        this._selectionDirection = "left";
        return;
      } else {
        this._selectionDirection = "right";
        this.selectionEnd += offset;

        if (this.selectionEnd > this.text.length) {
          this.selectionEnd = this.text.length;
        }
      }
    },

    /**
     * @param {Event} e Event object
     * @param {Boolean} isRight
     * @return {Number}
     */
    getUpCursorOffset: function (e, isRight) {
      var selectionProp = isRight ? this.selectionEnd : this.selectionStart,
        cursorLocation = this.get2DCursorLocation(selectionProp);

      // if on first line, up cursor goes to start of line
      if (cursorLocation.lineIndex === 0 || e.metaKey) {
        return selectionProp;
      }

      var textBeforeCursor = this.text.slice(0, selectionProp),
        textOnSameLineBeforeCursor = textBeforeCursor.slice(
          textBeforeCursor.lastIndexOf("\n") + 1
        ),
        textOnPreviousLine =
          (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || "",
        textLines = this.text.split(this._reNewline),
        _char,
        widthOfSameLineBeforeCursor = this._getWidthOfLine(
          this.ctx,
          cursorLocation.lineIndex,
          textLines
        ),
        lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor),
        widthOfCharsOnSameLineBeforeCursor = lineLeftOffset,
        lineIndex = cursorLocation.lineIndex;

      for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) {
        _char = textOnSameLineBeforeCursor[i];
        widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(
          this.ctx,
          _char,
          lineIndex,
          i
        );
      }

      var indexOnPrevLine = this._getIndexOnPrevLine(
        cursorLocation,
        textOnPreviousLine,
        widthOfCharsOnSameLineBeforeCursor,
        textLines
      );

      return (
        textOnPreviousLine.length -
        indexOnPrevLine +
        textOnSameLineBeforeCursor.length
      );
    },

    /**
     * @private
     */
    _getIndexOnPrevLine: function (
      cursorLocation,
      textOnPreviousLine,
      widthOfCharsOnSameLineBeforeCursor,
      textLines
    ) {
      var lineIndex = cursorLocation.lineIndex - 1,
        widthOfPreviousLine = this._getWidthOfLine(
          this.ctx,
          lineIndex,
          textLines
        ),
        lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine),
        widthOfCharsOnPreviousLine = lineLeftOffset,
        indexOnPrevLine = 0,
        foundMatch;

      for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) {
        var _char = textOnPreviousLine[j],
          widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);

        widthOfCharsOnPreviousLine += widthOfChar;

        if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) {
          foundMatch = true;

          var leftEdge = widthOfCharsOnPreviousLine - widthOfChar,
            rightEdge = widthOfCharsOnPreviousLine,
            offsetFromLeftEdge = Math.abs(
              leftEdge - widthOfCharsOnSameLineBeforeCursor
            ),
            offsetFromRightEdge = Math.abs(
              rightEdge - widthOfCharsOnSameLineBeforeCursor
            );

          indexOnPrevLine =
            offsetFromRightEdge < offsetFromLeftEdge ? j : j - 1;

          break;
        }
      }

      // reached end
      if (!foundMatch) {
        indexOnPrevLine = textOnPreviousLine.length - 1;
      }

      return indexOnPrevLine;
    },

    /**
     * Moves cursor up
     * @param {Event} e Event object
     */
    moveCursorUp: function (e) {
      this.abortCursorAnimation();
      this._currentCursorOpacity = 1;

      var offset = this.getUpCursorOffset(
        e,
        this._selectionDirection === "right"
      );

      if (e.shiftKey) {
        this.moveCursorUpWithShift(offset);
      } else {
        this.moveCursorUpWithoutShift(offset);
      }

      this.initDelayedCursor();
    },

    /**
     * Moves cursor up with shift
     * @param {Number} offset
     */
    moveCursorUpWithShift: function (offset) {
      if (this.selectionStart === this.selectionEnd) {
        this.selectionStart -= offset;
      } else {
        if (this._selectionDirection === "right") {
          this.selectionEnd -= offset;
          this._selectionDirection = "right";
          return;
        } else {
          this.selectionStart -= offset;
        }
      }

      if (this.selectionStart < 0) {
        this.selectionStart = 0;
      }

      this._selectionDirection = "left";
    },

    /**
     * Moves cursor up without shift
     * @param {Number} offset
     */
    moveCursorUpWithoutShift: function (offset) {
      if (this.selectionStart === this.selectionEnd) {
        this.selectionStart -= offset;
      }
      if (this.selectionStart < 0) {
        this.selectionStart = 0;
      }
      this.selectionEnd = this.selectionStart;

      this._selectionDirection = "left";
    },

    /**
     * Moves cursor left
     * @param {Event} e Event object
     */
    moveCursorLeft: function (e) {
      if (this.selectionStart === 0 && this.selectionEnd === 0) return;

      this.abortCursorAnimation();
      this._currentCursorOpacity = 1;

      if (e.shiftKey) {
        this.moveCursorLeftWithShift(e);
      } else {
        this.moveCursorLeftWithoutShift(e);
      }

      this.initDelayedCursor();
    },

    /**
     * @private
     */
    _move: function (e, prop, direction) {
      if (e.altKey) {
        this[prop] = this["findWordBoundary" + direction](this[prop]);
      } else if (e.metaKey) {
        this[prop] = this["findLineBoundary" + direction](this[prop]);
      } else {
        this[prop] += direction === "Left" ? -1 : 1;
      }
    },

    /**
     * @private
     */
    _moveLeft: function (e, prop) {
      this._move(e, prop, "Left");
    },

    /**
     * @private
     */
    _moveRight: function (e, prop) {
      this._move(e, prop, "Right");
    },

    /**
     * Moves cursor left without keeping selection
     * @param {Event} e
     */
    moveCursorLeftWithoutShift: function (e) {
      this._selectionDirection = "left";

      // only move cursor when there is no selection,
      // otherwise we discard it, and leave cursor on same place
      if (this.selectionEnd === this.selectionStart) {
        this._moveLeft(e, "selectionStart");
      }
      this.selectionEnd = this.selectionStart;
    },

    /**
     * Moves cursor left while keeping selection
     * @param {Event} e
     */
    moveCursorLeftWithShift: function (e) {
      if (
        this._selectionDirection === "right" &&
        this.selectionStart !== this.selectionEnd
      ) {
        this._moveLeft(e, "selectionEnd");
      } else {
        this._selectionDirection = "left";
        this._moveLeft(e, "selectionStart");

        // increase selection by one if it's a newline
        if (this.text.charAt(this.selectionStart) === "\n") {
          this.selectionStart--;
        }
        if (this.selectionStart < 0) {
          this.selectionStart = 0;
        }
      }
    },

    /**
     * Moves cursor right
     * @param {Event} e Event object
     */
    moveCursorRight: function (e) {
      if (
        this.selectionStart >= this.text.length &&
        this.selectionEnd >= this.text.length
      )
        return;

      this.abortCursorAnimation();
      this._currentCursorOpacity = 1;

      if (e.shiftKey) {
        this.moveCursorRightWithShift(e);
      } else {
        this.moveCursorRightWithoutShift(e);
      }

      this.initDelayedCursor();
    },

    /**
     * Moves cursor right while keeping selection
     * @param {Event} e
     */
    moveCursorRightWithShift: function (e) {
      if (
        this._selectionDirection === "left" &&
        this.selectionStart !== this.selectionEnd
      ) {
        this._moveRight(e, "selectionStart");
      } else {
        this._selectionDirection = "right";
        this._moveRight(e, "selectionEnd");

        // increase selection by one if it's a newline
        if (this.text.charAt(this.selectionEnd - 1) === "\n") {
          this.selectionEnd++;
        }
        if (this.selectionEnd > this.text.length) {
          this.selectionEnd = this.text.length;
        }
      }
    },

    /**
     * Moves cursor right without keeping selection
     * @param {Event} e Event object
     */
    moveCursorRightWithoutShift: function (e) {
      this._selectionDirection = "right";

      if (this.selectionStart === this.selectionEnd) {
        this._moveRight(e, "selectionStart");
        this.selectionEnd = this.selectionStart;
      } else {
        this.selectionEnd += this.getNumNewLinesInSelectedText();
        if (this.selectionEnd > this.text.length) {
          this.selectionEnd = this.text.length;
        }
        this.selectionStart = this.selectionEnd;
      }
    },

    /**
     * Inserts a character where cursor is (replacing selection if one exists)
     * @param {Event} e Event object
     */
    removeChars: function (e) {
      if (this.selectionStart === this.selectionEnd) {
        this._removeCharsNearCursor(e);
      } else {
        this._removeCharsFromTo(this.selectionStart, this.selectionEnd);
      }

      this.selectionEnd = this.selectionStart;

      this._removeExtraneousStyles();

      if (this.canvas) {
        // TODO: double renderAll gets rid of text box shift happenning sometimes
        // need to find out what exactly causes it and fix it
        this.canvas.renderAll().renderAll();
      }

      this.setCoords();
      this.fire("changed");
      this.canvas && this.canvas.fire("text:changed", { target: this });
    },

    /**
     * @private
     * @param {Event} e Event object
     */
    _removeCharsNearCursor: function (e) {
      if (this.selectionStart !== 0) {
        if (e.metaKey) {
          // remove all till the start of current line
          var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart);

          this._removeCharsFromTo(leftLineBoundary, this.selectionStart);
          this.selectionStart = leftLineBoundary;
        } else if (e.altKey) {
          // remove all till the start of current word
          var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart);

          this._removeCharsFromTo(leftWordBoundary, this.selectionStart);
          this.selectionStart = leftWordBoundary;
        } else {
          var isBeginningOfLine =
            this.text.slice(this.selectionStart - 1, this.selectionStart) ===
            "\n";
          this.removeStyleObject(isBeginningOfLine);

          this.selectionStart--;
          this.text =
            this.text.slice(0, this.selectionStart) +
            this.text.slice(this.selectionStart + 1);
        }
      }
    }
  }
);

/* _TO_SVG_START_ */
fabric.util.object.extend(
  fabric.IText.prototype,
  /** @lends fabric.IText.prototype */ {
    /**
     * @private
     */
    _setSVGTextLineText: function (
      textLine,
      lineIndex,
      textSpans,
      lineHeight,
      lineTopOffsetMultiplier,
      textBgRects
    ) {
      if (!this.styles[lineIndex]) {
        this.callSuper(
          "_setSVGTextLineText",
          textLine,
          lineIndex,
          textSpans,
          lineHeight,
          lineTopOffsetMultiplier
        );
      } else {
        this._setSVGTextLineChars(
          textLine,
          lineIndex,
          textSpans,
          lineHeight,
          lineTopOffsetMultiplier,
          textBgRects
        );
      }
    },

    /**
     * @private
     */
    _setSVGTextLineChars: function (
      textLine,
      lineIndex,
      textSpans,
      lineHeight,
      lineTopOffsetMultiplier,
      textBgRects
    ) {
      var yProp = lineIndex === 0 || this.useNative ? "y" : "dy",
        chars = textLine.split(""),
        charOffset = 0,
        lineLeftOffset = this._getSVGLineLeftOffset(lineIndex),
        lineTopOffset = this._getSVGLineTopOffset(lineIndex),
        heightOfLine = this._getHeightOfLine(this.ctx, lineIndex);

      for (var i = 0, len = chars.length; i < len; i++) {
        var styleDecl = this.styles[lineIndex][i] || {};

        textSpans.push(
          this._createTextCharSpan(
            chars[i],
            styleDecl,
            lineLeftOffset,
            lineTopOffset,
            yProp,
            charOffset
          )
        );

        var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i);

        if (styleDecl.textBackgroundColor) {
          textBgRects.push(
            this._createTextCharBg(
              styleDecl,
              lineLeftOffset,
              lineTopOffset,
              heightOfLine,
              charWidth,
              charOffset
            )
          );
        }

        charOffset += charWidth;
      }
    },

    /**
     * @private
     */
    _getSVGLineLeftOffset: function (lineIndex) {
      return this._boundaries && this._boundaries[lineIndex]
        ? fabric.util.toFixed(this._boundaries[lineIndex].left, 2)
        : 0;
    },

    /**
     * @private
     */
    _getSVGLineTopOffset: function (lineIndex) {
      var lineTopOffset = 0;
      for (var j = 0; j <= lineIndex; j++) {
        lineTopOffset += this._getHeightOfLine(this.ctx, j);
      }
      return lineTopOffset - this.height / 2;
    },

    /**
     * @private
     */
    _createTextCharBg: function (
      styleDecl,
      lineLeftOffset,
      lineTopOffset,
      heightOfLine,
      charWidth,
      charOffset
    ) {
      return [
        '<rect fill="',
        styleDecl.textBackgroundColor,
        '" transform="translate(',
        -this.width / 2,
        " ",
        -this.height + heightOfLine,
        ")",
        '" x="',
        lineLeftOffset + charOffset,
        '" y="',
        lineTopOffset + heightOfLine,
        '" width="',
        charWidth,
        '" height="',
        heightOfLine,
        '"></rect>'
      ].join("");
    },

    /**
     * @private
     */
    _createTextCharSpan: function (
      _char,
      styleDecl,
      lineLeftOffset,
      lineTopOffset,
      yProp,
      charOffset
    ) {
      var fillStyles = this.getSvgStyles.call(
        fabric.util.object.extend(
          {
            visible: true,
            fill: this.fill,
            stroke: this.stroke,
            type: "text"
          },
          styleDecl
        )
      );

      return [
        '<tspan x="',
        lineLeftOffset + charOffset,
        '" ',
        yProp,
        '="',
        lineTopOffset,
        '" ',

        styleDecl.fontFamily
          ? 'font-family="' + styleDecl.fontFamily.replace(/"/g, "'") + '" '
          : "",
        styleDecl.fontSize ? 'font-size="' + styleDecl.fontSize + '" ' : "",
        styleDecl.fontStyle ? 'font-style="' + styleDecl.fontStyle + '" ' : "",
        styleDecl.fontWeight
          ? 'font-weight="' + styleDecl.fontWeight + '" '
          : "",
        styleDecl.textDecoration
          ? 'text-decoration="' + styleDecl.textDecoration + '" '
          : "",
        'style="',
        fillStyles,
        '">',

        fabric.util.string.escapeXml(_char),
        "</tspan>"
      ].join("");
    }
  }
);
/* _TO_SVG_END_ */

(function () {
  if (typeof document !== "undefined" && typeof window !== "undefined") {
    return;
  }

  var DOMParser = require("xmldom").DOMParser,
    URL = require("url"),
    HTTP = require("http"),
    HTTPS = require("https"),
    Canvas = require("canvas"),
    Image = require("canvas").Image;

  /** @private */
  function request(url, encoding, callback) {
    var oURL = URL.parse(url);

    // detect if http or https is used
    if (!oURL.port) {
      oURL.port = oURL.protocol.indexOf("https:") === 0 ? 443 : 80;
    }

    // assign request handler based on protocol
    var reqHandler = oURL.port === 443 ? HTTPS : HTTP,
      req = reqHandler.request(
        {
          hostname: oURL.hostname,
          port: oURL.port,
          path: oURL.path,
          method: "GET"
        },
        function (response) {
          var body = "";
          if (encoding) {
            response.setEncoding(encoding);
          }
          response.on("end", function () {
            callback(body);
          });
          response.on("data", function (chunk) {
            if (response.statusCode === 200) {
              body += chunk;
            }
          });
        }
      );

    req.on("error", function (err) {
      if (err.errno === process.ECONNREFUSED) {
        fabric.log(
          "ECONNREFUSED: connection refused to " +
            oURL.hostname +
            ":" +
            oURL.port
        );
      } else {
        fabric.log(err.message);
      }
    });

    req.end();
  }

  /** @private */
  function requestFs(path, callback) {
    var fs = require("fs");
    fs.readFile(path, function (err, data) {
      if (err) {
        fabric.log(err);
        throw err;
      } else {
        callback(data);
      }
    });
  }

  fabric.util.loadImage = function (url, callback, context) {
    function createImageAndCallBack(data) {
      img.src = new Buffer(data, "binary");
      // preserving original url, which seems to be lost in node-canvas
      img._src = url;
      callback && callback.call(context, img);
    }
    var img = new Image();
    if (url && (url instanceof Buffer || url.indexOf("data") === 0)) {
      img.src = img._src = url;
      callback && callback.call(context, img);
    } else if (url && url.indexOf("http") !== 0) {
      requestFs(url, createImageAndCallBack);
    } else if (url) {
      request(url, "binary", createImageAndCallBack);
    } else {
      callback && callback.call(context, url);
    }
  };

  fabric.loadSVGFromURL = function (url, callback, reviver) {
    url = url
      .replace(/^\n\s*/, "")
      .replace(/\?.*$/, "")
      .trim();
    if (url.indexOf("http") !== 0) {
      requestFs(url, function (body) {
        fabric.loadSVGFromString(body.toString(), callback, reviver);
      });
    } else {
      request(url, "", function (body) {
        fabric.loadSVGFromString(body, callback, reviver);
      });
    }
  };

  fabric.loadSVGFromString = function (string, callback, reviver) {
    var doc = new DOMParser().parseFromString(string);
    fabric.parseSVGDocument(
      doc.documentElement,
      function (results, options) {
        callback && callback(results, options);
      },
      reviver
    );
  };

  fabric.util.getScript = function (url, callback) {
    request(url, "", function (body) {
      eval(body);
      callback && callback();
    });
  };

  fabric.Image.fromObject = function (object, callback) {
    fabric.util.loadImage(object.src, function (img) {
      var oImg = new fabric.Image(img);

      oImg._initConfig(object);
      oImg._initFilters(object, function (filters) {
        oImg.filters = filters || [];
        callback && callback(oImg);
      });
    });
  };

  /**
   * Only available when running fabric on node.js
   * @param width Canvas width
   * @param height Canvas height
   * @param {Object} options to pass to FabricCanvas.
   * @param {Object} options to pass to NodeCanvas.
   * @return {Object} wrapped canvas instance
   */
  fabric.createCanvasForNode = function (
    width,
    height,
    options,
    nodeCanvasOptions
  ) {
    nodeCanvasOptions = nodeCanvasOptions || options;

    var canvasEl = fabric.document.createElement("canvas"),
      nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions);

    // jsdom doesn't create style on canvas element, so here be temp. workaround
    canvasEl.style = {};

    canvasEl.width = nodeCanvas.width;
    canvasEl.height = nodeCanvas.height;

    var FabricCanvas = fabric.Canvas || fabric.StaticCanvas,
      fabricCanvas = new FabricCanvas(canvasEl, options);

    fabricCanvas.contextContainer = nodeCanvas.getContext("2d");
    fabricCanvas.nodeCanvas = nodeCanvas;
    fabricCanvas.Font = Canvas.Font;

    return fabricCanvas;
  };

  /** @ignore */
  fabric.StaticCanvas.prototype.createPNGStream = function () {
    return this.nodeCanvas.createPNGStream();
  };

  fabric.StaticCanvas.prototype.createJPEGStream = function (opts) {
    return this.nodeCanvas.createJPEGStream(opts);
  };

  var origSetWidth = fabric.StaticCanvas.prototype.setWidth;
  fabric.StaticCanvas.prototype.setWidth = function (width) {
    origSetWidth.call(this, width);
    this.nodeCanvas.width = width;
    return this;
  };
  if (fabric.Canvas) {
    fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth;
  }

  var origSetHeight = fabric.StaticCanvas.prototype.setHeight;
  fabric.StaticCanvas.prototype.setHeight = function (height) {
    origSetHeight.call(this, height);
    this.nodeCanvas.height = height;
    return this;
  };
  if (fabric.Canvas) {
    fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight;
  }
})();