/*! drawerjs - 1.11.0
 */

/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */

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

if (typeof document !== 'undefined' && typeof window !== 'undefined') {
  fabric.document = document;
  fabric.window = window;
  // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system)
  window.fabric = fabric;
}
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>");

  if (fabric.document.createWindow) {
    fabric.window = fabric.document.createWindow();
  } else {
    fabric.window = fabric.document.parentWindow;
  }
}

/**
 * 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';

/* _FROM_SVG_START_ */
/**
 * 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",
  "id"
];
/* _FROM_SVG_END_ */

/**
 * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.
 */
fabric.DPI = 96;
fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)';
fabric.fontPaths = {};

/**
 * Cache Object for widths of chars in text rendering.
 */
fabric.charWidthsCache = {};

/**
 * Device Pixel Ratio
 * @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html
 */
fabric.devicePixelRatio = fabric.window.devicePixelRatio ||
  fabric.window.webkitDevicePixelRatio ||
  fabric.window.mozDevicePixelRatio ||
  1;


(function () {

  /**
   * @private
   * @param {String} eventName
   * @param {Function} handler
   */
  function _removeEventListener(eventName, handler) {
    if (!this.__eventListeners[eventName]) {
      return;
    }
    var eventListener = this.__eventListeners[eventName];
    if (handler) {
      eventListener[eventListener.indexOf(handler)] = false;
    }
    else {
      fabric.util.array.fill(eventListener, false);
    }
  }

  /**
   * 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) {
      for (eventName in this.__eventListeners) {
        _removeEventListener.call(this, eventName);
      }
    }
    // 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++) {
      listenersForEvent[i] && listenersForEvent[i].call(this, options || {});
    }
    this.__eventListeners[eventName] = listenersForEvent.filter(function (value) {
      return value !== false;
    });
    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 = {

  _objects: [],

  /**
   * Adds objects to collection, Canvas or Group, then renders canvas
   * (if `renderOnAddRemove` is not `false`).
   * in case of Group no changes to bounding box are made.
   * Objects should be instances of (or inherit from) fabric.Object
   * @param {...fabric.Object} object Zero or more fabric instances
   * @return {Self} thisArg
   * @chainable
   */
  add: function () {
    this._objects.push.apply(this._objects, arguments);
    if (this._onObjectAdded) {
      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 && 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, somethingRemoved = false;

    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) {
        somethingRemoved = true;
        objects.splice(index, 1);
        this._onObjectRemoved && this._onObjectRemoved(arguments[i]);
      }
    }

    this.renderOnAddRemove && somethingRemoved && 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.
   *                   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
   * @chainable
   */
  forEachObject: function (callback, context) {
    var objects = this.getObjects();
    for (var i = 0, len = objects.length; i < len; 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,
    pow = Math.pow,
    abs = Math.abs,
    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 {*} 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) {
      point.subtractEquals(origin);
      var v = fabric.util.rotateVector(point, radians);
      return new fabric.Point(v.x, v.y).addEquals(origin);
    },

    /**
     * Rotates `vector` with `radians`
     * @static
     * @memberOf fabric.util
     * @param {Object} vector The vector to rotate (x and y)
     * @param {Number} radians The radians of the angle for the rotation
     * @return {Object} The new rotated point
     */
    rotateVector: function (vector, radians) {
      var sin = Math.sin(radians),
        cos = Math.cos(radians),
        rx = vector.x * cos - vector.y * sin,
        ry = vector.x * sin + vector.y * cos;
      return {
        x: rx,
        y: ry
      };
    },

    /**
     * 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[2] * p.y,
          t[1] * p.x + t[3] * p.y
        );
      }
      return new fabric.Point(
        t[0] * p.x + t[2] * p.y + t[4],
        t[1] * p.x + t[3] * p.y + t[5]
      );
    },

    /**
     * Returns coordinates of points's bounding rectangle (left, top, width, height)
     * @param {Array} points 4 points array
     * @return {Object} Object with left, top, width, height properties
     */
    makeBoundingBoxFromPoints: function (points) {
      var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x],
        minX = fabric.util.array.min(xPoints),
        maxX = fabric.util.array.max(xPoints),
        width = Math.abs(minX - maxX),
        yPoints = [points[0].y, points[1].y, points[2].y, points[3].y],
        minY = fabric.util.array.min(yPoints),
        maxY = fabric.util.array.max(yPoints),
        height = Math.abs(minY - maxY);

      return {
        left: minX,
        top: minY,
        width: width,
        height: height
      };
    },

    /**
     * Invert transformation t
     * @static
     * @memberOf fabric.util
     * @param {Array} t The transform
     * @return {Array} The inverted transform
     */
    invertTransform: function (t) {
      var a = 1 / (t[0] * t[3] - t[1] * t[2]),
        r = [a * t[3], -a * t[1], -a * t[2], a * t[0]],
        o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r, true);
      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
     * @param {Number} fontSize
     * @return {Number|String}
     */
    parseUnit: function (value, fontSize) {
      var unit = /\D{0,2}$/.exec(value),
        number = parseFloat(value);
      if (!fontSize) {
        fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
      }
      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

        case 'em':
          return number * fontSize;

        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, i,
        obj = global || fabric.window;

      for (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 {*} [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 && crossOrigin) {
        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
     * @param {String} path Value to set sourcePath to
     * @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'));
      /* eslint-disable camelcase */
      if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
        G_vmlCanvasManager.initElement(canvasEl);
      }
      /* eslint-enable camelcase */
      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, i, propName,
        capitalizedPropName, setterName, getterName;

      for (i = proto.stateProperties.length; i--;) {

        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} a First transformMatrix
     * @param  {Array} b Second transformMatrix
     * @param  {Boolean} is2x2 flag to multiply matrices as 2x2 matrices
     * @return {Array} The product of the two transform matrices
     */
    multiplyTransformMatrices: function (a, b, is2x2) {
      // Matrix multiply a * b
      return [
        a[0] * b[0] + a[2] * b[1],
        a[1] * b[0] + a[3] * b[1],
        a[0] * b[2] + a[2] * b[3],
        a[1] * b[2] + a[3] * b[3],
        is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4],
        is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5]
      ];
    },

    /**
     * Decomposes standard 2x2 matrix into transform componentes
     * @static
     * @memberOf fabric.util
     * @param  {Array} a transformMatrix
     * @return {Object} Components of transform
     */
    qrDecompose: function (a) {
      var angle = atan2(a[1], a[0]),
        denom = pow(a[0], 2) + pow(a[1], 2),
        scaleX = sqrt(denom),
        scaleY = (a[0] * a[3] - a[2] * a[1]) / scaleX,
        skewX = atan2(a[0] * a[2] + a[1] * a[3], denom);
      return {
        angle: angle / PiBy180,
        scaleX: scaleX,
        scaleY: scaleY,
        skewX: skewX / PiBy180,
        skewY: 0,
        translateX: a[4],
        translateY: a[5]
      };
    },

    customTransformMatrix: function (scaleX, scaleY, skewX) {
      var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1],
        scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)];
      return fabric.util.multiplyTransformMatrices(scaleMatrix, skewMatrixX, true);
    },

    resetObjectTransform: function (target) {
      target.scaleX = 1;
      target.scaleY = 1;
      target.skewX = 0;
      target.skewY = 0;
      target.flipX = false;
      target.flipY = false;
      target.setAngle(0);
    },

    /**
     * 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];
    },

    /**
     * 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, i, temp,
        imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1),
        l = imageData.data.length;

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

      imageData = null;

      return _isTransparent;
    },

    /**
     * Parse preserveAspectRatio attribute from element
     * @param {string} attribute to be parsed
     * @return {Object} an object containing align and meetOrSlice attribute
     */
    parsePreserveAspectRatioAttribute: function (attribute) {
      var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid',
        aspectRatioAttrs = attribute.split(' '), align;

      if (aspectRatioAttrs && aspectRatioAttrs.length) {
        meetOrSlice = aspectRatioAttrs.pop();
        if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') {
          align = meetOrSlice;
          meetOrSlice = 'meet';
        }
        else if (aspectRatioAttrs.length) {
          align = aspectRatioAttrs.pop();
        }
      }
      //divide align in alignX and alignY
      alignX = align !== 'none' ? align.slice(1, 4) : 'none';
      alignY = align !== 'none' ? align.slice(5, 8) : 'none';
      return {
        meetOrSlice: meetOrSlice,
        alignX: alignX,
        alignY: alignY
      };
    },

    /**
     * Clear char widths cache for a font family.
     * @memberOf fabric.util
     * @param {String} [fontFamily] font family to clear
     */
    clearFabricFontCache: function (fontFamily) {
      if (!fontFamily) {
        fabric.charWidthsCache = {};
      }
      else if (fabric.charWidthsCache[fontFamily]) {
        delete fabric.charWidthsCache[fontFamily];
      }
    }
  };

})(typeof exports !== 'undefined' ? exports : this);


(function () {

  var arcToSegmentsCache = {},
    segmentToBezierCache = {},
    boundsOfCurveCache = {},
    _join = Array.prototype.join;

  /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp
   * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here
   * http://mozilla.org/MPL/2.0/
   */
  function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
    var argsString = _join.call(arguments);
    if (arcToSegmentsCache[argsString]) {
      return arcToSegmentsCache[argsString];
    }

    var PI = Math.PI, th = rotateX * PI / 180,
      sinTh = Math.sin(th),
      cosTh = Math.cos(th),
      fromX = 0, fromY = 0;

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

    var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5,
      py = -cosTh * toY * 0.5 + sinTh * toX * 0.5,
      rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,
      pl = rx2 * ry2 - rx2 * py2 - ry2 * px2,
      root = 0;

    if (pl < 0) {
      var s = Math.sqrt(1 - pl / (rx2 * ry2));
      rx *= s;
      ry *= s;
    }
    else {
      root = (large === sweep ? -1.0 : 1.0) *
        Math.sqrt(pl / (rx2 * py2 + ry2 * px2));
    }

    var cx = root * rx * py / ry,
      cy = -root * ry * px / rx,
      cx1 = cosTh * cx - sinTh * cy + toX * 0.5,
      cy1 = sinTh * cx + cosTh * cy + toY * 0.5,
      mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),
      dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);

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

    // Convert into cubic bezier segments <= 90deg
    var segments = Math.ceil(Math.abs(dtheta / PI * 2)),
      result = [], mDelta = dtheta / segments,
      mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),
      th3 = mTheta + mDelta;

    for (var i = 0; i < segments; i++) {
      result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);
      fromX = result[i][4];
      fromY = result[i][5];
      mTheta = th3;
      th3 += mDelta;
    }
    arcToSegmentsCache[argsString] = result;
    return result;
  }

  function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
    var argsString2 = _join.call(arguments);
    if (segmentToBezierCache[argsString2]) {
      return segmentToBezierCache[argsString2];
    }

    var costh2 = Math.cos(th2),
      sinth2 = Math.sin(th2),
      costh3 = Math.cos(th3),
      sinth3 = Math.sin(th3),
      toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
      toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
      cp1X = fromX + mT * (-cosTh * rx * sinth2 - sinTh * ry * costh2),
      cp1Y = fromY + mT * (-sinTh * rx * sinth2 + cosTh * ry * costh2),
      cp2X = toX + mT * (cosTh * rx * sinth3 + sinTh * ry * costh3),
      cp2Y = toY + mT * (sinTh * rx * sinth3 - cosTh * ry * costh3);

    segmentToBezierCache[argsString2] = [
      cp1X, cp1Y,
      cp2X, cp2Y,
      toX, toY
    ];
    return segmentToBezierCache[argsString2];
  }

  /*
   * Private
   */
  function calcVectorAngle(ux, uy, vx, vy) {
    var ta = Math.atan2(uy, ux),
      tb = Math.atan2(vy, vx);
    if (tb >= ta) {
      return tb - ta;
    }
    else {
      return 2 * Math.PI - (ta - tb);
    }
  }

  /**
   * Draws arc
   * @param {CanvasRenderingContext2D} ctx
   * @param {Number} fx
   * @param {Number} fy
   * @param {Array} coords
   */
  fabric.util.drawArc = function (ctx, fx, fy, coords) {
    var rx = coords[0],
      ry = coords[1],
      rot = coords[2],
      large = coords[3],
      sweep = coords[4],
      tx = coords[5],
      ty = coords[6],
      segs = [[], [], [], []],
      segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);

    for (var i = 0, len = segsNorm.length; i < len; i++) {
      segs[i][0] = segsNorm[i][0] + fx;
      segs[i][1] = segsNorm[i][1] + fy;
      segs[i][2] = segsNorm[i][2] + fx;
      segs[i][3] = segsNorm[i][3] + fy;
      segs[i][4] = segsNorm[i][4] + fx;
      segs[i][5] = segsNorm[i][5] + fy;
      ctx.bezierCurveTo.apply(ctx, segs[i]);
    }
  };

  /**
   * Calculate bounding box of a elliptic-arc
   * @param {Number} fx start point of arc
   * @param {Number} fy
   * @param {Number} rx horizontal radius
   * @param {Number} ry vertical radius
   * @param {Number} rot angle of horizontal axe
   * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points
   * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction
   * @param {Number} tx end point of arc
   * @param {Number} ty
   */
  fabric.util.getBoundsOfArc = function (fx, fy, rx, ry, rot, large, sweep, tx, ty) {

    var fromX = 0, fromY = 0, bound, bounds = [],
      segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);

    for (var i = 0, len = segs.length; i < len; i++) {
      bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]);
      bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy });
      bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy });
      fromX = segs[i][4];
      fromY = segs[i][5];
    }
    return bounds;
  };

  /**
   * Calculate bounding box of a beziercurve
   * @param {Number} x0 starting point
   * @param {Number} y0
   * @param {Number} x1 first control point
   * @param {Number} y1
   * @param {Number} x2 secondo control point
   * @param {Number} y2
   * @param {Number} x3 end of beizer
   * @param {Number} y3
   */
  // taken from http://jsbin.com/ivomiq/56/edit  no credits available for that.
  function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {
    var argsString = _join.call(arguments);
    if (boundsOfCurveCache[argsString]) {
      return boundsOfCurveCache[argsString];
    }

    var sqrt = Math.sqrt,
      min = Math.min, max = Math.max,
      abs = Math.abs, tvalues = [],
      bounds = [[], []],
      a, b, c, t, t1, t2, b2ac, sqrtb2ac;

    b = 6 * x0 - 12 * x1 + 6 * x2;
    a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
    c = 3 * x1 - 3 * x0;

    for (var i = 0; i < 2; ++i) {
      if (i > 0) {
        b = 6 * y0 - 12 * y1 + 6 * y2;
        a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
        c = 3 * y1 - 3 * y0;
      }

      if (abs(a) < 1e-12) {
        if (abs(b) < 1e-12) {
          continue;
        }
        t = -c / b;
        if (0 < t && t < 1) {
          tvalues.push(t);
        }
        continue;
      }
      b2ac = b * b - 4 * c * a;
      if (b2ac < 0) {
        continue;
      }
      sqrtb2ac = sqrt(b2ac);
      t1 = (-b + sqrtb2ac) / (2 * a);
      if (0 < t1 && t1 < 1) {
        tvalues.push(t1);
      }
      t2 = (-b - sqrtb2ac) / (2 * a);
      if (0 < t2 && t2 < 1) {
        tvalues.push(t2);
      }
    }

    var x, y, j = tvalues.length, jlen = j, mt;
    while (j--) {
      t = tvalues[j];
      mt = 1 - t;
      x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
      bounds[0][j] = x;

      y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
      bounds[1][j] = y;
    }

    bounds[0][jlen] = x0;
    bounds[1][jlen] = y0;
    bounds[0][jlen + 1] = x3;
    bounds[1][jlen + 1] = y3;
    var result = [
      {
        x: min.apply(null, bounds[0]),
        y: min.apply(null, bounds[1])
      },
      {
        x: max.apply(null, bounds[0]),
        y: max.apply(null, bounds[1])
      }
    ];
    boundsOfCurveCache[argsString] = result;
    return result;
  }

  fabric.util.getBoundsOfCurve = getBoundsOfCurve;

})();


(function () {

  var slice = Array.prototype.slice;

  /* _ES5_COMPAT_START_ */

  if (!Array.prototype.indexOf) {
    /**
     * Finds index of an element in an array
     * @param {*} searchElement
     * @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
     * @return {*}
     */
    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 {*}
   */
  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 {*}
   */
  function min(array, byProperty) {
    return find(array, byProperty, function (value1, value2) {
      return value1 < value2;
    });
  }

  /**
   * @private
   */
  function fill(array, value) {
    var k = array.length;
    while (k--) {
      array[k] = value;
    }
    return array;
  }

  /**
   * @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 = {
    fill: fill,
    invoke: invoke,
    min: min,
    max: max
  };

})();


(function () {
  /**
   * Copies all enumerable properties of one js object to another
   * Does not clone or extend fabric.Object subclasses.
   * @memberOf fabric.util.object
   * @param {Object} destination Where to copy to
   * @param {Object} source Where to copy from
   * @return {Object}
   */

  function extend(destination, source, deep) {
    // JScript DontEnum bug is not taken care of
    // the deep clone is for internal use, is not meant to avoid
    // javascript traps or cloning html element or self referenced objects.
    if (deep) {
      if (!fabric.isLikelyNode && source instanceof Element) {
        // avoid cloning deep images, canvases,
        destination = source;
      }
      else if (source instanceof Array) {
        destination = [];
        for (var i = 0, len = source.length; i < len; i++) {
          destination[i] = extend({}, source[i], deep);
        }
      }
      else if (source && typeof source === 'object') {
        for (var property in source) {
          if (source.hasOwnProperty(property)) {
            destination[property] = extend({}, source[property], deep);
          }
        }
      }
      else {
        // this sounds odd for an extend but is ok for recursive use
        destination = source;
      }
    }
    else {
      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, deep) {
    return extend({}, object, deep);
  }

  /** @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 {Function} [parent] optional "Class" to inherit from
   * @param {Object} [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
   */
  function getPointer(event) {
    event || (event = fabric.window.event);

    var element = event.target ||
      (typeof event.srcElement !== unknown ? event.srcElement : null),

      scroll = fabric.util.getScrollLeftTop(element);

    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) {
    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
   * @return {Object} Object with left/top values
   */
  function getScrollLeftTop(element) {

    var left = 0,
      top = 0,
      docElement = fabric.document.documentElement,
      body = fabric.document.body || {
        scrollLeft: 0, scrollTop: 0
      };

    // While loop checks (and then sets element to) .parentNode OR .host
    //  to account for ShadowDOM. We still want to traverse up out of ShadowDOM,
    //  but the .parentNode of a root ShadowDOM node will always be null, instead
    //  it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938
    while (element && (element.parentNode || element.host)) {

      // Set element to element parent, or 'host' in case of ShadowDOM
      element = element.parentNode || element.host;

      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;
      }

      if (element.nodeType === 1 &&
        fabric.util.getElementStyle(element, 'position') === 'fixed') {
        break;
      }
    }

    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 offset;
    }

    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 = getScrollLeftTop(element);

    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) {
      var style = fabric.document.defaultView.getComputedStyle(element, null);
      return style ? style[attr] : undefined;
    };
  }
  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 {String} [options.parameters] parameters to append to url in GET or in body
   * @param {String} [options.body] body to send with POST or PUT request
   * @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 = options.body || options.parameters;

    /** @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 {*} [values] Values to log
 */
fabric.log = function () { };

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

/* eslint-disable */
if (typeof console !== 'undefined') {

  ['log', 'warn'].forEach(function (methodName) {

    if (typeof console[methodName] !== 'undefined' &&
      typeof console[methodName].apply === 'function') {

      fabric[methodName] = function () {
        return console[methodName].apply(console, arguments);
      };
    }
  });
}
/* eslint-enable */


(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 {
      //handle the 0/0 case:
      if (c === 0 && a === 0) {
        s = p / (2 * Math.PI) * Math.asin(1);
      }
      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,

    reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/i,
    reViewBoxTagNames = /^(symbol|image|marker|pattern|view|svg)$/i,
    reNotAllowedAncestors = /^(?:pattern|defs|symbol|metadata)$/i,
    reAllowedParents = /^(symbol|g|a|svg)$/i,

    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'
    };

  fabric.cssRules = {};
  fabric.gradientDefs = {};

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

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

    if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
      value = '';
    }
    else if (attr === 'strokeDashArray') {
      if (value === 'none') {
        value = null;
      }
      else {
        value = value.replace(/,/g, ' ').split(/\s+/).map(function (n) {
          return parseFloat(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, fontSize);
    }

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

  /**
   * @private
   * @param {Object} attributes Array of attributes to parse
   */
  function _setStrokeFillOpacity(attributes) {
    for (var attr in colorAttributes) {

      if (typeof attributes[colorAttributes[attr]] === 'undefined' || attributes[attr] === '') {
        continue;
      }

      if (typeof attributes[attr] === 'undefined') {
        if (!fabric.Object.prototype[attr]) {
          continue;
        }
        attributes[attr] = fabric.Object.prototype[attr];
      }

      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();
    }
    return attributes;
  }

  /**
   * @private
   */
  function _getMultipleNodes(doc, nodeNames) {
    var nodeName, nodeArray = [], nodeList;
    for (var i = 0; i < nodeNames.length; i++) {
      nodeName = nodeNames[i];
      nodeList = doc.getElementsByTagName(nodeName);
      nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList));
    }
    return nodeArray;
  }

  /**
   * 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],
        x = (args.length === 3) ? args[1] : 0,
        y = (args.length === 3) ? args[2] : 0;

      matrix[0] = Math.cos(angle);
      matrix[1] = Math.sin(angle);
      matrix[2] = -Math.sin(angle);
      matrix[3] = Math.cos(angle);
      matrix[4] = x - (matrix[0] * x + matrix[2] * y);
      matrix[5] = y - (matrix[1] * x + matrix[3] * y);
    }

    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] = Math.tan(fabric.util.degreesToRadians(args[0]));
    }

    function skewYMatrix(matrix, args) {
      matrix[1] = Math.tan(fabric.util.degreesToRadians(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 = fabric.reNum,

      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) {
          // match !== '' && match != null
          return (!!match);
        }),
          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;
    };
  })();

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

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

      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]);

      oStyle[attr] = value;
    }
  }

  /**
   * @private
   */
  function getGlobalStylesForElement(element, svgUid) {
    var styles = {};
    for (var rule in fabric.cssRules[svgUid]) {
      if (elementMatchesRule(element, rule.split(' '))) {
        for (var property in fabric.cssRules[svgUid][rule]) {
          styles[property] = fabric.cssRules[svgUid][rule][property];
        }
      }
    }
    return styles;
  }

  /**
   * @private
   */
  function elementMatchesRule(element, selectors) {
    var firstMatching, parentMatching = true;
    //start from rightmost selector.
    firstMatching = selectorMatches(element, selectors.pop());
    if (firstMatching && selectors.length) {
      parentMatching = doesSomeParentMatch(element, selectors);
    }
    return firstMatching && parentMatching && (selectors.length === 0);
  }

  function doesSomeParentMatch(element, selectors) {
    var selector, parentMatching = true;
    while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) {
      if (parentMatching) {
        selector = selectors.pop();
      }
      element = element.parentNode;
      parentMatching = selectorMatches(element, selector);
    }
    return selectors.length === 0;
  }

  /**
   * @private
   */
  function selectorMatches(element, selector) {
    var nodeName = element.nodeName,
      classNames = element.getAttribute('class'),
      id = element.getAttribute('id'), matcher;
    // i check if a selector matches slicing away part from it.
    // if i get empty string i should match
    matcher = new RegExp('^' + nodeName, 'i');
    selector = selector.replace(matcher, '');
    if (id && selector.length) {
      matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i');
      selector = selector.replace(matcher, '');
    }
    if (classNames && selector.length) {
      classNames = classNames.split(' ');
      for (var i = classNames.length; i--;) {
        matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');
        selector = selector.replace(matcher, '');
      }
    }
    return selector.length === 0;
  }

  /**
   * @private
   * to support IE8 missing getElementById on SVGdocument
   */
  function elementById(doc, id) {
    var el;
    doc.getElementById && (el = doc.getElementById(id));
    if (el) {
      return el;
    }
    var node, i, nodelist = doc.getElementsByTagName('*');
    for (i = 0; i < nodelist.length; i++) {
      node = nodelist[i];
      if (id === node.getAttribute('id')) {
        return node;
      }
    }
  }

  /**
   * @private
   */
  function parseUseDirectives(doc) {
    var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0;

    while (nodelist.length && i < nodelist.length) {
      var el = nodelist[i],
        xlink = el.getAttribute('xlink:href').substr(1),
        x = el.getAttribute('x') || 0,
        y = el.getAttribute('y') || 0,
        el2 = elementById(doc, xlink).cloneNode(true),
        currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
        parentNode, oldLength = nodelist.length, attr, j, attrs, l;

      applyViewboxTransform(el2);
      if (/^svg$/i.test(el2.nodeName)) {
        var el3 = el2.ownerDocument.createElement('g');
        for (j = 0, attrs = el2.attributes, l = attrs.length; j < l; j++) {
          attr = attrs.item(j);
          el3.setAttribute(attr.nodeName, attr.nodeValue);
        }
        // el2.firstChild != null
        while (el2.firstChild) {
          el3.appendChild(el2.firstChild);
        }
        el2 = el3;
      }

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

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

      el2.setAttribute('transform', currentTrans);
      el2.setAttribute('instantiated_by_use', '1');
      el2.removeAttribute('id');
      parentNode = el.parentNode;
      parentNode.replaceChild(el2, el);
      // some browsers do not shorten nodelist after replaceChild (IE8)
      if (nodelist.length === oldLength) {
        i++;
      }
    }
  }

  // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
  // matches, e.g.: +14.56e-12, etc.
  var reViewBoxAttrValue = new RegExp(
    '^' +
    '\\s*(' + fabric.reNum + '+)\\s*,?' +
    '\\s*(' + fabric.reNum + '+)\\s*,?' +
    '\\s*(' + fabric.reNum + '+)\\s*,?' +
    '\\s*(' + fabric.reNum + '+)\\s*' +
    '$'
  );

  /**
   * Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
   */
  function applyViewboxTransform(element) {

    var viewBoxAttr = element.getAttribute('viewBox'),
      scaleX = 1,
      scaleY = 1,
      minX = 0,
      minY = 0,
      viewBoxWidth, viewBoxHeight, matrix, el,
      widthAttr = element.getAttribute('width'),
      heightAttr = element.getAttribute('height'),
      x = element.getAttribute('x') || 0,
      y = element.getAttribute('y') || 0,
      preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '',
      missingViewBox = (!viewBoxAttr || !reViewBoxTagNames.test(element.nodeName)
        || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))),
      missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'),
      toBeParsed = missingViewBox && missingDimAttr,
      parsedDim = {}, translateMatrix = '';

    parsedDim.width = 0;
    parsedDim.height = 0;
    parsedDim.toBeParsed = toBeParsed;

    if (toBeParsed) {
      return parsedDim;
    }

    if (missingViewBox) {
      parsedDim.width = parseUnit(widthAttr);
      parsedDim.height = parseUnit(heightAttr);
      return parsedDim;
    }

    minX = -parseFloat(viewBoxAttr[1]);
    minY = -parseFloat(viewBoxAttr[2]);
    viewBoxWidth = parseFloat(viewBoxAttr[3]);
    viewBoxHeight = parseFloat(viewBoxAttr[4]);

    if (!missingDimAttr) {
      parsedDim.width = parseUnit(widthAttr);
      parsedDim.height = parseUnit(heightAttr);
      scaleX = parsedDim.width / viewBoxWidth;
      scaleY = parsedDim.height / viewBoxHeight;
    }
    else {
      parsedDim.width = viewBoxWidth;
      parsedDim.height = viewBoxHeight;
    }

    // default is to preserve aspect ratio
    preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio);
    if (preserveAspectRatio.alignX !== 'none') {
      //translate all container for the effect of Mid, Min, Max
      scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
    }

    if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) {
      return parsedDim;
    }

    if (x || y) {
      translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') ';
    }

    matrix = translateMatrix + ' matrix(' + scaleX +
      ' 0' +
      ' 0 ' +
      scaleY + ' ' +
      (minX * scaleX) + ' ' +
      (minY * scaleY) + ') ';

    if (element.nodeName === 'svg') {
      el = element.ownerDocument.createElement('g');
      // element.firstChild != null
      while (element.firstChild) {
        el.appendChild(element.firstChild);
      }
      element.appendChild(el);
    }
    else {
      el = element;
      matrix = el.getAttribute('transform') + matrix;
    }

    el.setAttribute('transform', matrix);
    return parsedDim;
  }

  /**
   * 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 () {

    function hasAncestorWithNodeName(element, nodeName) {
      while (element && (element = element.parentNode)) {
        if (element.nodeName && nodeName.test(element.nodeName.replace('svg:', ''))
          && !element.getAttribute('instantiated_by_use')) {
          return true;
        }
      }
      return false;
    }

    return function (doc, callback, reviver) {
      if (!doc) {
        return;
      }

      parseUseDirectives(doc);

      var startTime = new Date(),
        svgUid = fabric.Object.__uid++,
        options = applyViewboxTransform(doc),
        descendants = fabric.util.toArray(doc.getElementsByTagName('*'));

      options.svgUid = svgUid;

      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) {
        applyViewboxTransform(el);
        return reAllowedSVGTagNames.test(el.nodeName.replace('svg:', '')) &&
          !hasAncestorWithNodeName(el, reNotAllowedAncestors); // http://www.w3.org/TR/SVG/struct.html#DefsElement
      });

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

      fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
      fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
      // 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(
        '\t<pattern x="0" y="0" id="', property, 'Pattern" ',
        'width="', canvas[property].source.width,
        '" height="', canvas[property].source.height,
        '" patternUnits="userSpaceOnUse">\n',
        '\t\t<image x="0" y="0" ',
        'width="', canvas[property].source.width,
        '" height="', canvas[property].source.height,
        '" xlink:href="', canvas[property].source.src,
        '"></image>\n\t</pattern>\n'
      );
    }
  }

  var reFontDeclaration = new RegExp(
    '(normal|italic)?\\s*(normal|small-caps)?\\s*' +
    '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' +
    fabric.reNum +
    '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)');

  extend(fabric, {
    /**
     * Parses a short font declaration, building adding its properties to a style object
     * @static
     * @function
     * @memberOf fabric
     * @param {String} value font declaration
     * @param {Object} oStyle definition
     */
    parseFontDeclaration: function (value, oStyle) {
      var match = value.match(reFontDeclaration);

      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 = parseUnit(fontSize);
      }
      if (fontFamily) {
        oStyle.fontFamily = fontFamily;
      }
      if (lineHeight) {
        oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
      }
    },

    /**
     * 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 tagArray = [
        'linearGradient',
        'radialGradient',
        'svg:linearGradient',
        'svg:radialGradient'],
        elList = _getMultipleNodes(doc, tagArray),
        el, j = 0, id, xlink,
        gradientDefs = {}, idsToXlinkMap = {};

      j = elList.length;

      while (j--) {
        el = elList[j];
        xlink = el.getAttribute('xlink:href');
        id = el.getAttribute('id');
        if (xlink) {
          idsToXlinkMap[id] = xlink.substr(1);
        }
        gradientDefs[id] = el;
      }

      for (id in idsToXlinkMap) {
        var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true);
        el = gradientDefs[id];
        while (el2.firstChild) {
          el.appendChild(el2.firstChild);
        }
      }
      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, svgUid) {

      if (!element) {
        return;
      }

      var value,
        parentAttributes = {},
        fontSize;

      if (typeof svgUid === 'undefined') {
        svgUid = element.getAttribute('svgUid');
      }
      // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards
      if (element.parentNode && reAllowedParents.test(element.parentNode.nodeName)) {
        parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
      }
      fontSize = (parentAttributes && parentAttributes.fontSize) ||
        element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE;

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

          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, svgUid), fabric.parseStyleAttribute(element)));
      if (ownAttributes.font) {
        fabric.parseFontDeclaration(ownAttributes.font, ownAttributes);
      }
      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++) {
        // IE9 doesn't support textContent, but provides text instead.
        var styleContents = styles[i].textContent || styles[i].text;

        // remove comments
        styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
        if (styleContents.trim() === '') {
          continue;
        }
        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*\{([^}]*)\}/),
            ruleObj = {}, declaration = match[2].trim(),
            propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);

          for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
            var pair = propertyValuePairs[i].split(/\s*:\s*/),
              property = normalizeAttr(pair[0]),
              value = normalizeValue(property, pair[1], pair[0]);
            ruleObj[property] = value;
          }
          rule = match[1];
          rule.split(',').forEach(function (_rule) {
            _rule = _rule.replace(/^svg/i, '').trim();
            if (_rule === '') {
              return;
            }
            if (allRules[_rule]) {
              fabric.util.object.extend(allRules[_rule], ruleObj);
            }
            else {
              allRules[_rule] = fabric.util.object.clone(ruleObj);
            }
          });
        });
      }
      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) {
          callback && callback(null);
        }

        fabric.parseSVGDocument(xml.documentElement, function (results, options) {
          svgCache.set(url, {
            objects: fabric.util.array.invoke(results, 'toObject'),
            options: options
          });
          callback && 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,
     * font URLs for font faces must be collected by developers
     * and are not extracted from the DOM by fabricjs
     * @param {Array} objects Array of fabric objects
     * @return {String}
     */
    createSVGFontFacesMarkup: function (objects) {
      var markup = '', fontList = {}, obj, fontFamily,
        style, row, rowIndex, _char, charIndex,
        fontPaths = fabric.fontPaths;

      for (var i = 0, len = objects.length; i < len; i++) {
        obj = objects[i];
        fontFamily = obj.fontFamily;
        if (obj.type.indexOf('text') === -1 || fontList[fontFamily] || !fontPaths[fontFamily]) {
          continue;
        }
        fontList[fontFamily] = true;
        if (!obj.styles) {
          continue;
        }
        style = obj.styles;
        for (rowIndex in style) {
          row = style[rowIndex];
          for (charIndex in row) {
            _char = row[charIndex];
            fontFamily = _char.fontFamily;
            if (!fontList[fontFamily] && fontPaths[fontFamily]) {
              fontList[fontFamily] = true;
            }
          }
        }
      }

      for (var j in fontList) {
        markup += [
          '\t\t@font-face {\n',
          '\t\t\tfont-family: \'', j, '\';\n',
          '\t\t\tsrc: url(\'', fontPaths[j], '\');\n',
          '\t\t}\n'
        ].join('');
      }

      if (markup) {
        markup = [
          '\t<style type="text/css">',
          '<![CDATA[\n',
          markup,
          ']]>',
          '</style>\n'
        ].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;
  this.svgUid = (options && options.svgUid) || 0;
};

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++) {
    this.elements[i].setAttribute('svgUid', this.svgUid);
    (function (_obj, i) {
      setTimeout(function () {
        _obj.createObject(_obj.elements[i], i);
      }, 0);
    })(this, i);
  }
};

fabric.ElementsParser.prototype.createObject = function (el, index) {
  var klass = fabric[fabric.util.string.capitalize(el.tagName.replace('svg:', ''))];
  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.resolveGradient(obj, 'fill');
    this.resolveGradient(obj, 'stroke');
    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.resolveGradient(obj, 'fill');
    _this.resolveGradient(obj, 'stroke');
    _this.reviver && _this.reviver(el, obj);
    _this.instances[index] = obj;
    _this.checkIfDone();
  };
};

fabric.ElementsParser.prototype.resolveGradient = function (obj, property) {

  var instanceFillValue = obj.get(property);
  if (!(/^url\(/).test(instanceFillValue)) {
    return;
  }
  var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
  if (fabric.gradientDefs[this.svgUid][gradientId]) {
    obj.set(property,
      fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj));
  }
};

fabric.ElementsParser.prototype.checkIfDone = function () {
  if (--this.numElements === 0) {
    this.instances = this.instances.filter(function (el) {
      // eslint-disable-next-line no-eq-null, eqeqeq
      return el != null;
    });
    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 */ {

    type: 'point',

    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
     * @chainable
     */
    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
     * @chainable
     */
    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
     * @chainable
     */
    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
     * @chainable
     */
    scalarSubtractEquals: function (scalar) {
      this.x -= scalar;
      this.y -= scalar;
      return this;
    },

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

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

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

    /**
     * Divides this point by a value
     * TODO: rename in scalarDivideEquals in 2.0
     * @param {Number} scalar
     * @return {fabric.Point} thisArg
     * @chainable
     */
    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 , position of interpolation, between 0 and 1 default 0.5
     * @return {fabric.Point}
     */
    lerp: function (that, t) {
      if (typeof t === 'undefined') {
        t = 0.5;
      }
      t = Math.max(Math.min(1, t), 0);
      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 this.lerp(that);
    },

    /**
     * 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
     * @param {Number} y
     * @chainable
     */
    setXY: function (x, y) {
      this.x = x;
      this.y = y;
      return this;
    },

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

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

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

    /**
     * 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;
    },

    /**
     * return a cloned instance of the point
     * @return {fabric.Point}
     */
    clone: function () {
      return new Point(this.x, this.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 */ {

    constructor: Intersection,

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

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

  /**
   * Checks if one line intersects another
   * TODO: rename in intersectSegmentSegment
   * @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.appendPoint(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
   * TODO: rename in intersectSegmentPolygon
   * fix detection of coincident
   * @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,
      b1, b2, inter;

    for (var i = 0; i < length; i++) {
      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 {fabric.Point} r1
   * @param {fabric.Point} 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) or hsl format or from known color list
   * @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') {
        source = [255, 255, 255, 0];
      }

      if (!source) {
        source = Color.sourceFromHex(color);
      }
      if (!source) {
        source = Color.sourceFromRgb(color);
      }
      if (!source) {
        source = Color.sourceFromHsl(color);
      }
      if (!source) {
        //if color is not recognize let's make black as canvas does
        source = [0, 0, 0, 1];
      }
      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
   */
  // eslint-disable-next-line max-len
  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: #FF5544CC, #FF5555, 010155, aff)
   * @static
   * @field
   * @memberOf fabric.Color
   */
  fabric.Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[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',
    grey: '#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 representation (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 representation (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 representation (ex: [100, 100, 200, 1]) of a color that's in HEX format
   * @static
   * @memberOf fabric.Color
   * @param {String} color ex: FF5555 or FF5544CC (RGBa)
   * @return {Array} source
   */
  fabric.Color.sourceFromHex = function (color) {
    if (color.match(Color.reHex)) {
      var value = color.slice(color.indexOf('#') + 1),
        isShortNotation = (value.length === 3 || value.length === 4),
        isRGBa = (value.length === 8 || value.length === 4),
        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),
        a = isRGBa ? (isShortNotation ? (value.charAt(3) + value.charAt(3)) : value.substring(6, 8)) : 'FF';

      return [
        parseInt(r, 16),
        parseInt(g, 16),
        parseInt(b, 16),
        parseFloat((parseInt(a, 16) / 255).toFixed(2))
      ];
    }
  };

  /**
   * 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') || 0,
      color, colorAlpha, opacity;

    // convert percents to absolute values
    offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
    offset = offset < 0 ? 0 : offset > 1 ? 1 : offset;
    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');
    }

    color = new fabric.Color(color);
    colorAlpha = color.getAlpha();
    opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity);
    opacity *= colorAlpha;

    return {
      offset: offset,
      color: color.toRgb(),
      opacity: 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 */ {

    /**
     * Horizontal offset for aligning gradients coming from SVG when outside pathgroups
     * @type Number
     * @default 0
     */
    offsetX: 0,

    /**
     * Vertical offset for aligning gradients coming from SVG when outside pathgroups
     * @type Number
     * @default 0
     */
    offsetY: 0,

    /**
     * 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.colorStops = options.colorStops.slice();
      if (options.gradientTransform) {
        this.gradientTransform = options.gradientTransform;
      }
      this.offsetX = options.offsetX || this.offsetX;
      this.offsetY = options.offsetY || this.offsetY;
    },

    /**
     * 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
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
     * @return {Object}
     */
    toObject: function (propertiesToInclude) {
      var object = {
        type: this.type,
        coords: this.coords,
        colorStops: this.colorStops,
        offsetX: this.offsetX,
        offsetY: this.offsetY,
        gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform
      };
      fabric.util.populateWithProperties(this, object, propertiesToInclude);

      return object;
    },

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

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

      if (!(object.group && object.group.type === 'path-group')) {
        for (var prop in coords) {
          if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
            coords[prop] += this.offsetX - object.width / 2;
          }
          else if (prop === 'y1' || prop === 'y2') {
            coords[prop] += this.offsetY - object.height / 2;
          }
        }
      }

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

      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 !== null ? ';stop-opacity: ' + this.colorStops[i].opacity : ';'),
          '"/>\n'
        );
      }

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

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

    /**
     * Returns an instance of CanvasGradient
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Object} object
     * @return {CanvasGradient}
     */
    toLive: function (ctx, object) {
      var gradient, prop, coords = fabric.util.object.clone(this.coords);

      if (!this.type) {
        return;
      }

      if (object.group && object.group.type === 'path-group') {
        for (prop in coords) {
          if (prop === 'x1' || prop === 'x2') {
            coords[prop] += -this.offsetX + object.width / 2;
          }
          else if (prop === 'y1' || prop === 'y2') {
            coords[prop] += -this.offsetY + object.height / 2;
          }
        }
      }

      if (this.type === 'linear') {
        gradient = ctx.createLinearGradient(
          coords.x1, coords.y1, coords.x2, coords.y2);
      }
      else if (this.type === 'radial') {
        gradient = ctx.createRadialGradient(
          coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, 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,
        gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
        gradientTransform = el.getAttribute('gradientTransform'),
        colorStops = [],
        coords, ellipseMatrix;

      if (el.nodeName === 'linearGradient' || el.nodeName === 'LINEARGRADIENT') {
        type = 'linear';
      }
      else {
        type = 'radial';
      }

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

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

      ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits);

      var gradient = new fabric.Gradient({
        type: type,
        coords: coords,
        colorStops: colorStops,
        offsetX: -instance.left,
        offsetY: -instance.top
      });

      if (gradientTransform || ellipseMatrix !== '') {
        gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix);
      }
      return gradient;
    },
    /* _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.coords, 'userSpaceOnUse');
      return new fabric.Gradient(options);
    }
  });

  /**
   * @private
   */
  function _convertPercentUnitsToValues(object, options, gradientUnits) {
    var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = '';
    for (var prop in options) {
      if (options[prop] === 'Infinity') {
        options[prop] = 1;
      }
      else if (options[prop] === '-Infinity') {
        options[prop] = 0;
      }
      propValue = parseFloat(options[prop], 10);
      if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) {
        multFactor = 0.01;
      }
      else {
        multFactor = 1;
      }
      if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
        multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1;
        addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0;
      }
      else if (prop === 'y1' || prop === 'y2') {
        multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1;
        addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0;
      }
      options[prop] = propValue * multFactor + addFactor;
    }
    if (object.type === 'ellipse' &&
      options.r2 !== null &&
      gradientUnits === 'objectBoundingBox' &&
      object.rx !== object.ry) {

      var scaleFactor = object.ry / object.rx;
      ellipseMatrix = ' scale(1, ' + scaleFactor + ')';
      if (options.y1) {
        options.y1 /= scaleFactor;
      }
      if (options.y2) {
        options.y2 /= scaleFactor;
      }
    }
    return ellipseMatrix;
  }
})();


/**
 * 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
   * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
   * @return {Object} Object representation of a pattern instance
   */
  toObject: function (propertiesToInclude) {

    var source, object;

    // callback
    if (typeof this.source === 'function') {
      source = String(this.source);
    }
    // <img> element
    else if (typeof this.source.src === 'string') {
      source = this.source.src;
    }
    // <canvas> element
    else if (typeof this.source === 'object' && this.source.toDataURL) {
      source = this.source.toDataURL();
    }

    object = {
      source: source,
      repeat: this.repeat,
      offsetX: this.offsetX,
      offsetY: this.offsetY
    };
    fabric.util.populateWithProperties(this, object, propertiesToInclude);

    return object;
  },

  /* _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(),
      patternOffsetX = this.offsetX / object.getWidth(),
      patternOffsetY = this.offsetY / object.getHeight(),
      patternImgSrc = '';
    if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') {
      patternHeight = 1;
    }
    if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') {
      patternWidth = 1;
    }
    if (patternSource.src) {
      patternImgSrc = patternSource.src;
    }
    else if (patternSource.toDataURL) {
      patternImgSrc = patternSource.toDataURL();
    }

    return '<pattern id="SVGID_' + this.id +
      '" x="' + patternOffsetX +
      '" y="' + patternOffsetY +
      '" width="' + patternWidth +
      '" height="' + patternHeight + '">\n' +
      '<image x="0" y="0"' +
      ' width="' + patternSource.width +
      '" height="' + patternSource.height +
      '" xlink:href="' + patternImgSrc +
      '"></image>\n' +
      '</pattern>\n';
  },
  /* _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 = {}),
    toFixed = fabric.util.toFixed;

  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 fBoxX = 40, fBoxY = 40, NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
        offset = fabric.util.rotateVector(
          { x: this.offsetX, y: this.offsetY },
          fabric.util.degreesToRadians(-object.angle)),
        BLUR_BOX = 20;

      if (object.width && object.height) {
        //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion
        // we add some extra space to filter box to contain the blur ( 20 )
        fBoxX = toFixed((Math.abs(offset.x) + this.blur) / object.width, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX;
        fBoxY = toFixed((Math.abs(offset.y) + this.blur) / object.height, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX;
      }
      if (object.flipX) {
        offset.x *= -1;
      }
      if (object.flipY) {
        offset.y *= -1;
      }
      return (
        '<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' +
        'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + '>\n' +
        '\t<feGaussianBlur in="SourceAlpha" stdDeviation="' +
        toFixed(this.blur ? this.blur / 2 : 0, NUM_FRACTION_DIGITS) + '"></feGaussianBlur>\n' +
        '\t<feOffset dx="' + toFixed(offset.x, NUM_FRACTION_DIGITS) +
        '" dy="' + toFixed(offset.y, NUM_FRACTION_DIGITS) + '" result="oBlur" ></feOffset>\n' +
        '\t<feFlood flood-color="' + this.color + '"/>\n' +
        '\t<feComposite in2="oBlur" operator="in" />\n' +
        '\t<feMerge>\n' +
        '\t\t<feMergeNode></feMergeNode>\n' +
        '\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' +
        '\t</feMerge>\n' +
        '</filter>\n');
    },
    /* _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,
          affectStroke: this.affectStroke
        };
      }
      var obj = {}, proto = fabric.Shadow.prototype;

      ['color', 'blur', 'offsetX', 'offsetY', 'affectStroke'].forEach(function (prop) {
        if (this[prop] !== proto[prop]) {
          obj[prop] = this[prop];
        }
      }, this);

      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
   */
  // eslint-disable-next-line max-len
  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,
    toFixed = fabric.util.toFixed,

    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);
    },

    /**
     * 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: false,

    /**
     * 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],

    /**
     * if set to false background image is not affected by viewport transform
     * @since 1.6.3
     * @type Boolean
     * @default
     */
    backgroundVpt: true,

    /**
     * if set to false overlya image is not affected by viewport transform
     * @since 1.6.3
     * @type Boolean
     * @default
     */
    overlayVpt: true,

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

    /**
     * When true, canvas is scaled by devicePixelRatio for better rendering on retina screens
     */
    enableRetinaScaling: true,

    /**
     * @private
     * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
     * @param {Object} [options] Options object
     */
    _initStatic: function (el, options) {
      var cb = fabric.StaticCanvas.prototype.renderAll.bind(this);
      this._objects = [];
      this._createLowerCanvas(el);
      this._initOptions(options);
      this._setImageSmoothing();
      // only initialize retina scaling once
      if (!this.interactive) {
        this._initRetinaScaling();
      }

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

    /**
     * @private
     */
    _isRetinaScaling: function () {
      return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling);
    },

    /**
     * @private
     * @return {Number} retinaScaling if applied, otherwise 1;
     */
    getRetinaScaling: function () {
      return this._isRetinaScaling() ? fabric.devicePixelRatio : 1;
    },

    /**
     * @private
     */
    _initRetinaScaling: function () {
      if (!this._isRetinaScaling()) {
        return;
      }
      this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio);
      this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio);

      this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio);
    },

    /**
     * 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'
     * });
     * @example <caption>overlayImage loaded from cross-origin</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',
     *   crossOrigin: 'anonymous'
     * });
     */
    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'
     * });
     * @example <caption>backgroundImage loaded from cross-origin</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',
     *   crossOrigin: 'anonymous'
     * });
     */
    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 = ctx.imageSmoothingEnabled || ctx.webkitImageSmoothingEnabled
        || ctx.mozImageSmoothingEnabled || ctx.msImageSmoothingEnabled || ctx.oImageSmoothingEnabled;
      ctx.imageSmoothingEnabled = 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) {
          img && (this[property] = new fabric.Image(img, options));
          callback && callback(img);
        }, this, options && options.crossOrigin);
      }
      else {
        options && image.setOptions(options);
        this[property] = image;
        callback && callback(image);
      }

      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 (canvasEl) {
      var element = fabric.util.createCanvasElement(canvasEl)
      if (!element.style) {
        element.style = {};
      }
      if (!element) {
        throw CANVAS_INIT_ERROR;
      }
      if (typeof element.getContext === 'undefined') {
        throw CANVAS_INIT_ERROR;
      }
      return element;
    },

    /**
     * @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(canvasEl);

      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|String} value                         Value to set width to
     * @param {Object}        [options]                     Options object
     * @param {Boolean}       [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
     * @param {Boolean}       [options.cssOnly=false]       Set the given dimensions only as css dimensions
     * @return {fabric.Canvas} instance
     * @chainable true
     */
    setWidth: function (value, options) {
      return this.setDimensions({ width: value }, options);
    },

    /**
     * Sets height of this canvas instance
     * @param {Number|String} value                         Value to set height to
     * @param {Object}        [options]                     Options object
     * @param {Boolean}       [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
     * @param {Boolean}       [options.cssOnly=false]       Set the given dimensions only as css dimensions
     * @return {fabric.Canvas} instance
     * @chainable true
     */
    setHeight: function (value, options) {
      return this.setDimensions({ height: value }, options);
    },

    /**
     * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em)
     * @param {Object}        dimensions                    Object with width/height properties
     * @param {Number|String} [dimensions.width]            Width of canvas element
     * @param {Number|String} [dimensions.height]           Height of canvas element
     * @param {Object}        [options]                     Options object
     * @param {Boolean}       [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
     * @param {Boolean}       [options.cssOnly=false]       Set the given dimensions only as css dimensions
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    setDimensions: function (dimensions, options) {
      var cssValue;

      options = options || {};

      for (var prop in dimensions) {
        cssValue = dimensions[prop];

        if (!options.cssOnly) {
          this._setBackstoreDimension(prop, dimensions[prop]);
          cssValue += 'px';
        }

        if (!options.backstoreOnly) {
          this._setCssDimension(prop, cssValue);
        }
      }
      this._initRetinaScaling();
      this._setImageSmoothing();
      this.calcOffset();

      if (!options.cssOnly) {
        this.renderAll();
      }

      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
     */
    _setBackstoreDimension: function (prop, value) {
      this.lowerCanvasEl[prop] = value;

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

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

      this[prop] = value;

      return this;
    },

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

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

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

      return this;
    },

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

    /**
     * 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) {
      var activeGroup = this._activeGroup, object;
      this.viewportTransform = vpt;
      for (var i = 0, len = this._objects.length; i < len; i++) {
        object = this._objects[i];
        object.group || object.setCoords();
      }
      if (activeGroup) {
        activeGroup.setCoords();
      }
      this.renderAll();
      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, vpt = this.viewportTransform.slice(0);
      point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform));
      vpt[0] = value;
      vpt[3] = value;
      var after = fabric.util.transformPoint(point, vpt);
      vpt[4] += before.x - after.x;
      vpt[5] += before.y - after.y;
      return this.setViewportTransform(vpt);
    },

    /**
     * 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) {
      var vpt = this.viewportTransform.slice(0);
      vpt[4] = -point.x;
      vpt[5] = -point.y;
      return this.setViewportTransform(vpt);
    },

    /**
     * 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;
    },

    /**
     * @private
     * @param {fabric.Object} obj Object that was added
     */
    _onObjectAdded: function (obj) {
      this.stateful && obj.setupState();
      obj._set('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) {
      this.fire('object:removed', { target: obj });
      obj.fire('removed');
      delete obj.canvas;
    },

    /**
     * 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;
      this.backgroundImage = null;
      this.overlayImage = null;
      this.backgroundColor = '';
      this.overlayColor = ''
      if (this._hasITextHandlers) {
        this.off('selection:cleared', this._canvasITextSelectionClearedHanlder);
        this.off('object:selected', this._canvasITextSelectionClearedHanlder);
        this.off('mouse:up', this._mouseUpITextHandler);
        this._iTextInstances = null;
        this._hasITextHandlers = false;
      }
      this.clearContext(this.contextContainer);
      this.fire('canvas:cleared');
      this.renderAll();
      return this;
    },

    /**
     * Renders both the canvas.
     * @return {fabric.Canvas} instance
     * @chainable
     */
    renderAll: function () {
      var canvasToDrawOn = this.contextContainer;
      this.renderCanvas(canvasToDrawOn, this._objects);
      return this;
    },

    /**
     * Renders background, objects, overlay and controls.
     * @param {CanvasRenderingContext2D} ctx
     * @param {Array} objects to render
     * @return {fabric.Canvas} instance
     * @chainable
     */
    renderCanvas: function (ctx, objects) {
      this.clearContext(ctx);
      this.fire('before:render');
      if (this.clipTo) {
        fabric.util.clipContext(this, ctx);
      }
      this._renderBackground(ctx);

      ctx.save();
      //apply viewport transform once for all rendering process
      ctx.transform.apply(ctx, this.viewportTransform);
      this._renderObjects(ctx, objects);
      ctx.restore();
      if (!this.controlsAboveOverlay && this.interactive) {
        this.drawControls(ctx);
      }
      if (this.clipTo) {
        ctx.restore();
      }
      this._renderOverlay(ctx);
      if (this.controlsAboveOverlay && this.interactive) {
        this.drawControls(ctx);
      }
      this.fire('after:render');
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Array} objects to render
     */
    _renderObjects: function (ctx, objects) {
      for (var i = 0, length = objects.length; i < length; ++i) {
        objects[i] && objects[i].render(ctx);
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {string} property 'background' or 'overlay'
     */
    _renderBackgroundOrOverlay: function (ctx, property) {
      var object = this[property + 'Color'];
      if (object) {
        ctx.fillStyle = object.toLive
          ? object.toLive(ctx)
          : object;

        ctx.fillRect(
          object.offsetX || 0,
          object.offsetY || 0,
          this.width,
          this.height);
      }
      object = this[property + 'Image'];
      if (object) {
        if (this[property + 'Vpt']) {
          ctx.save();
          ctx.transform.apply(ctx, this.viewportTransform);
        }
        object.render(ctx);
        this[property + 'Vpt'] && ctx.restore();
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderBackground: function (ctx) {
      this._renderBackgroundOrOverlay(ctx, 'background');
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderOverlay: function (ctx) {
      this._renderBackgroundOrOverlay(ctx, 'overlay');
    },

    /**
     * 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 in the canvas
     * 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) {
      return this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y));
    },

    /**
     * Centers object vertically in the canvas
     * 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) {
      return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top));
    },

    /**
     * Centers object vertically and horizontally in the canvas
     * 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();

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

    /**
     * Centers object vertically and horizontally in the viewport
     * 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
     */
    viewportCenterObject: function (object) {
      var vpCenter = this.getVpCenter();

      return this._centerObject(object, vpCenter);
    },

    /**
     * Centers object horizontally in the viewport, object.top is unchanged
     * 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
     */
    viewportCenterObjectH: function (object) {
      var vpCenter = this.getVpCenter();
      this._centerObject(object, new fabric.Point(vpCenter.x, object.getCenterPoint().y));
      return this;
    },

    /**
     * Centers object Vertically in the viewport, object.top is unchanged
     * 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
     */
    viewportCenterObjectV: function (object) {
      var vpCenter = this.getVpCenter();

      return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, vpCenter.y));
    },

    /**
     * Calculate the point in canvas that correspond to the center of actual viewport.
     * @return {fabric.Point} vpCenter, viewport center
     * @chainable
     */
    getVpCenter: function () {
      var center = this.getCenter(),
        iVpt = fabric.util.invertTransform(this.viewportTransform);
      return fabric.util.transformPoint({ x: center.left, y: center.top }, iVpt);
    },

    /**
     * @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');
      this.renderAll();
      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 data = {
        objects: this._toObjects(methodName, propertiesToInclude)
      };

      extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude));

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

      return data;
    },

    /**
     * @private
     */
    _toObjects: function (methodName, propertiesToInclude) {
      return this.getObjects().filter(function (object) {
        return !object.excludeFromExport;
      }).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 (methodName, propertiesToInclude) {
      var data = {
        background: (this.backgroundColor && this.backgroundColor.toObject)
          ? this.backgroundColor.toObject(propertiesToInclude)
          : this.backgroundColor
      };

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

      return data;
    },

    /* _TO_SVG_START_ */
    /**
     * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true,
     * a zoomed canvas will then produce zoomed SVG output.
     * @type Boolean
     * @default
     */
    svgViewportTransformation: true,

    /**
     * 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 {String} [options.width] desired width of svg with or without units
     * @param {String} [options.height] desired height of svg with or without units
     * @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', reviver);

      this._setSVGObjects(markup, reviver);

      this._setSVGBgOverlayColor(markup, 'overlayColor');
      this._setSVGBgOverlayImage(markup, 'overlayImage', reviver);

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

      return markup.join('');
    },

    /**
     * @private
     */
    _setSVGPreamble: function (markup, options) {
      if (options.suppressPreamble) {
        return;
      }
      markup.push(
        '<?xml version="1.0" encoding="', (options.encoding || 'UTF-8'), '" standalone="no" ?>\n',
        '<!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) {
      var width = options.width || this.width,
        height = options.height || this.height,
        vpt, viewBox = 'viewBox="0 0 ' + this.width + ' ' + this.height + '" ',
        NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;

      if (options.viewBox) {
        viewBox = 'viewBox="' +
          options.viewBox.x + ' ' +
          options.viewBox.y + ' ' +
          options.viewBox.width + ' ' +
          options.viewBox.height + '" ';
      }
      else {
        if (this.svgViewportTransformation) {
          vpt = this.viewportTransform;
          viewBox = 'viewBox="' +
            toFixed(-vpt[4] / vpt[0], NUM_FRACTION_DIGITS) + ' ' +
            toFixed(-vpt[5] / vpt[3], NUM_FRACTION_DIGITS) + ' ' +
            toFixed(this.width / vpt[0], NUM_FRACTION_DIGITS) + ' ' +
            toFixed(this.height / vpt[3], NUM_FRACTION_DIGITS) + '" ';
        }
      }

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

    /**
     * @private
     */
    _setSVGObjects: function (markup, reviver) {
      var instance;
      for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) {
        instance = objects[i];
        if (instance.excludeFromExport) {
          continue;
        }
        this._setSVGObject(markup, instance, reviver);
      }
    },

    /**
     * push single object svg representation in the markup
     * @private
     */
    _setSVGObject: function (markup, instance, reviver) {
      markup.push(instance.toSVG(reviver));
    },

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

    /**
     * @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>\n'
        );
      }
      else if (this[property] && property === 'overlayColor') {
        markup.push(
          '<rect x="0" y="0" ',
          'width="', this.width,
          '" height="', this.height,
          '" fill="', this[property], '"',
          '></rect>\n'
        );
      }
    },
    /* _TO_SVG_END_ */

    /**
     * Moves an object or the objects of a multiple selection
     * 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) {
      if (!object) {
        return this;
      }
      var activeGroup = this._activeGroup,
        i, obj, objs;
      if (object === activeGroup) {
        objs = activeGroup._objects;
        for (i = objs.length; i--;) {
          obj = objs[i];
          removeFromArray(this._objects, obj);
          this._objects.unshift(obj);
        }
      }
      else {
        removeFromArray(this._objects, object);
        this._objects.unshift(object);
      }
      return this.renderAll && this.renderAll();
    },

    /**
     * Moves an object or the objects of a multiple selection
     * to the top of the stack of drawn objects
     * @param {fabric.Object} object Object to send
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    bringToFront: function (object) {
      if (!object) {
        return this;
      }
      var activeGroup = this._activeGroup,
        i, obj, objs;
      if (object === activeGroup) {
        objs = activeGroup._objects;
        for (i = 0; i < objs.length; i++) {
          obj = objs[i];
          removeFromArray(this._objects, obj);
          this._objects.push(obj);
        }
      }
      else {
        removeFromArray(this._objects, object);
        this._objects.push(object);
      }
      return this.renderAll && this.renderAll();
    },

    /**
     * Moves an object or a selection 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) {
      if (!object) {
        return this;
      }
      var activeGroup = this._activeGroup,
        i, obj, idx, newIdx, objs;

      if (object === activeGroup) {
        objs = activeGroup._objects;
        for (i = 0; i < objs.length; i++) {
          obj = objs[i];
          idx = this._objects.indexOf(obj);
          if (idx !== 0) {
            newIdx = idx - 1;
            removeFromArray(this._objects, obj);
            this._objects.splice(newIdx, 0, obj);
          }
        }
      }
      else {
        idx = this._objects.indexOf(object);
        if (idx !== 0) {
          // if object is not on the bottom of stack
          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 or a selection 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) {
      if (!object) {
        return this;
      }
      var activeGroup = this._activeGroup,
        i, obj, idx, newIdx, objs;

      if (object === activeGroup) {
        objs = activeGroup._objects;
        for (i = objs.length; i--;) {
          obj = objs[i];
          idx = this._objects.indexOf(obj);
          if (idx !== this._objects.length - 1) {
            newIdx = idx + 1;
            removeFromArray(this._objects, obj);
            this._objects.splice(newIdx, 0, obj);
          }
        }
      }
      else {
        idx = this._objects.indexOf(object);
        if (idx !== this._objects.length - 1) {
          // if object is not on top of stack (last item in an array)
          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();
      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',

  /**
   * Stroke Dash Array.
   * @type Array
   * @default
   */
  strokeDashArray: null,

  /**
   * 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;
    if (this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) {
      ctx.setLineDash(this.strokeDashArray);
    }
  },

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

    var ctx = this.canvas.contextTop,
      zoom = this.canvas.getZoom();

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

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

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


(function () {

  /**
   * 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();
    },

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

      path.push('M ', points[0].x, ' ', points[0].y, ' ');
      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, points[i].y);
        if ((i + 1) < points.length) {
          p2 = new fabric.Point(points[i + 1].x, points[i + 1].y);
        }
      }
      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, {
        fill: null,
        stroke: this.color,
        strokeWidth: this.width,
        strokeLineCap: this.strokeLineCap,
        strokeLineJoin: this.strokeLineJoin,
        strokeDashArray: this.strokeDashArray,
        originX: 'center',
        originY: 'center'
      });

      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.convertPointsToSVGPath(this._points).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;
      }

      var path = this.createPath(pathData);

      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),
      topLeft = path._getLeftTopCoords().scalarAdd(path.strokeWidth / 2);

    path.stroke = new fabric.Pattern({
      source: this.source || this.getPatternSrcFunction(),
      offsetX: -topLeft.x,
      offsetY: -topLeft.y
    });
    return path;
  }
});


(function () {

  var getPointer = fabric.util.getPointer,
    degreesToRadians = fabric.util.degreesToRadians,
    radiansToDegrees = fabric.util.radiansToDegrees,
    atan2 = Math.atan2,
    abs = Math.abs,
    supportLineDash = fabric.StaticCanvas.supports('setLineDash'),

    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:added
   * @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();
    },

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

    /**
     * Indicates which key enable unproportional scaling
     * values: 'altKey', 'shiftKey', 'ctrlKey'.
     * If `null` or 'none' or any other string that is not a modifier key
     * feature is disabled feature disabled.
     * @since 1.6.2
     * @type String
     * @default
     */
    uniScaleKey: 'shiftKey',

    /**
     * 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 which key enable centered Transfrom
     * values: 'altKey', 'shiftKey', 'ctrlKey'.
     * If `null` or 'none' or any other string that is not a modifier key
     * feature is disabled feature disabled.
     * @since 1.6.2
     * @type String
     * @default
     */
    centeredKey: 'altKey',

    /**
     * Indicates which key enable alternate action on corner
     * values: 'altKey', 'shiftKey', 'ctrlKey'.
     * If `null` or 'none' or any other string that is not a modifier key
     * feature is disabled feature disabled.
     * @since 1.6.2
     * @type String
     * @default
     */
    altActionKey: 'shiftKey',

    /**
     * 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,

    /**
     * Indicates which key enable multiple click selection
     * values: 'altKey', 'shiftKey', 'ctrlKey'.
     * If `null` or 'none' or any other string that is not a modifier key
     * feature is disabled feature disabled.
     * @since 1.6.2
     * @type String
     * @default
     */
    selectionKey: 'shiftKey',

    /**
     * Indicates which key enable alternative selection
     * in case of target overlapping with active object
     * values: 'altKey', 'shiftKey', 'ctrlKey'.
     * If `null` or 'none' or any other string that is not a modifier key
     * feature is disabled feature disabled.
     * @since 1.6.5
     * @type null|String
     * @default
     */
    altSelectionKey: null,

    /**
     * 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,

    /**
     * When true, mouse events on canvas (mousedown/mousemove/mouseup) result in free drawing.
     * After mousedown, mousemove creates a shape,
     * and then mouseup finalizes it and adds an instance of `fabric.Path` onto canvas.
     * @tutorial {@link http://fabricjs.com/fabric-intro-part-4#free_drawing}
     * @type Boolean
     * @default
     */
    isDrawingMode: false,

    /**
     * Indicates whether objects should remain in current stack position when selected.
     * When false objects are brought to top and rendered as part of the selection group
     * @type Boolean
     * @default
     */
    preserveObjectStacking: false,

    /**
     * Indicates the angle that an object will lock to while rotating.
     * @type Number
     * @since 1.6.7
     * @default
     */
    snapAngle: 0,

    /**
     * Indicates the distance from the snapAngle the rotation will lock to the snapAngle.
     * When `null`, the snapThreshold will default to the snapAngle.
     * @type null|Number
     * @since 1.6.7
     * @default
     */
    snapThreshold: null,

    /**
     * Indicates if the right click on canvas can output the context menu or not
     * @type Boolean
     * @since 1.6.5
     * @default
     */
    stopContextMenu: false,

    /**
     * Indicates if the canvas can fire right click events
     * @type Boolean
     * @since 1.6.5
     * @default
     */
    fireRightClick: false,

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

      this._initRetinaScaling();

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

      this.calcOffset();
    },

    /**
     * Divides objects in two groups, one to render immediately
     * and one to render as activeGroup.
     * @return {Array} objects to render immediately and pushes the other in the activeGroup.
     */
    _chooseObjectsToRender: function () {
      var activeGroup = this.getActiveGroup(),
        activeObject = this.getActiveObject(),
        object, objsToRender = [], activeGroupObjects = [];

      if ((activeGroup || activeObject) && !this.preserveObjectStacking) {
        for (var i = 0, length = this._objects.length; i < length; i++) {
          object = this._objects[i];
          if ((!activeGroup || !activeGroup.contains(object)) && object !== activeObject) {
            objsToRender.push(object);
          }
          else {
            activeGroupObjects.push(object);
          }
        }
        if (activeGroup) {
          activeGroup._set('_objects', activeGroupObjects);
          objsToRender.push(activeGroup);
        }
        activeObject && objsToRender.push(activeObject);
      }
      else {
        objsToRender = this._objects;
      }
      return objsToRender;
    },

    /**
     * Renders both the top canvas and the secondary container canvas.
     * @return {fabric.Canvas} instance
     * @chainable
     */
    renderAll: function () {
      if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) {
        this.clearContext(this.contextTop);
        this.contextTopDirty = false;
      }
      var canvasToDrawOn = this.contextContainer;
      this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender());
      return this;
    },

    /**
     * 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.clearContext(ctx);

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

      this.fire('after:render');
      this.contextTopDirty = true;
      return this;
    },

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

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

      if (this._shouldCenterTransform(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
     * @param {Object} [point] x,y object of point coordinates we want to check.
     * @return {Boolean} true if point is contained within an area of given object
     */
    containsPoint: function (e, target, point) {
      var ignoreZoom = true,
        pointer = point || this.getPointer(e, ignoreZoom),
        xy;

      if (target.group && target.group === this.getActiveGroup()) {
        xy = this._normalizePointer(target.group, pointer);
      }
      else {
        xy = { x: pointer.x, y: pointer.y };
      }
      // 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 m = object.calcTransformMatrix(),
        invertedM = fabric.util.invertTransform(m),
        vptPointer = this.restorePointerVpt(pointer);
      return fabric.util.transformPoint(vptPointer, invertedM);
    },

    /**
     * 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,
        ctx = this.contextCache,
        originalColor = target.selectionBackgroundColor;

      target.hasBorders = target.transparentCorners = false;
      target.selectionBackgroundColor = '';

      ctx.save();
      ctx.transform.apply(ctx, this.viewportTransform);
      target.render(ctx);
      ctx.restore();

      target.active && target._renderControls(ctx);

      target.hasBorders = hasBorders;
      target.transparentCorners = transparentCorners;
      target.selectionBackgroundColor = originalColor;

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

      this.clearContext(ctx);

      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[this.selectionKey])
        ||
        (target && !target.evented)
        ||
        (target &&
          !target.selectable &&
          activeObject &&
          activeObject !== target)
      );
    },

    /**
     * @private
     * @param {fabric.Object} target
     */
    _shouldCenterTransform: function (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 ? !t.altKey : t.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, e) {
      if (!corner) {
        return 'drag';
      }

      switch (corner) {
        case 'mtr':
          return 'rotate';
        case 'ml':
        case 'mr':
          return e[this.altActionKey] ? 'skewY' : 'scaleX';
        case 'mt':
        case 'mb':
          return e[this.altActionKey] ? 'skewX' : 'scaleY';
        default:
          return 'scale';
      }
    },

    /**
     * @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, e),
        origin = this._getOriginFromCorner(target, corner);

      this._currentTransform = {
        target: target,
        action: action,
        corner: corner,
        scaleX: target.scaleX,
        scaleY: target.scaleY,
        skewX: target.skewX,
        skewY: target.skewY,
        offsetX: pointer.x - target.left,
        offsetY: pointer.y - target.top,
        originX: origin.x,
        originY: origin.y,
        ex: pointer.x,
        ey: pointer.y,
        lastX: pointer.x,
        lastY: pointer.y,
        left: target.left,
        top: target.top,
        theta: degreesToRadians(target.angle),
        width: target.width * target.scaleX,
        mouseXSign: 1,
        mouseYSign: 1,
        shiftKey: e.shiftKey,
        altKey: e[this.centeredKey]
      };

      this._currentTransform.original = {
        left: target.left,
        top: target.top,
        scaleX: target.scaleX,
        scaleY: target.scaleY,
        skewX: target.skewX,
        skewY: target.skewY,
        originX: origin.x,
        originY: origin.y
      };

      this._resetCurrentTransform();
    },

    /**
     * Translates object by "setting" its left/top
     * @private
     * @param {Number} x pointer's x coordinate
     * @param {Number} y pointer's y coordinate
     * @return {Boolean} true if the translation occurred
     */
    _translateObject: function (x, y) {
      var transform = this._currentTransform,
        target = transform.target,
        newLeft = x - transform.offsetX,
        newTop = y - transform.offsetY,
        moveX = !target.get('lockMovementX') && target.left !== newLeft,
        moveY = !target.get('lockMovementY') && target.top !== newTop;

      moveX && target.set('left', newLeft);
      moveY && target.set('top', newTop);
      return moveX || moveY;
    },

    /**
     * Check if we are increasing a positive skew or lower it,
     * checking mouse direction and pressed corner.
     * @private
     */
    _changeSkewTransformOrigin: function (mouseMove, t, by) {
      var property = 'originX', origins = { 0: 'center' },
        skew = t.target.skewX, originA = 'left', originB = 'right',
        corner = t.corner === 'mt' || t.corner === 'ml' ? 1 : -1,
        flipSign = 1;

      mouseMove = mouseMove > 0 ? 1 : -1;
      if (by === 'y') {
        skew = t.target.skewY;
        originA = 'top';
        originB = 'bottom';
        property = 'originY';
      }
      origins[-1] = originA;
      origins[1] = originB;

      t.target.flipX && (flipSign *= -1);
      t.target.flipY && (flipSign *= -1);

      if (skew === 0) {
        t.skewSign = -corner * mouseMove * flipSign;
        t[property] = origins[-mouseMove];
      }
      else {
        skew = skew > 0 ? 1 : -1;
        t.skewSign = skew;
        t[property] = origins[skew * corner * flipSign];
      }
    },

    /**
     * Skew object by mouse events
     * @private
     * @param {Number} x pointer's x coordinate
     * @param {Number} y pointer's y coordinate
     * @param {String} by Either 'x' or 'y'
     * @return {Boolean} true if the skewing occurred
     */
    _skewObject: function (x, y, by) {
      var t = this._currentTransform,
        target = t.target, skewed = false,
        lockSkewingX = target.get('lockSkewingX'),
        lockSkewingY = target.get('lockSkewingY');

      if ((lockSkewingX && by === 'x') || (lockSkewingY && by === 'y')) {
        return false;
      }

      // Get the constraint point
      var center = target.getCenterPoint(),
        actualMouseByCenter = target.toLocalPoint(new fabric.Point(x, y), 'center', 'center')[by],
        lastMouseByCenter = target.toLocalPoint(new fabric.Point(t.lastX, t.lastY), 'center', 'center')[by],
        actualMouseByOrigin, constraintPosition, dim = target._getTransformedDimensions();

      this._changeSkewTransformOrigin(actualMouseByCenter - lastMouseByCenter, t, by);
      actualMouseByOrigin = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY)[by];
      constraintPosition = target.translateToOriginPoint(center, t.originX, t.originY);
      // Actually skew the object
      skewed = this._setObjectSkew(actualMouseByOrigin, t, by, dim);
      t.lastX = x;
      t.lastY = y;
      // Make sure the constraints apply
      target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
      return skewed;
    },

    /**
     * Set object skew
     * @private
     * @return {Boolean} true if the skewing occurred
     */
    _setObjectSkew: function (localMouse, transform, by, _dim) {
      var target = transform.target, newValue, skewed = false,
        skewSign = transform.skewSign, newDim, dimNoSkew,
        otherBy, _otherBy, _by, newDimMouse, skewX, skewY;

      if (by === 'x') {
        otherBy = 'y';
        _otherBy = 'Y';
        _by = 'X';
        skewX = 0;
        skewY = target.skewY;
      }
      else {
        otherBy = 'x';
        _otherBy = 'X';
        _by = 'Y';
        skewX = target.skewX;
        skewY = 0;
      }

      dimNoSkew = target._getTransformedDimensions(skewX, skewY);
      newDimMouse = 2 * Math.abs(localMouse) - dimNoSkew[by];
      if (newDimMouse <= 2) {
        newValue = 0;
      }
      else {
        newValue = skewSign * Math.atan((newDimMouse / target['scale' + _by]) /
          (dimNoSkew[otherBy] / target['scale' + _otherBy]));
        newValue = fabric.util.radiansToDegrees(newValue);
      }
      skewed = target['skew' + _by] !== newValue;
      target.set('skew' + _by, newValue);
      if (target['skew' + _otherBy] !== 0) {
        newDim = target._getTransformedDimensions();
        newValue = (_dim[otherBy] / newDim[otherBy]) * target['scale' + _otherBy];
        target.set('scale' + _otherBy, newValue);
      }
      return skewed;
    },

    /**
     * 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
     * @return {Boolean} true if the scaling occurred
     */
    _scaleObject: function (x, y, by) {
      var t = this._currentTransform,
        target = t.target,
        lockScalingX = target.get('lockScalingX'),
        lockScalingY = target.get('lockScalingY'),
        lockScalingFlip = target.get('lockScalingFlip');

      if (lockScalingX && lockScalingY) {
        return false;
      }

      // 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),
        dim = target._getTransformedDimensions(), scaled = false;

      this._setLocalMouse(localMouse, t);

      // Actually scale the object
      scaled = this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip, dim);

      // Make sure the constraints apply
      target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
      return scaled;
    },

    /**
     * @private
     * @return {Boolean} true if the scaling occurred
     */
    _setObjectScale: function (localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip, _dim) {
      var target = transform.target, forbidScalingX = false, forbidScalingY = false, scaled = false,
        changeX, changeY, scaleX, scaleY;

      scaleX = localMouse.x * target.scaleX / _dim.x;
      scaleY = localMouse.y * target.scaleY / _dim.y;
      changeX = target.scaleX !== scaleX;
      changeY = target.scaleY !== scaleY;

      if (lockScalingFlip && scaleX <= 0 && scaleX < target.scaleX) {
        forbidScalingX = true;
      }

      if (lockScalingFlip && scaleY <= 0 && scaleY < target.scaleY) {
        forbidScalingY = true;
      }

      if (by === 'equally' && !lockScalingX && !lockScalingY) {
        forbidScalingX || forbidScalingY || (scaled = this._scaleObjectEqually(localMouse, target, transform, _dim));
      }
      else if (!by) {
        forbidScalingX || lockScalingX || (target.set('scaleX', scaleX) && (scaled = scaled || changeX));
        forbidScalingY || lockScalingY || (target.set('scaleY', scaleY) && (scaled = scaled || changeY));
      }
      else if (by === 'x' && !target.get('lockUniScaling')) {
        forbidScalingX || lockScalingX || (target.set('scaleX', scaleX) && (scaled = scaled || changeX));
      }
      else if (by === 'y' && !target.get('lockUniScaling')) {
        forbidScalingY || lockScalingY || (target.set('scaleY', scaleY) && (scaled = scaled || changeY));
      }
      transform.newScaleX = scaleX;
      transform.newScaleY = scaleY;
      forbidScalingX || forbidScalingY || this._flipObject(transform, by);
      return scaled;
    },

    /**
     * @private
     * @return {Boolean} true if the scaling occurred
     */
    _scaleObjectEqually: function (localMouse, target, transform, _dim) {

      var dist = localMouse.y + localMouse.x,
        lastDist = _dim.y * transform.original.scaleY / target.scaleY +
          _dim.x * transform.original.scaleX / target.scaleX,
        scaled;

      // 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;
      scaled = transform.newScaleX !== target.scaleX || transform.newScaleY !== target.scaleY;
      target.set('scaleX', transform.newScaleX);
      target.set('scaleY', transform.newScaleY);
      return scaled;
    },

    /**
     * @private
     */
    _flipObject: function (transform, by) {
      if (transform.newScaleX < 0 && by !== 'y') {
        if (transform.originX === 'left') {
          transform.originX = 'right';
        }
        else if (transform.originX === 'right') {
          transform.originX = 'left';
        }
      }

      if (transform.newScaleY < 0 && by !== 'x') {
        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
     * @return {Boolean} true if the rotation occurred
     */
    _rotateObject: function (x, y) {

      var t = this._currentTransform;

      if (t.target.get('lockRotation')) {
        return false;
      }

      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),
        hasRoated = true;

      // normalize angle to positive value
      if (angle < 0) {
        angle = 360 + angle;
      }

      angle %= 360

      if (t.target.snapAngle > 0) {
        var snapAngle = t.target.snapAngle,
          snapThreshold = t.target.snapThreshold || snapAngle,
          rightAngleLocked = Math.ceil(angle / snapAngle) * snapAngle,
          leftAngleLocked = Math.floor(angle / snapAngle) * snapAngle;

        if (Math.abs(angle - leftAngleLocked) < snapThreshold) {
          angle = leftAngleLocked;
        }
        else if (Math.abs(angle - rightAngleLocked) < snapThreshold) {
          angle = rightAngleLocked;
        }

        if (t.target.angle === angle) {
          hasRoated = false
        }
      }

      t.target.angle = angle;
      return hasRoated;
    },

    /**
     * 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;
    },

    /**
     * @param {fabric.Object} target to reset transform
     * @private
     */
    _resetObjectTransform: function (target) {
      target.scaleX = 1;
      target.scaleY = 1;
      target.skewX = 0;
      target.skewY = 0;
      target.setAngle(0);
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx to draw the selection on
     */
    _drawSelection: function (ctx) {
      var groupSelector = this._groupSelector,
        left = groupSelector.left,
        top = groupSelector.top,
        aleft = abs(left),
        atop = abs(top);

      if (this.selectionColor) {
        ctx.fillStyle = this.selectionColor;

        ctx.fillRect(
          groupSelector.ex - ((left > 0) ? 0 : -left),
          groupSelector.ey - ((top > 0) ? 0 : -top),
          aleft,
          atop
        );
      }

      if (!this.selectionLineWidth || !this.selectionBorderColor) {
        return;
      }
      ctx.lineWidth = this.selectionLineWidth;
      ctx.strokeStyle = this.selectionBorderColor;

      // selection border
      if (this.selectionDashArray.length > 1 && !supportLineDash) {

        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 {
        fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray);
        ctx.strokeRect(
          groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
          groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),
          aleft,
          atop
        );
      }
    },

    /**
     * Method that determines what object we are clicking on
     * @param {Event} e mouse event
     * @param {Boolean} skipGroup when true, activeGroup is skipped and only objects are traversed through
     */
    findTarget: function (e, skipGroup) {
      if (this.skipTargetFind) {
        return;
      }

      var ignoreZoom = true,
        pointer = this.getPointer(e, ignoreZoom),
        activeGroup = this.getActiveGroup(),
        activeObject = this.getActiveObject(),
        activeTarget;

      // first check current group (if one exists)
      // active group does not check sub targets like normal groups.
      // if active group just exits.
      if (activeGroup && !skipGroup && this._checkTarget(pointer, activeGroup)) {
        this._fireOverOutEvents(activeGroup, e);
        return activeGroup;
      }
      // if we hit the corner of an activeObject, let's return that.
      if (activeObject && activeObject._findTargetCorner(pointer)) {
        this._fireOverOutEvents(activeObject, e);
        return activeObject;
      }
      if (activeObject && this._checkTarget(pointer, activeObject)) {
        if (!this.preserveObjectStacking) {
          this._fireOverOutEvents(activeObject, e);
          return activeObject;
        }
        else {
          activeTarget = activeObject;
        }
      }

      this.targets = [];

      var target = this._searchPossibleTargets(this._objects, pointer);
      if (e[this.altSelectionKey] && target && activeTarget && target !== activeTarget) {
        target = activeTarget;
      }
      this._fireOverOutEvents(target, e);
      return target;
    },

    /**
     * @private
     */
    _fireOverOutEvents: function (target, e) {
      if (target) {
        if (this._hoveredTarget !== target) {
          if (this._hoveredTarget) {
            this.fire('mouse:out', { target: this._hoveredTarget, e: e });
            this._hoveredTarget.fire('mouseout');
          }
          this.fire('mouse:over', { target: target, e: e });
          target.fire('mouseover');
          this._hoveredTarget = target;
        }
      }
      else if (this._hoveredTarget) {
        this.fire('mouse:out', { target: this._hoveredTarget, e: e });
        this._hoveredTarget.fire('mouseout');
        this._hoveredTarget = null;
      }
    },

    /**
     * @private
     */
    _checkTarget: function (pointer, obj) {
      if (obj &&
        obj.visible &&
        obj.evented &&
        this.containsPoint(null, obj, pointer)) {
        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 (objects, pointer) {

      // Cache all targets where their bounding box contains point.
      var target, i = objects.length, normalizedPointer, subTarget;
      // Do not check for currently grouped objects, since we check the parent group itself.
      // untill we call this function specifically to search inside the activeGroup
      while (i--) {
        if (this._checkTarget(pointer, objects[i])) {
          target = objects[i];
          if (target.type === 'group' && target.subTargetCheck) {
            normalizedPointer = this._normalizePointer(target, pointer);
            subTarget = this._searchPossibleTargets(target._objects, normalizedPointer);
            subTarget && this.targets.push(subTarget);
          }
          break;
        }
      }
      return target;
    },

    /**
     * Returns pointer coordinates without the effect of the viewport
     * @param {Object} pointer with "x" and "y" number values
     * @return {Object} object with "x" and "y" number values
     */
    restorePointerVpt: function (pointer) {
      return fabric.util.transformPoint(
        pointer,
        fabric.util.invertTransform(this.viewportTransform)
      );
    },

    /**
     * Returns pointer coordinates relative to canvas.
     * @param {Event} e
     * @param {Boolean} ignoreZoom
     * @return {Object} object with "x" and "y" number values
     */
    getPointer: function (e, ignoreZoom, upperCanvasEl) {
      if (!upperCanvasEl) {
        upperCanvasEl = this.upperCanvasEl;
      }
      var pointer = getPointer(e),
        bounds = upperCanvasEl.getBoundingClientRect(),
        boundsWidth = bounds.width || 0,
        boundsHeight = bounds.height || 0,
        cssScale;

      if (!boundsWidth || !boundsHeight) {
        if ('top' in bounds && 'bottom' in bounds) {
          boundsHeight = Math.abs(bounds.top - bounds.bottom);
        }
        if ('right' in bounds && 'left' in bounds) {
          boundsWidth = Math.abs(bounds.right - bounds.left);
        }
      }

      this.calcOffset();

      pointer.x = pointer.x - this._offset.left;
      pointer.y = pointer.y - this._offset.top;
      if (!ignoreZoom) {
        pointer = this.restorePointerVpt(pointer);
      }

      if (boundsWidth === 0 || boundsHeight === 0) {
        // If bounds are not available (i.e. not visible), do not apply scale.
        cssScale = { width: 1, height: 1 };
      }
      else {
        cssScale = {
          width: upperCanvasEl.width / boundsWidth,
          height: upperCanvasEl.height / boundsHeight
        };
      }

      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
     * @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', { target: obj });
        obj.fire('deselected');
      }
      this.callSuper('_onObjectRemoved', obj);
    },

    /**
     * @private
     */
    _discardActiveObject: function () {
      if (this._activeObject) {
        this._activeObject.set('active', false);
      }
      this._activeObject = null;
    },

    /**
     * Discards currently active object and fire events
     * @param {event} e
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    discardActiveObject: function (e) {
      var activeObject = this._activeObject;
      this.fire('before:selection:cleared', { target: activeObject, e: e });
      this._discardActiveObject();
      this.fire('selection:cleared', { e: e });
      activeObject && activeObject.fire('deselected', { 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 specified one
     * @param {fabric.Group} group Group to set as a current one
     * @param {Event} e Event object
     * @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 and fire events
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    discardActiveGroup: function (e) {
      var g = this.getActiveGroup();
      this.fire('before:selection:cleared', { e: e, target: g });
      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
     * @chainable
     */
    deactivateAll: function () {
      var allObjects = this.getObjects(),
        i = 0,
        len = allObjects.length,
        obj;
      for (; i < len; i++) {
        obj = allObjects[i];
        obj && obj.set('active', false);
      }
      this._discardActiveGroup();
      this._discardActiveObject();
      return this;
    },

    /**
     * Deactivates all objects and dispatches appropriate events
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    deactivateAllWithDispatch: function (e) {
      var activeGroup = this.getActiveGroup(),
        activeObject = this.getActiveObject();
      if (activeObject || activeGroup) {
        this.fire('before:selection:cleared', { target: activeObject || activeGroup, e: e });
      }
      this.deactivateAll();
      if (activeObject || activeGroup) {
        this.fire('selection:cleared', { e: e, target: activeObject });
        activeObject && activeObject.fire('deselected');
      }
      return this;
    },

    /**
     * Clears a canvas element and removes all event listeners
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    dispose: function () {
      this.callSuper('dispose');
      var wrapper = this.wrapperEl;
      this.removeListeners();
      wrapper.removeChild(this.upperCanvasEl);
      wrapper.removeChild(this.lowerCanvasEl);
      delete this.upperCanvasEl;
      if (wrapper.parentNode) {
        wrapper.parentNode.replaceChild(this.lowerCanvasEl, this.wrapperEl);
      }
      delete this.wrapperEl;
      return this;
    },

    /**
     * Clears all contexts (background, main, top) of an instance
     * @return {fabric.Canvas} thisArg
     * @chainable
     */
    clear: function () {
      this.discardActiveGroup();
      this.discardActiveObject();
      this.clearContext(this.contextTop);
      return this.callSuper('clear');
    },

    /**
     * Draws objects' controls (borders/controls)
     * @param {CanvasRenderingContext2D} ctx Context to render controls on
     */
    drawControls: function (ctx) {
      var activeGroup = this.getActiveGroup();

      if (activeGroup) {
        activeGroup._renderControls(ctx);
      }
      else {
        this._drawObjectsControls(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);
      }
    },

    /**
     * @private
     */
    _toObject: function (instance, methodName, propertiesToInclude) {
      //If the object is part of the current selection group, it should
      //be transformed appropriately
      //i.e. it should be serialised as it would appear if the selection group
      //were to be destroyed.
      var originalProperties = this._realizeGroupTransformOnObject(instance),
        object = this.callSuper('_toObject', instance, methodName, propertiesToInclude);
      //Undo the damage we did by changing all of its properties
      this._unwindGroupTransformOnObject(instance, originalProperties);
      return object;
    },

    /**
     * Realises an object's group transformation on it
     * @private
     * @param {fabric.Object} [instance] the object to transform (gets mutated)
     * @returns the original values of instance which were changed
     */
    _realizeGroupTransformOnObject: function (instance) {
      var layoutProps = ['angle', 'flipX', 'flipY', 'height', 'left', 'scaleX', 'scaleY', 'top', 'width'];
      if (instance.group && instance.group === this.getActiveGroup()) {
        //Copy all the positionally relevant properties across now
        var originalValues = {};
        layoutProps.forEach(function (prop) {
          originalValues[prop] = instance[prop];
        });
        this.getActiveGroup().realizeTransform(instance);
        return originalValues;
      }
      else {
        return null;
      }
    },

    /**
     * Restores the changed properties of instance
     * @private
     * @param {fabric.Object} [instance] the object to un-transform (gets mutated)
     * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject
     */
    _unwindGroupTransformOnObject: function (instance, originalValues) {
      if (originalValues) {
        instance.set(originalValues);
      }
    },

    /**
     * @private
     */
    _setSVGObject: function (markup, instance, reviver) {
      var originalProperties;
      //If the object is in a selection group, simulate what would happen to that
      //object when the group is deselected
      originalProperties = this._realizeGroupTransformOnObject(instance);
      this.callSuper('_setSVGObject', markup, instance, reviver);
      this._unwindGroupTransformOnObject(instance, originalProperties);
    },
  });

  // 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 () { };
  }

  /**
   * @ignore
   * @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, 'mouseout', this._onMouseOut);
      addListener(this.upperCanvasEl, 'mouseenter', this._onMouseEnter);
      addListener(this.upperCanvasEl, 'wheel', this._onMouseWheel);
      addListener(this.upperCanvasEl, 'contextmenu', this._onContextMenu);

      // touch events
      addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
      addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);

      if (typeof eventjs !== 'undefined' && 'add' in eventjs) {
        eventjs.add(this.upperCanvasEl, 'gesture', this._onGesture);
        eventjs.add(this.upperCanvasEl, 'drag', this._onDrag);
        eventjs.add(this.upperCanvasEl, 'orientation', this._onOrientationChange);
        eventjs.add(this.upperCanvasEl, 'shake', this._onShake);
        eventjs.add(this.upperCanvasEl, 'longpress', this._onLongPress);
      }
    },

    /**
     * @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._onLongPress = this._onLongPress.bind(this);
      this._onOrientationChange = this._onOrientationChange.bind(this);
      this._onMouseWheel = this._onMouseWheel.bind(this);
      this._onMouseOut = this._onMouseOut.bind(this);
      this._onMouseEnter = this._onMouseEnter.bind(this);
      this._onContextMenu = this._onContextMenu.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, 'mouseout', this._onMouseOut);
      removeListener(this.upperCanvasEl, 'mouseenter', this._onMouseEnter);
      removeListener(this.upperCanvasEl, 'wheel', this._onMouseWheel);
      removeListener(this.upperCanvasEl, 'contextmenu', this._onContextMenu);

      removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
      removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);

      if (typeof eventjs !== 'undefined' && 'remove' in eventjs) {
        eventjs.remove(this.upperCanvasEl, 'gesture', this._onGesture);
        eventjs.remove(this.upperCanvasEl, 'drag', this._onDrag);
        eventjs.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange);
        eventjs.remove(this.upperCanvasEl, 'shake', this._onShake);
        eventjs.remove(this.upperCanvasEl, 'longpress', this._onLongPress);
      }
    },

    /**
     * @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 wheel event
     */
    _onMouseWheel: function (e) {
      this.__onMouseWheel(e);
    },

    /**
     * @private
     * @param {Event} e Event object fired on mousedown
     */
    _onMouseOut: function (e) {
      var target = this._hoveredTarget;
      this.fire('mouse:out', { target: target, e: e });
      this._hoveredTarget = null;
      target && target.fire('mouseout', { e: e });
    },

    /**
     * @private
     * @param {Event} e Event object fired on mouseenter
     */
    _onMouseEnter: function (e) {
      if (!this.findTarget(e)) {
        this.fire('mouse:over', { target: null, e: e });
        this._hoveredTarget = null;
      }
    },

    /**
     * @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 Event.js shake
     * @param {Event} [self] Inner Event object
     */
    _onLongPress: function (e, self) {
      this.__onLongPress && this.__onLongPress(e, self);
    },

    /**
     * @private
     * @param {Event} e Event object fired on mousedown
     */
    _onContextMenu: function (e) {
      if (this.stopContextMenu) {
        e.stopPropagation()
        e.preventDefault();
      }
      return false;
    },

    /**
     * @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, searchTarget = true, transform = this._currentTransform,
        groupSelector = this._groupSelector,
        isClick = (!groupSelector || (groupSelector.left === 0 && groupSelector.top === 0));

      if (this.isDrawingMode && this._isCurrentlyDrawing) {
        this._onMouseUpInDrawingMode(e);
        return;
      }

      if (transform) {
        this._finalizeCurrentTransform();
        searchTarget = !transform.actionPerformed;
      }

      target = searchTarget ? this.findTarget(e, true) : transform.target;

      var shouldRender = this._shouldRender(target, this.getPointer(e));

      if (target || !isClick) {
        this._maybeGroupObjects(e);
      }
      else {
        // those are done by default on mouse up
        // by _maybeGroupObjects, we are skipping it in case of no target find
        this._groupSelector = null;
        this._currentTransform = null;
      }

      if (target) {
        target.isMoving = false;
      }

      this._handleCursorAndEvent(e, target, 'up');
      target && (target.__corner = 0);
      shouldRender && this.renderAll();
    },

    /**
     * set cursor for mouse up and handle mouseUp event
     * @param {Event} e event from mouse
     * @param {fabric.Object} target receiving event
     * @param {String} eventType event to fire (up, down or move)
     */
    _handleCursorAndEvent: function (e, target, eventType) {
      this._setCursorFromEvent(e, target);
      this._handleEvent(e, eventType, target ? target : null);
    },

    /**
     * Handle event firing for target and subtargets
     * @param {Event} e event from mouse
     * @param {String} eventType event to fire (up, down or move)
     * @param {fabric.Object} targetObj receiving event
     */
    _handleEvent: function (e, eventType, targetObj) {
      var target = typeof targetObj === undefined ? this.findTarget(e) : targetObj,
        targets = this.targets || [],
        options = { e: e, target: target, subTargets: targets };

      this.fire('mouse:' + eventType, options);
      target && target.fire('mouse' + eventType, options);
      for (var i = 0; i < targets.length; i++) {
        targets[i].fire('mouse' + eventType, options);
      }
    },

    /**
     * @private
     */
    _finalizeCurrentTransform: function () {

      var transform = this._currentTransform,
        target = transform.target;

      if (target._scaling) {
        target._scaling = false;
      }

      target.setCoords();
      this._restoreOriginXY(target);

      if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) {
        this.fire('object:modified', { target: target });
        target.fire('modified');
      }
    },

    /**
     * @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 pointer = this.getPointer(e);
      this.freeDrawingBrush.onMouseDown(pointer);
      this._handleEvent(e, 'down');
    },

    /**
     * @private
     * @param {Event} e Event object fired on mousemove
     */
    _onMouseMoveInDrawingMode: function (e) {
      if (this._isCurrentlyDrawing) {
        var pointer = this.getPointer(e);
        this.freeDrawingBrush.onMouseMove(pointer);
      }
      this.setCursor(this.freeDrawingCursor);
      this._handleEvent(e, 'move');
    },

    /**
     * @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._handleEvent(e, 'up');
    },

    /**
     * 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) {

      var target = this.findTarget(e),
        pointer = this.getPointer(e, true);

      // if right click just fire events
      var isRightClick = 'which' in e ? e.which === 3 : e.button === 2;
      if (isRightClick) {
        if (this.fireRightClick) {
          this._handleEvent(e, 'down', target ? target : null);
        }
        return;
      }

      if (this.isDrawingMode) {
        this._onMouseDownInDrawingMode(e);
        return;
      }

      // ignore if some object is being transformed at this moment
      if (this._currentTransform) {
        return;
      }

      // 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) {
        if (target.selectable && (target.__corner || !shouldGroup)) {
          this._beforeTransform(e, target);
          this._setupCurrentTransform(e, target);
        }

        if (target !== this.getActiveGroup() && target !== this.getActiveObject()) {
          this.deactivateAll();
          target.selectable && this.setActiveObject(target, e);
        }
      }
      this._handleEvent(e, 'down', target ? target : null);
      // we must renderAll so that we update the visuals
      shouldRender && this.renderAll();
    },

    /**
     * @private
     */
    _beforeTransform: function (e, target) {
      this.stateful && target.saveState();

      // determine if it's a drag or rotate case
      if (target._findTargetCorner(this.getPointer(e))) {
        this.onBeforeScaleRotate(target);
      }

    },

    /**
     * @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;
      }
      if (typeof e.touches !== 'undefined' && e.touches.length > 1) {
        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);
        this._setCursorFromEvent(e, target);
      }
      else {
        this._transformObject(e);
      }
      this._handleEvent(e, 'move', target ? target : null);
    },

    /**
     * Method that defines actions when an Event Mouse Wheel
     * @param {Event} e Event object fired on mouseup
     */
    __onMouseWheel: function (e) {
      this.fire('mouse:wheel', {
        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);

      transform.actionPerformed && this.renderAll();
    },

    /**
     * @private
     */
    _performTransformAction: function (e, transform, pointer) {
      var x = pointer.x,
        y = pointer.y,
        target = transform.target,
        action = transform.action,
        actionPerformed = false;

      if (action === 'rotate') {
        (actionPerformed = this._rotateObject(x, y)) && this._fire('rotating', target, e);
      }
      else if (action === 'scale') {
        (actionPerformed = this._onScale(e, transform, x, y)) && this._fire('scaling', target, e);
      }
      else if (action === 'scaleX') {
        (actionPerformed = this._scaleObject(x, y, 'x')) && this._fire('scaling', target, e);
      }
      else if (action === 'scaleY') {
        (actionPerformed = this._scaleObject(x, y, 'y')) && this._fire('scaling', target, e);
      }
      else if (action === 'skewX') {
        (actionPerformed = this._skewObject(x, y, 'x')) && this._fire('skewing', target, e);
      }
      else if (action === 'skewY') {
        (actionPerformed = this._skewObject(x, y, 'y')) && this._fire('skewing', target, e);
      }
      else {
        actionPerformed = this._translateObject(x, y);
        if (actionPerformed) {
          this._fire('moving', target, e);
          this.setCursor(target.moveCursor || this.moveCursor);
        }
      }
      transform.actionPerformed = actionPerformed;
    },

    /**
     * @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(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();
          transform.reset = true;
        }
      }
    },

    /**
     * @private
     * @param {Event} e Event object
     * @param {Object} transform current tranform
     * @param {Number} x mouse position x from origin
     * @param {Number} y mouse poistion y from origin
     * @return {Boolean} true if the scaling occurred
     */
    _onScale: function (e, transform, x, y) {
      if ((e[this.uniScaleKey] || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) {
        transform.currentAction = 'scale';
        return this._scaleObject(x, y);
      }
      else {
        // Switch from a normal resize to proportional
        if (!transform.reset && transform.currentAction === 'scale') {
          this._resetCurrentTransform();
        }

        transform.currentAction = 'scaleEqually';
        return 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) {
        this.setCursor(this.defaultCursor);
        return false;
      }

      var hoverCursor = target.hoverCursor || this.hoverCursor;
      if (!target.selectable) {
        //let's skip _findTargetCorner if object is not selectable
        this.setCursor(hoverCursor);
      }
      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(hoverCursor);
        }
        else {
          this._setCornerCursor(corner, target, e);
        }
      }
      //actually unclear why it should return something
      //is never evaluated
      return true;
    },

    /**
     * @private
     */
    _setCornerCursor: function (corner, target, e) {
      if (corner in cursorOffset) {
        this.setCursor(this._getRotatedCornerCursor(corner, target, e));
      }
      else if (corner === 'mtr' && target.hasRotatingPoint) {
        this.setCursor(this.rotationCursor);
      }
      else {
        this.setCursor(this.defaultCursor);
        return false;
      }
    },

    /**
     * @private
     */
    _getRotatedCornerCursor: function (corner, target, e) {
      var n = Math.round((target.getAngle() % 360) / 45);

      if (n < 0) {
        n += 8; // full circle ahead
      }
      n += cursorOffset[corner];
      if (e[this.altActionKey] && cursorOffset[corner] % 2 === 0) {
        //if we are holding shift and we are on a mx corner...
        n += 2;
      }
      // 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[this.selectionKey] && target && target.selectable &&
        (this.getActiveGroup() || (activeObject && activeObject !== target))
        && this.selection;
    },

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

      if (target === activeGroup) {
        // if it's a group, find target again, using activeGroup objects
        target = this.findTarget(e, true);
        // if even object is not found, bail out
        if (!target) {
          return;
        }
      }
      if (activeGroup) {
        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);
        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.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];
      this._activeObject.isEditing && this._activeObject.exitEditing();
      return new fabric.Group(groupObjects, {
        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(), {
          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;
    }
  });

})();


(function () {

  var supportQuality = fabric.StaticCanvas.supports('toDataURLWithQuality');

  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 || 0,
          top: options.top || 0,
          width: options.width || 0,
          height: options.height || 0,
        };
      return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier);
    },

    /**
     * @private
     */
    __toDataURLWithMultiplier: function (format, quality, cropping, multiplier) {

      var origWidth = this.getWidth(),
        origHeight = this.getHeight(),
        scaledWidth = (cropping.width || this.getWidth()) * multiplier,
        scaledHeight = (cropping.height || this.getHeight()) * multiplier,
        zoom = this.getZoom(),
        newZoom = zoom * multiplier,
        vp = this.viewportTransform,
        translateX = (vp[4] - cropping.left) * multiplier,
        translateY = (vp[5] - cropping.top) * multiplier,
        newVp = [newZoom, 0, 0, newZoom, translateX, translateY],
        originalInteractive = this.interactive;

      this.viewportTransform = newVp;
      // setting interactive to false avoid exporting controls
      this.interactive && (this.interactive = false);
      if (origWidth !== scaledWidth || origHeight !== scaledHeight) {
        // this.setDimensions is going to renderAll also;
        this.setDimensions({ width: scaledWidth, height: scaledHeight });
      }
      else {
        this.renderAll();
      }
      var data = this.__toDataURL(format, quality, cropping);
      originalInteractive && (this.interactive = originalInteractive);
      this.viewportTransform = vp;
      //setDimensions with no option object is taking care of:
      //this.width, this.height, this.renderAll()
      this.setDimensions({ width: origWidth, height: origHeight });
      return data;
    },

    /**
     * @private
     */
    __toDataURL: function (format, quality) {

      var canvasEl = this.contextContainer.canvas;
      // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
      if (format === 'jpg') {
        format = 'jpeg';
      }

      var data = supportQuality
        ? canvasEl.toDataURL('image/' + format, quality)
        : canvasEl.toDataURL('image/' + format);

      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
      });
    },
  });

})();


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)
      : fabric.util.object.clone(json);

    this.clear();

    var _this = this;
    this._enlivenObjects(serialized.objects, function () {
      _this._setBgOverlay(serialized, function () {
        // remove parts i cannot set as options
        delete serialized.objects;
        delete serialized.backgroundImage;
        delete serialized.overlayImage;
        delete serialized.background;
        delete serialized.overlay;
        // this._initOptions does too many things to just
        // call it. Normally loading an Object from JSON
        // create the Object instance. Here the Canvas is
        // already an instance and we are just loading things over it
        for (var prop in serialized) {
          _this[prop] = serialized[prop];
        }
        callback && 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) {
        // we splice the array just in case some custom classes restored from JSON
        // will add more object to canvas at canvas init.
        _this.insertAt(obj, index);
      });

      _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'),
    objectCaching = !fabric.isLikelyNode;

  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 deselected
   * @fires modified
   * @fires rotating
   * @fires scaling
   * @fires moving
   * @fires skewing
   *
   * @fires mousedown
   * @fires mouseup
   * @fires mouseover
   * @fires mouseout
   */
  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}
     */

    /**
     * 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.).
     * Note that this property is meant to be read-only and not meant to be modified.
     * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly.
     * @type String
     * @default
     */
    type: 'object',

    /**
     * Horizontal origin of transformation of an object (one of "left", "right", "center")
     * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups
     * @type String
     * @default
     */
    originX: 'left',

    /**
     * Vertical origin of transformation of an object (one of "top", "bottom", "center")
     * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups
     * @type String
     * @default
     */
    originY: 'top',

    /**
     * Top position of an object. Note that by default it's relative to object top. You can change this by setting originY={top/center/bottom}
     * @type Number
     * @default
     */
    top: 0,

    /**
     * Left position of an object. Note that by default it's relative to object left. You can change this by setting originX={left/center/right}
     * @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,

    /**
     * Angle of skew on x axes of an object (in degrees)
     * @type Number
     * @default
     */
    skewX: 0,

    /**
     * Angle of skew on y axes of an object (in degrees)
     * @type Number
     * @default
     */
    skewY: 0,

    /**
     * Size of object's controlling corners (in pixels)
     * @type Number
     * @default
     */
    cornerSize: 13,

    /**
     * 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,

    /**
     * Default cursor value used when moving this object on canvas
     * @type String
     * @default
     */
    moveCursor: 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)',

    /**
     * Array specifying dash pattern of an object's borders (hasBorder must be true)
     * @since 1.6.2
     * @type Array
     */
    borderDashArray: null,

    /**
     * Color of controlling corners of an object (when it's active)
     * @type String
     * @default
     */
    cornerColor: 'rgba(102,153,255,0.5)',

    /**
     * Color of controlling corners of an object (when it's active and transparentCorners false)
     * @since 1.6.2
     * @type String
     * @default
     */
    cornerStrokeColor: null,

    /**
     * Specify style of control, 'rect' or 'circle'
     * @since 1.6.2
     * @type String
     */
    cornerStyle: 'rect',

    /**
     * Array specifying dash pattern of an object's control (hasBorder must be true)
     * @since 1.6.2
     * @type Array
     */
    cornerDashArray: null,

    /**
     * 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
     * accepted values are nonzero, evenodd
     * <b>Backwards incompatibility note:</b> This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead)
     * @type String
     * @default
     */
    fillRule: 'nonzero',

    /**
     * Composite rule used for canvas globalCompositeOperation
     * @type String
     * @default
     */
    globalCompositeOperation: 'source-over',

    /**
     * Background color of an object. Only works with text objects at the moment.
     * @type String
     * @default
     */
    backgroundColor: '',

    /**
     * Selection Background color of an object. colored layer behind the object when it is active.
     * does not mix good with globalCompositeOperation methods.
     * @type String
     * @default
     */
    selectionBackgroundColor: '',

    /**
     * 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,

    /**
     * When `true`, object horizontal skewing is locked
     * @type Boolean
     * @default
     */
    lockSkewingX: false,

    /**
     * When `true`, object vertical skewing is locked
     * @type Boolean
     * @default
     */
    lockSkewingY: false,

    /**
     * When `true`, object cannot be flipped by scaling into negative values
     * @type Boolean
     * @default
     */
    lockScalingFlip: false,

    /**
     * When `true`, object is not exported in SVG or OBJECT/JSON
     * since 1.6.3
     * @type Boolean
     * @default
     */
    excludeFromExport: false,

    /**
     * When `true`, object is cached on an additional canvas.
     * default to true
     * since 1.7.0
     * @type Boolean
     * @default
     */
    objectCaching: objectCaching,

    /**
     * When `true`, object properties are checked for cache invalidation. In some particular
     * situation you may want this to be disabled ( spray brush, very big pathgroups, groups)
     * or if your application does not allow you to modify properties for groups child you want
     * to disable it for groups.
     * default to false
     * since 1.7.0
     * @type Boolean
     * @default false
     */
    statefullCache: false,

    /**
     * When `true`, cache does not get updated during scaling. The picture will get blocky if scaled
     * too much and will be redrawn with correct details at the end of scaling.
     * this setting is performance and application dependant.
     * default to false
     * since 1.7.0
     * @type Boolean
     * @default true
     */
    noScaleCache: true,

    /**
     * When set to `true`, object's cache will be rerendered next render call.
     * @type Boolean
     * @default false
     */
    dirty: 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 globalCompositeOperation shadow clipTo visible backgroundColor ' +
      'skewX skewY'
    ).split(' '),

    /**
     * List of properties to consider when checking if cache needs refresh
     * @type Array
     */
    cacheProperties: (
      'fill stroke strokeWidth strokeDashArray width height stroke strokeWidth strokeDashArray' +
      ' strokeLineCap strokeLineJoin strokeMiterLimit fillRule backgroundColor'
    ).split(' '),

    /**
     * Constructor
     * @param {Object} [options] Options object
     */
    initialize: function (options) {
      options = options || {};
      if (options) {
        this.setOptions(options);
      }
      if (this.objectCaching) {
        this._createCacheCanvas();
        this.setupState({ propertySet: 'cacheProperties' });
      }
    },

    /**
     * Create a the canvas used to keep the cached copy of the object
     * @private
     */
    _createCacheCanvas: function () {
      this._cacheCanvas = fabric.document.createElement('canvas');
      this._cacheContext = this._cacheCanvas.getContext('2d');
      this._updateCacheCanvas();
    },

    /**
     * Update width and height of the canvas for cache
     * returns true or false if canvas needed resize.
     * @private
     * @return {Boolean} true if the canvas has been resized
     */
    _updateCacheCanvas: function () {
      if (this.noScaleCache && this.canvas && this.canvas._currentTransform) {
        var action = this.canvas._currentTransform.action;
        if (action.slice(0, 5) === 'scale') {
          return false;
        }
      }
      var zoom = this.getViewportTransform()[0],
        objectScale = this.getObjectScaling(),
        dim = this._getNonTransformedDimensions(),
        retina = this.canvas && this.canvas._isRetinaScaling() ? fabric.devicePixelRatio : 1,
        zoomX = objectScale.scaleX * zoom * retina,
        zoomY = objectScale.scaleY * zoom * retina,
        width = dim.x * zoomX,
        height = dim.y * zoomY;
      if (width !== this.cacheWidth || height !== this.cacheHeight) {
        this._cacheCanvas.width = width;
        this._cacheCanvas.height = height;
        this._cacheContext.translate(width / 2, height / 2);
        this._cacheContext.scale(zoomX, zoomY);
        this.cacheWidth = width;
        this.cacheHeight = height;
        this.zoomX = zoomX;
        this.zoomY = zoomY;
        return true
      }
      return false
    },

    /**
     * @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));
      }
      if (options.stroke && options.stroke.colorStops && !(options.stroke instanceof fabric.Gradient)) {
        this.set('stroke', new fabric.Gradient(options.stroke));
      }
    },

    /**
     * @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._transformDone && this.group === this.canvas._activeGroup) {
        this.group.transform(ctx);
      }
      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)
      );
      ctx.transform(1, 0, Math.tan(degreesToRadians(this.skewX)), 1, 0, 0);
      ctx.transform(1, Math.tan(degreesToRadians(this.skewY)), 0, 1, 0, 0);
    },

    /**
     * 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 ? this.strokeDashArray.concat() : 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,
          fillRule: this.fillRule,
          globalCompositeOperation: this.globalCompositeOperation,
          transformMatrix: this.transformMatrix ? this.transformMatrix.concat() : this.transformMatrix,
          skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS),
          skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS)
        };

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

      if (!this.includeDefaultValues) {
        object = this._removeDefaultValues(object);
      }

      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];
        }
        var isArray = Object.prototype.toString.call(object[prop]) === '[object Array]' &&
          Object.prototype.toString.call(prototype[prop]) === '[object Array]';

        // basically a check for [] === []
        if (isArray && object[prop].length === 0 && prototype[prop].length === 0) {
          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 {*} value of a property
     */
    get: function (property) {
      return this[property];
    },

    /**
     * Return the object scale factor counting also the group scaling
     * @return {Object} object with scaleX and scaleY properties
     */
    getObjectScaling: function () {
      var scaleX = this.scaleX, scaleY = this.scaleY;
      if (this.group) {
        var scaling = this.group.getObjectScaling();
        scaleX *= scaling.scaleX;
        scaleY *= scaling.scaleY;
      }
      return { scaleX: scaleX, scaleY: scaleY };
    },

    /**
     * @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 {*} 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 === 'shadow' && value && !(value instanceof fabric.Shadow)) {
        value = new fabric.Shadow(value);
      }

      this[key] = value;

      if (this.cacheProperties.indexOf(key) > -1) {
        this.dirty = true;
      }

      if (key === 'width' || key === 'height') {
        this.minScaleLimit = Math.min(0.1, 1 / Math.max(this.width, this.height));
      }

      return this;
    },

    /**
     * This callback function is called by the parent group of an object every
     * time a non-delegated property changes on the group. It is passed the key
     * and value as parameters. Not adding in this function's signature to avoid
     * Travis build error about unused variables.
     */
    setOnGroup: function () {
      // implemented by sub-classes, as needed.
    },

    /**
     * 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._setupCompositeOperation(ctx);
      this.drawSelectionBackground(ctx);
      if (!noTransform) {
        this.transform(ctx);
      }
      this._setOpacity(ctx);
      this._setShadow(ctx);
      if (this.transformMatrix) {
        ctx.transform.apply(ctx, this.transformMatrix);
      }
      this.clipTo && fabric.util.clipContext(this, ctx);
      if (this.objectCaching && !this.group) {
        if (this.isCacheDirty(noTransform)) {
          this.statefullCache && this.saveState({ propertySet: 'cacheProperties' });
          this.drawObject(this._cacheContext, noTransform);
          this.dirty = false;
        }
        this.drawCacheOnCanvas(ctx);
      }
      else {
        this.drawObject(ctx, noTransform);
        if (noTransform && this.objectCaching && this.statefullCache) {
          this.saveState({ propertySet: 'cacheProperties' });
        }
      }
      this.clipTo && ctx.restore();
      ctx.restore();
    },

    /**
     * Execute the drawing operation for an object on a specified context
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} [noTransform] When true, context is not transformed
     */
    drawObject: function (ctx, noTransform) {
      this._renderBackground(ctx);
      this._setStrokeStyles(ctx);
      this._setFillStyles(ctx);
      this._render(ctx, noTransform);
    },

    /**
     * Paint the cached copy of the object on the target context.
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    drawCacheOnCanvas: function (ctx) {
      ctx.scale(1 / this.zoomX, 1 / this.zoomY);
      ctx.drawImage(this._cacheCanvas, -this.cacheWidth / 2, -this.cacheHeight / 2);
    },

    /**
     * Check if cache is dirty
     * @param {Boolean} skipCanvas skip canvas checks because this object is painted
     * on parent canvas.
     */
    isCacheDirty: function (skipCanvas) {
      if (!skipCanvas && this._updateCacheCanvas()) {
        // in this case the context is already cleared.
        return true;
      }
      else {
        if (this.dirty || (this.statefullCache && this.hasStateChanged('cacheProperties'))) {
          if (!skipCanvas) {
            var dim = this._getNonTransformedDimensions();
            this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
          }
          return true;
        }
      }
      return false;
    },

    /**
     * Draws a background for the object big as its untrasformed dimensions
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderBackground: function (ctx) {
      if (!this.backgroundColor) {
        return;
      }
      var dim = this._getNonTransformedDimensions();
      ctx.fillStyle = this.backgroundColor;

      ctx.fillRect(
        -dim.x / 2,
        -dim.y / 2,
        dim.x,
        dim.y
      );
      // if there is background color no other shadows
      // should be casted
      this._removeShadow(ctx);
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _setOpacity: function (ctx) {
      ctx.globalAlpha *= this.opacity;
    },

    _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)
          : this.stroke;
      }
    },

    _setFillStyles: function (ctx) {
      if (this.fill) {
        ctx.fillStyle = this.fill.toLive
          ? this.fill.toLive(ctx, this)
          : this.fill;
      }
    },

    /**
     * @private
     * Sets line dash
     * @param {CanvasRenderingContext2D} ctx Context to set the dash line on
     * @param {Array} dashArray array representing dashes
     * @param {Function} alternative function to call if browaser does not support lineDash
     */
    _setLineDash: function (ctx, dashArray, alternative) {
      if (!dashArray) {
        return;
      }
      // Spec requires the concatenation of two copies the dash list when the number of elements is odd
      if (1 & dashArray.length) {
        dashArray.push.apply(dashArray, dashArray);
      }
      if (supportsLineDash) {
        ctx.setLineDash(dashArray);
      }
      else {
        alternative && alternative(ctx);
      }
    },

    /**
     * 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) {
      if (!this.active || noTransform
        || (this.group && this.group !== this.canvas.getActiveGroup())) {
        return;
      }

      var vpt = this.getViewportTransform(),
        matrix = this.calcTransformMatrix(),
        options;
      matrix = fabric.util.multiplyTransformMatrices(vpt, matrix);
      options = fabric.util.qrDecompose(matrix);

      ctx.save();
      ctx.translate(options.translateX, options.translateY);
      ctx.lineWidth = 1 * this.borderScaleFactor;
      if (!this.group) {
        ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
      }
      if (this.group && this.group === this.canvas.getActiveGroup()) {
        ctx.rotate(degreesToRadians(options.angle));
        this.drawBordersInGroup(ctx, options);
      }
      else {
        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;
      }

      var multX = (this.canvas && this.canvas.viewportTransform[0]) || 1,
        multY = (this.canvas && this.canvas.viewportTransform[3]) || 1,
        scaling = this.getObjectScaling();
      if (this.canvas && this.canvas._isRetinaScaling()) {
        multX *= fabric.devicePixelRatio;
        multY *= fabric.devicePixelRatio;
      }
      ctx.shadowColor = this.shadow.color;
      ctx.shadowBlur = this.shadow.blur * (multX + multY) * (scaling.scaleX + scaling.scaleY) / 4;
      ctx.shadowOffsetX = this.shadow.offsetX * multX * scaling.scaleX;
      ctx.shadowOffsetY = this.shadow.offsetY * multY * scaling.scaleY;
    },

    /**
     * @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;
      }

      ctx.save();
      if (this.fill.gradientTransform) {
        var g = this.fill.gradientTransform;
        ctx.transform.apply(ctx, g);
      }
      if (this.fill.toLive) {
        ctx.translate(
          -this.width / 2 + this.fill.offsetX || 0,
          -this.height / 2 + this.fill.offsetY || 0);
      }
      if (this.fillRule === 'evenodd') {
        ctx.fill('evenodd');
      }
      else {
        ctx.fill();
      }
      ctx.restore();
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderStroke: function (ctx) {
      if (!this.stroke || this.strokeWidth === 0) {
        return;
      }

      if (this.shadow && !this.shadow.affectStroke) {
        this._removeShadow(ctx);
      }

      ctx.save();

      this._setLineDash(ctx, this.strokeDashArray, this._renderDashedStroke);
      if (this.stroke.gradientTransform) {
        var g = this.stroke.gradientTransform;
        ctx.transform.apply(ctx, g);
      }
      if (this.stroke.toLive) {
        ctx.translate(
          -this.width / 2 + this.stroke.offsetX || 0,
          -this.height / 2 + this.stroke.offsetY || 0);
      }
      ctx.stroke();
      ctx.restore();
    },

    /**
     * Clones an instance, some objects are async, so using callback method will work for every object.
     * Using the direct return does not work for images and groups.
     * @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
     * @param {Object} [options] for clone as image, passed to toDataURL
     * @param {Boolean} [options.enableRetinaScaling] enable retina scaling for the cloned image
     * @return {fabric.Object} thisArg
     */
    cloneAsImage: function (callback, options) {
      var dataUrl = this.toDataURL(options);
      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
     * @param {Boolean} [options.enableRetina] Enable retina scaling for clone image. Introduce in 1.6.4
     * @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.StaticCanvas(el, { enableRetinaScaling: options.enableRetinaScaling });
      // 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(canvas.getWidth() / 2, canvas.getHeight() / 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'}
     * @param {Object} [options.gradientTransform] transforMatrix for gradient
     * @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;
      }

      options.gradientTransform && (gradient.gradientTransform = options.gradientTransform);

      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 (in degrees)
     * @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 && this.canvas.centerObjectH(this);
      return this;
    },

    /**
     * Centers object horizontally on current viewport of 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
     */
    viewportCenterH: function () {
      this.canvas && this.canvas.viewportCenterObjectH(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 && this.canvas.centerObjectV(this);
      return this;
    },

    /**
     * Centers object vertically on current viewport of 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
     */
    viewportCenterV: function () {
      this.canvas && this.canvas.viewportCenterObjectV(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 && this.canvas.centerObject(this);
      return this;
    },

    /**
     * Centers object on current viewport of 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
     */
    viewportCenter: function () {
      this.canvas && this.canvas.viewportCenterObject(this);
      return this;
    },

    /**
     * Removes object from canvas to which it was added last
     * @return {fabric.Object} thisArg
     * @chainable
     */
    remove: function () {
      this.canvas && 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 pClicked = new fabric.Point(pointer.x, pointer.y),
        objectLeftTop = this._getLeftTopCoords();
      if (this.angle) {
        pClicked = fabric.util.rotatePoint(
          pClicked, objectLeftTop, fabric.util.degreesToRadians(-this.angle));
      }
      return {
        x: pClicked.x - objectLeftTop.x,
        y: pClicked.y - objectLeftTop.y
      };
    },

    /**
     * Sets canvas globalCompositeOperation for specific object
     * custom composition operation for the particular object can be specifed using globalCompositeOperation property
     * @param {CanvasRenderingContext2D} ctx Rendering canvas context
     */
    _setupCompositeOperation: function (ctx) {
      if (this.globalCompositeOperation) {
        ctx.globalCompositeOperation = this.globalCompositeOperation;
      }
    }
  });

  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,
    originXOffset = {
      left: -0.5,
      center: 0,
      right: 0.5
    },
    originYOffset = {
      top: -0.5,
      center: 0,
      bottom: 0.5
    };

  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} fromOriginX Horizontal origin: 'left', 'center' or 'right'
     * @param {String} fromOriginY Vertical origin: 'top', 'center' or 'bottom'
     * @param {String} toOriginX Horizontal origin: 'left', 'center' or 'right'
     * @param {String} toOriginY Vertical origin: 'top', 'center' or 'bottom'
     * @return {fabric.Point}
     */
    translateToGivenOrigin: function (point, fromOriginX, fromOriginY, toOriginX, toOriginY) {
      var x = point.x,
        y = point.y,
        offsetX, offsetY, dim;

      if (typeof fromOriginX === 'string') {
        fromOriginX = originXOffset[fromOriginX];
      }
      else {
        fromOriginX -= 0.5;
      }

      if (typeof toOriginX === 'string') {
        toOriginX = originXOffset[toOriginX];
      }
      else {
        toOriginX -= 0.5;
      }

      offsetX = toOriginX - fromOriginX;

      if (typeof fromOriginY === 'string') {
        fromOriginY = originYOffset[fromOriginY];
      }
      else {
        fromOriginY -= 0.5;
      }

      if (typeof toOriginY === 'string') {
        toOriginY = originYOffset[toOriginY];
      }
      else {
        toOriginY -= 0.5;
      }

      offsetY = toOriginY - fromOriginY;

      if (offsetX || offsetY) {
        dim = this._getTransformedDimensions();
        x = point.x + offsetX * dim.x;
        y = point.y + offsetY * dim.y;
      }

      return new fabric.Point(x, y);
    },

    /**
     * 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 p = this.translateToGivenOrigin(point, originX, originY, 'center', 'center');
      if (this.angle) {
        return fabric.util.rotatePoint(p, point, degreesToRadians(this.angle));
      }
      return p;
    },

    /**
     * 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 p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY);
      if (this.angle) {
        return fabric.util.rotatePoint(p, center, degreesToRadians(this.angle));
      }
      return p;
    },

    /**
     * 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(),
        p, p2;

      if (typeof originX !== 'undefined' && typeof originY !== 'undefined') {
        p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY);
      }
      else {
        p = new fabric.Point(this.left, this.top);
      }

      p2 = new fabric.Point(point.x, point.y);
      if (this.angle) {
        p2 = fabric.util.rotatePoint(p2, center, -degreesToRadians(this.angle));
      }
      return p2.subtractEquals(p);
    },

    /**
     * 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),
        hypotFull = this.getWidth(),
        xFull = Math.cos(angle) * hypotFull,
        yFull = Math.sin(angle) * hypotFull,
        offsetFrom, offsetTo;

      //TODO: this function does not consider mixed situation like top, center.
      if (typeof this.originX === 'string') {
        offsetFrom = originXOffset[this.originX];
      }
      else {
        offsetFrom = this.originX - 0.5;
      }
      if (typeof to === 'string') {
        offsetTo = originXOffset[to];
      }
      else {
        offsetTo = to - 0.5;
      }
      this.left += xFull * (offsetTo - offsetFrom);
      this.top += yFull * (offsetTo - offsetFrom);
      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', 'top');
    }
  });

})();


(function () {

  function getCoords(oCoords) {
    return [
      new fabric.Point(oCoords.tl.x, oCoords.tl.y),
      new fabric.Point(oCoords.tr.x, oCoords.tr.y),
      new fabric.Point(oCoords.br.x, oCoords.br.y),
      new fabric.Point(oCoords.bl.x, oCoords.bl.y)
    ];
  }

  var degreesToRadians = fabric.util.degreesToRadians,
    multiplyMatrices = fabric.util.multiplyTransformMatrices;

  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 = getCoords(this.oCoords),
        intersection = fabric.Intersection.intersectPolygonRectangle(
          oCoords,
          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) {
      var intersection = fabric.Intersection.intersectPolygonPolygon(
        getCoords(this.oCoords),
        getCoords(other.oCoords)
      );

      return intersection.status === 'Intersection'
        || other.isContainedWithinObject(this)
        || this.isContainedWithinObject(other);
    },

    /**
     * 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 points = getCoords(this.oCoords),
        i = 0;
      for (; i < 4; i++) {
        if (!other.containsPoint(points[i])) {
          return false;
        }
      }
      return true;
    },

    /**
     * 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) {
      if (!this.oCoords) {
        this.setCoords();
      }
      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
     */
    // remove yi, not used but left code here just in case.
    _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)
     * the box is intented as aligned to axis of canvas.
     * @param {Boolean} ignoreVpt bounding box will not be affected by viewportTransform
     * @return {Object} Object with left, top, width, height properties
     */
    getBoundingRect: function (ignoreVpt) {
      var coords = this.calcCoords(ignoreVpt);
      return fabric.util.makeBoundingBoxFromPoints([
        coords.tl,
        coords.tr,
        coords.br,
        coords.bl
      ]);
    },

    /**
     * Returns width of an object bounding box counting transformations
     * @return {Number} width value
     */
    getWidth: function () {
      return this._getTransformedDimensions().x;
    },

    /**
     * Returns height of an object bounding box counting transformations
     * to be renamed in 2.0
     * @return {Number} height value
     */
    getHeight: function () {
      return this._getTransformedDimensions().y;
    },

    /**
     * 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;
      return this.setCoords();
    },

    /**
     * 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.getBoundingRect().width / 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.getBoundingRect().height / this.getHeight();
      return this.scale(value / this.height / boundingRectFactor);
    },

    /**
     * Calculate and returns the .coords of an object.
     * @return {Object} Object with tl, tr, br, bl ....
     * @chainable
     */
    calcCoords: function (ignoreVpt) {
      var theta = degreesToRadians(this.angle),
        vpt = this.getViewportTransform(),
        dim = ignoreVpt ? this._getTransformedDimensions() : this._calculateCurrentDimensions(),
        currentWidth = dim.x, currentHeight = dim.y,
        sinTh = Math.sin(theta),
        cosTh = Math.cos(theta),
        _angle = currentWidth > 0 ? Math.atan(currentHeight / currentWidth) : 0,
        _hypotenuse = (currentWidth / Math.cos(_angle)) / 2,
        offsetX = Math.cos(_angle + theta) * _hypotenuse,
        offsetY = Math.sin(_angle + theta) * _hypotenuse,
        center = this.getCenterPoint(),
        // offset added for rotate and scale actions
        coords = ignoreVpt ? center : fabric.util.transformPoint(center, vpt),
        tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY),
        tr = new fabric.Point(tl.x + (currentWidth * cosTh), tl.y + (currentWidth * sinTh)),
        bl = new fabric.Point(tl.x - (currentHeight * sinTh), tl.y + (currentHeight * cosTh)),
        br = new fabric.Point(coords.x + offsetX, coords.y + offsetY),
        ml = new fabric.Point((tl.x + bl.x) / 2, (tl.y + bl.y) / 2),
        mt = new fabric.Point((tr.x + tl.x) / 2, (tr.y + tl.y) / 2),
        mr = new fabric.Point((br.x + tr.x) / 2, (br.y + tr.y) / 2),
        mb = new fabric.Point((br.x + bl.x) / 2, (br.y + bl.y) / 2),
        mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset);
      // 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);
         canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3);
       }, 50); */

      return {
        // corners
        tl: tl, tr: tr, br: br, bl: bl,
        // middle
        ml: ml, mt: mt, mr: mr, mb: mb,
        // rotating point
        mtr: mtr
      };
    },

    /**
     * Sets corner position coordinates based on current angle, width and height
     * See https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords
     * @return {fabric.Object} thisArg
     * @chainable
     */
    setCoords: function (ignoreZoom) {
      this.oCoords = this.calcCoords(ignoreZoom);

      // set coordinates of the draggable boxes in the corners used to scale/rotate the image
      ignoreZoom || this._setCornerCoords && this._setCornerCoords();

      return this;
    },

    /*
     * calculate rotation matrix of an object
     * @return {Array} rotation matrix for the object
     */
    _calcRotateMatrix: function () {
      if (this.angle) {
        var theta = degreesToRadians(this.angle), cos = Math.cos(theta), sin = Math.sin(theta);
        return [cos, sin, -sin, cos, 0, 0];
      }
      return [1, 0, 0, 1, 0, 0];
    },

    /*
     * calculate trasform Matrix that represent current transformation from
     * object properties.
     * @return {Array} matrix Transform Matrix for the object
     */
    calcTransformMatrix: function () {
      var center = this.getCenterPoint(),
        translateMatrix = [1, 0, 0, 1, center.x, center.y],
        rotateMatrix = this._calcRotateMatrix(),
        dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true),
        matrix = this.group ? this.group.calcTransformMatrix() : [1, 0, 0, 1, 0, 0];
      matrix = multiplyMatrices(matrix, translateMatrix);
      matrix = multiplyMatrices(matrix, rotateMatrix);
      matrix = multiplyMatrices(matrix, dimensionMatrix);
      return matrix;
    },

    _calcDimensionsTransformMatrix: function (skewX, skewY, flipping) {
      var skewMatrixX = [1, 0, Math.tan(degreesToRadians(skewX)), 1],
        skewMatrixY = [1, Math.tan(degreesToRadians(skewY)), 0, 1],
        scaleX = this.scaleX * (flipping && this.flipX ? -1 : 1),
        scaleY = this.scaleY * (flipping && this.flipY ? -1 : 1),
        scaleMatrix = [scaleX, 0, 0, scaleY],
        m = multiplyMatrices(scaleMatrix, skewMatrixX, true);
      return multiplyMatrices(m, skewMatrixY, true);
    }
  });
})();


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_ */
(function () {

  function getSvgColorString(prop, value) {
    if (!value) {
      return prop + ': none; ';
    }
    else if (value.toLive) {
      return prop + ': url(#SVGID_' + value.id + '); ';
    }
    else {
      var color = new fabric.Color(value),
        str = prop + ': ' + color.toRgb() + '; ',
        opacity = color.getAlpha();
      if (opacity !== 1) {
        //change the color in rgb + opacity
        str += prop + '-opacity: ' + opacity.toString() + '; ';
      }
      return str;
    }
  }

  fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
    /**
     * Returns styles-string for svg-export
     * @param {Boolean} skipShadow a boolean to skip shadow filter output
     * @return {String}
     */
    getSvgStyles: function (skipShadow) {

      var fillRule = this.fillRule,
        strokeWidth = this.strokeWidth ? this.strokeWidth : '0',
        strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none',
        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 = skipShadow ? '' : this.getSvgFilter(),
        fill = getSvgColorString('fill', this.fill),
        stroke = getSvgColorString('stroke', this.stroke);

      return [
        stroke,
        'stroke-width: ', strokeWidth, '; ',
        'stroke-dasharray: ', strokeDashArray, '; ',
        'stroke-linecap: ', strokeLineCap, '; ',
        'stroke-linejoin: ', strokeLineJoin, '; ',
        'stroke-miterlimit: ', strokeMiterLimit, '; ',
        fill,
        'fill-rule: ', fillRule, '; ',
        'opacity: ', opacity, ';',
        filter,
        visibility
      ].join('');
    },

    /**
     * Returns filter for svg shadow
     * @return {String}
     */
    getSvgFilter: function () {
      return this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : '';
    },

    /**
     * Returns id attribute for svg output
     * @return {String}
     */
    getSvgId: function () {
      return this.id ? 'id="' + this.id + '" ' : '';
    },

    /**
     * Returns transform-string for svg-export
     * @return {String}
     */
    getSvgTransform: function () {
      if (this.group && this.group.type === 'path-group') {
        return '';
      }
      var toFixed = fabric.util.toFixed,
        angle = this.getAngle(),
        skewX = (this.getSkewX() % 360),
        skewY = (this.getSkewY() % 360),
        center = this.getCenterPoint(),

        NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,

        translatePart = this.type === 'path-group' ? '' : '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)
          ? '' :
          (' scale(' +
            toFixed(this.scaleX, NUM_FRACTION_DIGITS) +
            ' ' +
            toFixed(this.scaleY, NUM_FRACTION_DIGITS) +
            ')'),

        skewXPart = skewX !== 0 ? ' skewX(' + toFixed(skewX, NUM_FRACTION_DIGITS) + ')' : '',

        skewYPart = skewY !== 0 ? ' skewY(' + toFixed(skewY, NUM_FRACTION_DIGITS) + ')' : '',

        addTranslateX = this.type === 'path-group' ? this.width : 0,

        flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '',

        addTranslateY = this.type === 'path-group' ? this.height : 0,

        flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : '';

      return [
        translatePart, anglePart, scalePart, flipXPart, flipYPart, skewXPart, skewYPart
      ].join('');
    },

    /**
     * Returns transform-string for svg-export from the transform matrix of single elements
     * @return {String}
     */
    getSvgTransformMatrix: function () {
      return this.transformMatrix ? ' matrix(' + this.transformMatrix.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_ */


(function () {

  var extend = fabric.util.object.extend,
    originalSet = 'stateProperties';

  /*
    Depends on `stateProperties`
  */
  function saveProps(origin, destination, props) {
    var tmpObj = {}, deep = true;
    props.forEach(function (prop) {
      tmpObj[prop] = origin[prop];
    });
    extend(origin[destination], tmpObj, deep);
  }

  function _isEqual(origValue, currentValue, firstPass) {
    if (!fabric.isLikelyNode && origValue instanceof Element) {
      // avoid checking deep html elements
      return origValue === currentValue;
    }
    else if (origValue instanceof Array) {
      if (origValue.length !== currentValue.length) {
        return false
      }
      for (var i = 0, len = origValue.length; i < len; i++) {
        if (origValue[i] !== currentValue[i]) {
          return false;
        }
      }
      return true
    }
    else if (origValue && typeof origValue === 'object') {
      if (!firstPass && Object.keys(origValue).length !== Object.keys(currentValue).length) {
        return false;
      }
      for (var key in origValue) {
        if (!_isEqual(origValue[key], currentValue[key])) {
          return false;
        }
      }
      return true;
    }
    else {
      return origValue === currentValue;
    }
  }


  fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {

    /**
     * Returns true if object state (one of its state properties) was changed
     * @param {String} [propertySet] optional name for the set of property we want to save
     * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
     */
    hasStateChanged: function (propertySet) {
      propertySet = propertySet || originalSet;
      propertySet = '_' + propertySet;
      return !_isEqual(this[propertySet], this, true);
    },

    /**
     * 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) {
      var propertySet = options && options.propertySet || originalSet,
        destination = '_' + propertySet;
      saveProps(this, destination, this[propertySet]);
      if (options && options.stateProperties) {
        saveProps(this, destination, options.stateProperties);
      }
      return this;
    },

    /**
     * Setups state of an object
     * @param {Object} [options] Object with additional `stateProperties` array to include when saving state
     * @return {fabric.Object} thisArg
     */
    setupState: function (options) {
      options = options || {};
      var propertySet = options.propertySet || originalSet;
      options.propertySet = propertySet;
      this['_' + propertySet] = {};
      this.saveState(options);
      return this;
    }
  });
})();


(function () {

  var degreesToRadians = fabric.util.degreesToRadians,
    /* eslint-disable camelcase */
    isVML = function () { return typeof G_vmlCanvasManager !== 'undefined'; };
  /* eslint-enable camelcase */
  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;
      this.__corner = 0;
      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,
        newTheta = degreesToRadians(45 - this.angle),
        /* Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, */
        /* 0.707106 stands for sqrt(2)/2 */
        cornerHypotenuse = this.cornerSize * 0.707106,
        cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
        sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
        x, y;

      for (var point in coords) {
        x = coords[point].x;
        y = coords[point].y;
        coords[point].corner = {
          tl: {
            x: x - sinHalfOffset,
            y: y - cosHalfOffset
          },
          tr: {
            x: x + cosHalfOffset,
            y: y - sinHalfOffset
          },
          bl: {
            x: x - cosHalfOffset,
            y: y + sinHalfOffset
          },
          br: {
            x: x + sinHalfOffset,
            y: y + cosHalfOffset
          }
        };
      }
    },

    /*
     * Calculate object dimensions from its properties
     * @private
     */
    _getNonTransformedDimensions: function () {
      var strokeWidth = this.strokeWidth,
        w = this.width + strokeWidth,
        h = this.height + strokeWidth;
      return { x: w, y: h };
    },

    /*
     * @private
     */
    _getTransformedDimensions: function (skewX, skewY) {
      if (typeof skewX === 'undefined') {
        skewX = this.skewX;
      }
      if (typeof skewY === 'undefined') {
        skewY = this.skewY;
      }
      var dimensions = this._getNonTransformedDimensions(),
        dimX = dimensions.x / 2, dimY = dimensions.y / 2,
        points = [
          {
            x: -dimX,
            y: -dimY
          },
          {
            x: dimX,
            y: -dimY
          },
          {
            x: -dimX,
            y: dimY
          },
          {
            x: dimX,
            y: dimY
          }],
        i, transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false),
        bbox;
      for (i = 0; i < points.length; i++) {
        points[i] = fabric.util.transformPoint(points[i], transformMatrix);
      }
      bbox = fabric.util.makeBoundingBoxFromPoints(points);
      return { x: bbox.width, y: bbox.height };
    },

    /*
     * private
     */
    _calculateCurrentDimensions: function () {
      var vpt = this.getViewportTransform(),
        dim = this._getTransformedDimensions(),
        p = fabric.util.transformPoint(dim, vpt, true);

      return p.scalarAdd(2 * this.padding);
    },

    /**
     * Draws a colored layer behind the object, inside its selection borders.
     * Requires public options: padding, selectionBackgroundColor
     * this function is called when the context is transformed
     * @param {CanvasRenderingContext2D} ctx Context to draw on
     * @return {fabric.Object} thisArg
     * @chainable
     */
    drawSelectionBackground: function (ctx) {
      if (!this.selectionBackgroundColor || this.group || !this.active) {
        return this;
      }
      ctx.save();
      var center = this.getCenterPoint(), wh = this._calculateCurrentDimensions(),
        vpt = this.canvas.viewportTransform;
      ctx.translate(center.x, center.y);
      ctx.scale(1 / vpt[0], 1 / vpt[3]);
      ctx.rotate(degreesToRadians(this.angle));
      ctx.fillStyle = this.selectionBackgroundColor;
      ctx.fillRect(-wh.x / 2, -wh.y / 2, wh.x, wh.y);
      ctx.restore();
      return this;
    },

    /**
     * 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 wh = this._calculateCurrentDimensions(),
        strokeWidth = 1 / this.borderScaleFactor,
        width = wh.x + strokeWidth,
        height = wh.y + strokeWidth;

      ctx.save();
      ctx.strokeStyle = this.borderColor;
      this._setLineDash(ctx, this.borderDashArray, null);

      ctx.strokeRect(
        -width / 2,
        -height / 2,
        width,
        height
      );

      if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) {

        var rotateHeight = -height / 2;

        ctx.beginPath();
        ctx.moveTo(0, rotateHeight);
        ctx.lineTo(0, rotateHeight - this.rotatingPointOffset);
        ctx.closePath();
        ctx.stroke();
      }

      ctx.restore();
      return this;
    },

    /**
     * Draws borders of an object's bounding box when it is inside a group.
     * Requires public properties: width, height
     * Requires public options: padding, borderColor
     * @param {CanvasRenderingContext2D} ctx Context to draw on
     * @param {object} options object representing current object parameters
     * @return {fabric.Object} thisArg
     * @chainable
     */
    drawBordersInGroup: function (ctx, options) {
      if (!this.hasBorders) {
        return this;
      }

      var p = this._getNonTransformedDimensions(),
        matrix = fabric.util.customTransformMatrix(options.scaleX, options.scaleY, options.skewX),
        wh = fabric.util.transformPoint(p, matrix),
        strokeWidth = 1 / this.borderScaleFactor,
        width = wh.x + strokeWidth,
        height = wh.y + strokeWidth;

      ctx.save();
      this._setLineDash(ctx, this.borderDashArray, null);
      ctx.strokeStyle = this.borderColor;

      ctx.strokeRect(
        -width / 2,
        -height / 2,
        width,
        height
      );

      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 wh = this._calculateCurrentDimensions(),
        width = wh.x,
        height = wh.y,
        scaleOffset = this.cornerSize,
        left = -(width + scaleOffset) / 2,
        top = -(height + scaleOffset) / 2,
        methodName = this.transparentCorners ? 'stroke' : 'fill';

      ctx.save();
      ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
      if (!this.transparentCorners) {
        ctx.strokeStyle = this.cornerStrokeColor;
      }
      this._setLineDash(ctx, this.cornerDashArray, null);

      // top-left
      this._drawControl('tl', ctx, methodName,
        left,
        top);

      // top-right
      this._drawControl('tr', ctx, methodName,
        left + width,
        top);

      // bottom-left
      this._drawControl('bl', ctx, methodName,
        left,
        top + height);

      // bottom-right
      this._drawControl('br', ctx, methodName,
        left + width,
        top + height);

      if (!this.get('lockUniScaling')) {

        // middle-top
        this._drawControl('mt', ctx, methodName,
          left + width / 2,
          top);

        // middle-bottom
        this._drawControl('mb', ctx, methodName,
          left + width / 2,
          top + height);

        // middle-right
        this._drawControl('mr', ctx, methodName,
          left + width,
          top + height / 2);

        // middle-left
        this._drawControl('ml', ctx, methodName,
          left,
          top + height / 2);
      }

      // middle-top-rotate
      if (this.hasRotatingPoint) {
        this._drawControl('mtr', ctx, methodName,
          left + width / 2,
          top - this.rotatingPointOffset);
      }

      ctx.restore();

      return this;
    },

    /**
     * @private
     */
    _drawControl: function (control, ctx, methodName, left, top) {
      if (!this.isControlVisible(control)) {
        return;
      }
      var size = this.cornerSize, stroke = !this.transparentCorners && this.cornerStrokeColor;
      switch (this.cornerStyle) {
        case 'circle':
          ctx.beginPath();
          ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI, false);
          ctx[methodName]();
          if (stroke) {
            ctx.stroke();
          }
          break;
        default:
          isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size);
          ctx[methodName + 'Rect'](left, top, size, size);
          if (stroke) {
            ctx.strokeRect(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 Property to animate (if string) or properties to animate (if object)
   * @param {Number|Object} value 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) {
      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);
      this.height = Math.abs(this.y2 - this.y1);

      this.left = 'left' in options
        ? options.left
        : this._getLeftToOriginX();

      this.top = 'top' in options
        ? options.top
        : this._getTopToOriginY();
    },

    /**
     * @private
     * @param {String} key
     * @param {*} value
     */
    _set: function (key, value) {
      this.callSuper('_set', 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
     * @param {Boolean} noTransform
     */
    _render: function (ctx, noTransform) {
      ctx.beginPath();

      if (noTransform) {
        //  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 - this.strokeWidth / 2,
          cp.y - this.strokeWidth / 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 p = this.calcLinePoints();
        ctx.moveTo(p.x1, p.y1);
        ctx.lineTo(p.x2, p.y2);
      }

      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 p = this.calcLinePoints();

      ctx.beginPath();
      fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, 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), this.calcLinePoints());
    },

    /*
     * Calculate object dimensions from its properties
     * @private
     */
    _getNonTransformedDimensions: function () {
      var dim = this.callSuper('_getNonTransformedDimensions');
      if (this.strokeLineCap === 'butt') {
        if (dim.x === 0) {
          dim.y -= this.strokeWidth;
        }
        if (dim.y === 0) {
          dim.x -= this.strokeWidth;
        }
      }
      return dim;
    },

    /**
     * Recalculates line points given width and height
     * @private
     */
    calcLinePoints: function () {
      var xMult = this.x1 <= this.x2 ? -1 : 1,
        yMult = this.y1 <= this.y2 ? -1 : 1,
        x1 = (xMult * this.width * 0.5),
        y1 = (yMult * this.height * 0.5),
        x2 = (xMult * this.width * -0.5),
        y2 = (yMult * this.height * -0.5);

      return {
        x1: x1,
        x2: x2,
        y1: y1,
        y2: 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(),
        p = { x1: this.x1, x2: this.x2, y1: this.y1, y2: this.y2 };

      if (!(this.group && this.group.type === 'path-group')) {
        p = this.calcLinePoints();
      }
      markup.push(
        '<line ', this.getSvgId(),
        'x1="', p.x1,
        '" y1="', p.y1,
        '" x2="', p.x2,
        '" y2="', p.y2,
        '" style="', this.getSvgStyles(),
        '" transform="', this.getSvgTransform(),
        this.getSvgTransformMatrix(),
        '"/>\n'
      );

      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
   * @param {function} [callback] invoked with new instance as first argument
   * @return {fabric.Line} instance of fabric.Line
   */
  fabric.Line.fromObject = function (object, callback) {
    var points = [object.x1, object.y1, object.x2, object.y2],
      line = new fabric.Line(points, object);
    callback && callback(line);
    return line;
  };

  /**
   * 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 = {}),
    pi = Math.PI,
    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,

    /**
     * Start angle of the circle, moving clockwise
     * @type Number
     * @default 0
     */
    startAngle: 0,

    /**
     * End angle of the circle
     * @type Number
     * @default 2Pi
     */
    endAngle: pi * 2,

    /**
     * Constructor
     * @param {Object} [options] Options object
     * @return {fabric.Circle} thisArg
     */
    initialize: function (options) {
      this.callSuper('initialize', options);
      this.set('radius', options && options.radius || 0);
    },

    /**
     * @private
     * @param {String} key
     * @param {*} 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 this.callSuper('toObject', ['radius', 'startAngle', 'endAngle'].concat(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 markup = this._createBaseSVGMarkup(), x = 0, y = 0,
        angle = (this.endAngle - this.startAngle) % (2 * pi);

      if (angle === 0) {
        if (this.group && this.group.type === 'path-group') {
          x = this.left + this.radius;
          y = this.top + this.radius;
        }
        markup.push(
          '<circle ', this.getSvgId(),
          'cx="' + x + '" cy="' + y + '" ',
          'r="', this.radius,
          '" style="', this.getSvgStyles(),
          '" transform="', this.getSvgTransform(),
          ' ', this.getSvgTransformMatrix(),
          '"/>\n'
        );
      }
      else {
        var startX = Math.cos(this.startAngle) * this.radius,
          startY = Math.sin(this.startAngle) * this.radius,
          endX = Math.cos(this.endAngle) * this.radius,
          endY = Math.sin(this.endAngle) * this.radius,
          largeFlag = angle > pi ? '1' : '0';

        markup.push(
          '<path d="M ' + startX + ' ' + startY,
          ' A ' + this.radius + ' ' + this.radius,
          ' 0 ', +largeFlag + ' 1', ' ' + endX + ' ' + endY,
          '" style="', this.getSvgStyles(),
          '" transform="', this.getSvgTransform(),
          ' ', this.getSvgTransformMatrix(),
          '"/>\n'
        );
      }

      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();
      ctx.arc(noTransform ? this.left + this.radius : 0,
        noTransform ? this.top + this.radius : 0,
        this.radius,
        this.startAngle,
        this.endAngle, false);
      this._renderFill(ctx);
      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 {fabric.Circle} thisArg
     */
    setRadius: function (value) {
      this.radius = value;
      return 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');
    }

    parsedAttributes.left = parsedAttributes.left || 0;
    parsedAttributes.top = parsedAttributes.top || 0;

    var obj = new fabric.Circle(extend(parsedAttributes, options));

    obj.left -= obj.radius;
    obj.top -= obj.radius;
    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
   * @param {function} [callback] invoked with new instance as first argument
   * @return {Object} Instance of fabric.Circle
   */
  fabric.Circle.fromObject = function (object, callback) {
    var circle = new fabric.Circle(object);
    callback && callback(circle);
    return circle;
  };

})(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) {
      this.callSuper('initialize', options);
      this.set('width', options && options.width || 100)
        .set('height', options && 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 ', this.getSvgId(),
        '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
   * @param {Function} [callback] Callback to invoke when an fabric.Triangle instance is created
   * @return {Object} instance of Canvas.Triangle
   */
  fabric.Triangle.fromObject = function (object, callback) {
    var triangle = new fabric.Triangle(object);
    callback && callback(triangle);
    return triangle;
  };

})(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) {
      this.callSuper('initialize', options);
      this.set('rx', options && options.rx || 0);
      this.set('ry', options && options.ry || 0);
    },

    /**
     * @private
     * @param {String} key
     * @param {*} value
     * @return {fabric.Ellipse} thisArg
     */
    _set: function (key, value) {
      this.callSuper('_set', key, value);
      switch (key) {

        case 'rx':
          this.rx = value;
          this.set('width', value * 2);
          break;

        case 'ry':
          this.ry = value;
          this.set('height', value * 2);
          break;

      }
      return this;
    },

    /**
     * Returns horizontal radius of an object (according to how an object is scaled)
     * @return {Number}
     */
    getRx: function () {
      return this.get('rx') * this.get('scaleX');
    },

    /**
     * Returns Vertical radius of an object (according to how an object is scaled)
     * @return {Number}
     */
    getRy: function () {
      return this.get('ry') * this.get('scaleY');
    },

    /**
     * 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 this.callSuper('toObject', ['rx', 'ry'].concat(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 markup = this._createBaseSVGMarkup(), x = 0, y = 0;
      if (this.group && this.group.type === 'path-group') {
        x = this.left + this.rx;
        y = this.top + this.ry;
      }
      markup.push(
        '<ellipse ', this.getSvgId(),
        'cx="', x, '" cy="', y, '" ',
        'rx="', this.rx,
        '" ry="', this.ry,
        '" style="', this.getSvgStyles(),
        '" transform="', this.getSvgTransform(),
        this.getSvgTransformMatrix(),
        '"/>\n'
      );

      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();
      ctx.save();
      ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0);
      ctx.arc(
        noTransform ? this.left + this.rx : 0,
        noTransform ? (this.top + this.ry) * 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);

    parsedAttributes.left = parsedAttributes.left || 0;
    parsedAttributes.top = parsedAttributes.top || 0;

    var ellipse = new fabric.Ellipse(extend(parsedAttributes, options));

    ellipse.top -= ellipse.ry;
    ellipse.left -= ellipse.rx;
    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
   * @param {function} [callback] invoked with new instance as first argument
   * @return {fabric.Ellipse}
   */
  fabric.Ellipse.fromObject = function (object, callback) {
    var ellipse = new fabric.Ellipse(object);
    callback && callback(ellipse);
    return ellipse;
  };

})(typeof exports !== 'undefined' ? exports : this);


(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  if (fabric.Rect) {
    fabric.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,

    /**
     * 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) {
      this.callSuper('initialize', options);
      this._initRxRy();

    },

    /**
     * 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
     * @param {Boolean} noTransform
     */
    _render: function (ctx, noTransform) {

      // optimize 1x1 case (used in spray brush)
      if (this.width === 1 && this.height === 1) {
        ctx.fillRect(-0.5, -0.5, 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 = noTransform ? this.left : -this.width / 2,
        y = noTransform ? this.top : -this.height / 2,
        isRounded = rx !== 0 || ry !== 0,
        /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */
        k = 1 - 0.5522847498;
      ctx.beginPath();

      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();
    },

    /**
     * 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 this.callSuper('toObject', ['rx', 'ry'].concat(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 markup = this._createBaseSVGMarkup(), x = this.left, y = this.top;
      if (!(this.group && this.group.type === 'path-group')) {
        x = -this.width / 2;
        y = -this.height / 2;
      }
      markup.push(
        '<rect ', this.getSvgId(),
        'x="', x, '" y="', y,
        '" rx="', this.get('rx'), '" ry="', this.get('ry'),
        '" width="', this.width, '" height="', this.height,
        '" style="', this.getSvgStyles(),
        '" transform="', this.getSvgTransform(),
        this.getSvgTransformMatrix(),
        '"/>\n');

      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(' '));

  /**
   * 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;
    }
    options = options || {};

    var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES);

    parsedAttributes.left = parsedAttributes.left || 0;
    parsedAttributes.top = parsedAttributes.top || 0;
    var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : {}), parsedAttributes));
    rect.visible = rect.visible && rect.width > 0 && rect.height > 0;
    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
   * @param {Function} [callback] Callback to invoke when an fabric.Rect instance is created
   * @return {Object} instance of fabric.Rect
   */
  fabric.Rect.fromObject = function (object, callback) {
    var rect = new fabric.Rect(object);
    callback && callback(rect);
    return rect;
  };

})(typeof exports !== 'undefined' ? exports : this);


(function (global) {

  'use strict';

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

  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,

    /**
     * Minimum X from points values, necessary to offset points
     * @type Number
     * @default
     */
    minX: 0,

    /**
     * Minimum Y from points values, necessary to offset points
     * @type Number
     * @default
     */
    minY: 0,

    /**
     * Constructor
     * @param {Array} points Array of points (where each point is an object with x and y)
     * @param {Object} [options] Options object
     * @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) {
      return fabric.Polygon.prototype.initialize.call(this, points, options);
    },

    /**
     * @private
     */
    _calcDimensions: function () {
      return fabric.Polygon.prototype._calcDimensions.call(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 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) {
      return fabric.Polygon.prototype.toSVG.call(this, reviver);
    },
    /* _TO_SVG_END_ */

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} noTransform
     */
    _render: function (ctx, noTransform) {
      if (!fabric.Polygon.prototype.commonRender.call(this, ctx, noTransform)) {
        return;
      }
      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);

    return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options));
  };
  /* _FROM_SVG_END_ */

  /**
   * Returns fabric.Polyline instance from an object representation
   * @static
   * @memberOf fabric.Polyline
   * @param {Object} object Object to create an instance from
   * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created
   * @return {fabric.Polyline} Instance of fabric.Polyline
   */
  fabric.Polyline.fromObject = function (object, callback) {
    var polyline = new fabric.Polyline(object.points, object);
    callback && callback(polyline);
    return polyline;
  };

})(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,

    /**
     * Minimum X from points values, necessary to offset points
     * @type Number
     * @default
     */
    minX: 0,

    /**
     * Minimum Y from points values, necessary to offset points
     * @type Number
     * @default
     */
    minY: 0,

    /**
     * Constructor
     * @param {Array} points Array of points
     * @param {Object} [options] Options object
     * @return {fabric.Polygon} thisArg
     */
    initialize: function (points, options) {
      options = options || {};
      this.points = points || [];
      this.callSuper('initialize', options);
      this._calcDimensions();
      if (!('top' in options)) {
        this.top = this.minY;
      }
      if (!('left' in options)) {
        this.left = this.minX;
      }
      this.pathOffset = {
        x: this.minX + this.width / 2,
        y: this.minY + this.height / 2
      };
    },

    /**
     * @private
     */
    _calcDimensions: function () {

      var points = this.points,
        minX = min(points, 'x'),
        minY = min(points, 'y'),
        maxX = max(points, 'x'),
        maxY = max(points, 'y');

      this.width = (maxX - minX) || 0;
      this.height = (maxY - minY) || 0;
      this.minX = minX || 0;
      this.minY = minY || 0;
    },

    /**
     * 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 = [], addTransform,
        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), ' ');
      }
      if (!(this.group && this.group.type === 'path-group')) {
        addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') ';
      }
      markup.push(
        '<', this.type, ' ', this.getSvgId(),
        'points="', points.join(''),
        '" style="', this.getSvgStyles(),
        '" transform="', this.getSvgTransform(), addTransform,
        ' ', this.getSvgTransformMatrix(),
        '"/>\n'
      );

      return reviver ? reviver(markup.join('')) : markup.join('');
    },
    /* _TO_SVG_END_ */

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} noTransform
     */
    _render: function (ctx, noTransform) {
      if (!this.commonRender(ctx, noTransform)) {
        return;
      }
      this._renderFill(ctx);
      if (this.stroke || this.strokeDashArray) {
        ctx.closePath();
        this._renderStroke(ctx);
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} noTransform
     */
    commonRender: function (ctx, noTransform) {
      var point, len = this.points.length;

      if (!len || isNaN(this.points[len - 1].y)) {
        // do not draw if no points or odd points
        // NaN comes from parseFloat of a empty string in parser
        return false;
      }

      noTransform || ctx.translate(-this.pathOffset.x, -this.pathOffset.y);
      ctx.beginPath();
      ctx.moveTo(this.points[0].x, this.points[0].y);
      for (var i = 0; i < len; i++) {
        point = this.points[i];
        ctx.lineTo(point.x, point.y);
      }
      return true;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderDashedStroke: function (ctx) {
      fabric.Polyline.prototype._renderDashedStroke.call(this, ctx);
      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);

    return new fabric.Polygon(points, extend(parsedAttributes, options));
  };
  /* _FROM_SVG_END_ */

  /**
   * Returns fabric.Polygon instance from an object representation
   * @static
   * @memberOf fabric.Polygon
   * @param {Object} object Object to create an instance from
   * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created
   * @return {fabric.Polygon} Instance of fabric.Polygon
   */
  fabric.Polygon.fromObject = function (object, callback) {
    var polygon = new fabric.Polygon(object.points, object);
    callback && callback(polygon);
    return polygon;
  };

})(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;
  }

  /**
   * 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,

    /**
     * Minimum X from points values, necessary to offset points
     * @type Number
     * @default
     */
    minX: 0,

    /**
     * Minimum Y from points values, necessary to offset points
     * @type Number
     * @default
     */
    minY: 0,

    /**
     * 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 || {};

      if (options) {
        this.setOptions(options);
      }

      if (!path) {
        path = [];
      }

      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._setPositionDimensions(options);

      if (options.sourcePath) {
        this.setSourcePath(options.sourcePath);
      }
      if (this.objectCaching) {
        this._createCacheCanvas();
        this.setupState({ propertySet: 'cacheProperties' });
      }
    },

    /**
     * @private
     * @param {Object} options Options object
     */
    _setPositionDimensions: function (options) {
      var calcDim = this._parseDimensions();

      this.minX = calcDim.left;
      this.minY = calcDim.top;
      this.width = calcDim.width;
      this.height = calcDim.height;

      if (typeof options.left === 'undefined') {
        this.left = calcDim.left + (this.originX === 'center'
          ? this.width / 2
          : this.originX === 'right'
            ? this.width
            : 0);
      }

      if (typeof options.top === 'undefined') {
        this.top = calcDim.top + (this.originY === 'center'
          ? this.height / 2
          : this.originY === 'bottom'
            ? this.height
            : 0);
      }

      this.pathOffset = this.pathOffset || {
        x: this.minX + this.width / 2,
        y: this.minY + this.height / 2
      };
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx context to render path on
     */
    _renderPathCommands: function (ctx) {
      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,
        l = -this.pathOffset.x,
        t = -this.pathOffset.y;

      if (this.group && this.group.type === 'path-group') {
        l = 0;
        t = 0;
      }

      ctx.beginPath();

      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];

            if (previous[0].match(/[CcSs]/) === null) {
              // If there is no previous command or if the previous command was not a C, c, S, or s,
              // the control point is coincident with the current point
              controlX = x;
              controlY = y;
            }
            else {
              // calculate reflection of previous control points
              controlX = 2 * x - controlX;
              controlY = 2 * y - controlY;
            }

            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];
            if (previous[0].match(/[CcSs]/) === null) {
              // If there is no previous command or if the previous command was not a C, c, S, or s,
              // the control point is coincident with the current point
              controlX = x;
              controlY = y;
            }
            else {
              // 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 {
              // calculate reflection of previous control point
              controlX = 2 * x - controlX;
              controlY = 2 * y - controlY;
            }

            ctx.quadraticCurveTo(
              controlX + l,
              controlY + t,
              tempX + l,
              tempY + t
            );
            x = tempX;
            y = tempY;

            break;

          case 'T':
            tempX = current[1];
            tempY = 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 {
              // calculate reflection of previous control point
              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;
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx context to render path on
     */
    _render: function (ctx) {
      this._renderPathCommands(ctx);
      this._renderFill(ctx);
      this._renderStroke(ctx);
    },

    /**
     * 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', ['sourcePath', 'pathOffset'].concat(propertiesToInclude)), {
        path: this.path.map(function (item) { return item.slice() })
      });
      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(), addTransform = '';

      for (var i = 0, len = this.path.length; i < len; i++) {
        chunks.push(this.path[i].join(' '));
      }
      var path = chunks.join(' ');
      if (!(this.group && this.group.type === 'path-group')) {
        addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') ';
      }
      markup.push(
        '<path ', this.getSvgId(),
        'd="', path,
        '" style="', this.getSvgStyles(),
        '" transform="', this.getSvgTransform(), addTransform,
        this.getSvgTransformMatrix(), '" stroke-linecap="round" ',
        '/>\n'
      );

      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+)?)/ig,
        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 = [],
        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,
        bounds;

      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];
            bounds = [];
            break;

          case 'L': // lineto, absolute
            x = current[1];
            y = current[2];
            bounds = [];
            break;

          case 'h': // horizontal lineto, relative
            x += current[1];
            bounds = [];
            break;

          case 'H': // horizontal lineto, absolute
            x = current[1];
            bounds = [];
            break;

          case 'v': // vertical lineto, relative
            y += current[1];
            bounds = [];
            break;

          case 'V': // verical lineto, absolute
            y = current[1];
            bounds = [];
            break;

          case 'm': // moveTo, relative
            x += current[1];
            y += current[2];
            subpathStartX = x;
            subpathStartY = y;
            bounds = [];
            break;

          case 'M': // moveTo, absolute
            x = current[1];
            y = current[2];
            subpathStartX = x;
            subpathStartY = y;
            bounds = [];
            break;

          case 'c': // bezierCurveTo, relative
            tempX = x + current[5];
            tempY = y + current[6];
            controlX = x + current[3];
            controlY = y + current[4];
            bounds = fabric.util.getBoundsOfCurve(x, y,
              x + current[1], // x1
              y + current[2], // y1
              controlX, // x2
              controlY, // y2
              tempX,
              tempY
            );
            x = tempX;
            y = tempY;
            break;

          case 'C': // bezierCurveTo, absolute
            x = current[5];
            y = current[6];
            controlX = current[3];
            controlY = current[4];
            bounds = fabric.util.getBoundsOfCurve(x, y,
              current[1],
              current[2],
              controlX,
              controlY,
              x,
              y
            );
            break;

          case 's': // shorthand cubic bezierCurveTo, relative

            // transform to absolute x,y
            tempX = x + current[3];
            tempY = y + current[4];

            if (previous[0].match(/[CcSs]/) === null) {
              // If there is no previous command or if the previous command was not a C, c, S, or s,
              // the control point is coincident with the current point
              controlX = x;
              controlY = y;
            }
            else {
              // calculate reflection of previous control points
              controlX = 2 * x - controlX;
              controlY = 2 * y - controlY;
            }

            bounds = fabric.util.getBoundsOfCurve(x, y,
              controlX,
              controlY,
              x + current[1],
              y + current[2],
              tempX,
              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 = x + current[1];
            controlY = y + current[2];
            x = tempX;
            y = tempY;
            break;

          case 'S': // shorthand cubic bezierCurveTo, absolute
            tempX = current[3];
            tempY = current[4];
            if (previous[0].match(/[CcSs]/) === null) {
              // If there is no previous command or if the previous command was not a C, c, S, or s,
              // the control point is coincident with the current point
              controlX = x;
              controlY = y;
            }
            else {
              // calculate reflection of previous control points
              controlX = 2 * x - controlX;
              controlY = 2 * y - controlY;
            }
            bounds = fabric.util.getBoundsOfCurve(x, y,
              controlX,
              controlY,
              current[1],
              current[2],
              tempX,
              tempY
            );
            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];
            bounds = fabric.util.getBoundsOfCurve(x, y,
              controlX,
              controlY,
              controlX,
              controlY,
              tempX,
              tempY
            );
            x = tempX;
            y = tempY;
            break;

          case 'Q': // quadraticCurveTo, absolute
            controlX = current[1];
            controlY = current[2];
            bounds = fabric.util.getBoundsOfCurve(x, y,
              controlX,
              controlY,
              controlX,
              controlY,
              current[3],
              current[4]
            );
            x = current[3];
            y = current[4];
            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 {
              // calculate reflection of previous control point
              controlX = 2 * x - controlX;
              controlY = 2 * y - controlY;
            }

            bounds = fabric.util.getBoundsOfCurve(x, y,
              controlX,
              controlY,
              controlX,
              controlY,
              tempX,
              tempY
            );
            x = tempX;
            y = tempY;

            break;

          case 'T':
            tempX = current[1];
            tempY = 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 {
              // calculate reflection of previous control point
              controlX = 2 * x - controlX;
              controlY = 2 * y - controlY;
            }
            bounds = fabric.util.getBoundsOfCurve(x, y,
              controlX,
              controlY,
              controlX,
              controlY,
              tempX,
              tempY
            );
            x = tempX;
            y = tempY;
            break;

          case 'a':
            // TODO: optimize this
            bounds = fabric.util.getBoundsOfArc(x, y,
              current[1],
              current[2],
              current[3],
              current[4],
              current[5],
              current[6] + x,
              current[7] + y
            );
            x += current[6];
            y += current[7];
            break;

          case 'A':
            // TODO: optimize this
            bounds = fabric.util.getBoundsOfArc(x, y,
              current[1],
              current[2],
              current[3],
              current[4],
              current[5],
              current[6],
              current[7]
            );
            x = current[6];
            y = current[7];
            break;

          case 'z':
          case 'Z':
            x = subpathStartX;
            y = subpathStartY;
            break;
        }
        previous = current;
        bounds.forEach(function (point) {
          aX.push(point.x);
          aY.push(point.y);
        });
        aX.push(x);
        aY.push(y);
      }

      var minX = min(aX) || 0,
        minY = min(aY) || 0,
        maxX = max(aX) || 0,
        maxY = max(aY) || 0,
        deltaX = maxX - minX,
        deltaY = maxY - minY,

        o = {
          left: minX,
          top: minY,
          width: deltaX,
          height: deltaY
        };

      return o;
    }
  });

  /**
   * 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) {
    // remove this pattern rom 2.0, accept just object.
    var path;
    if (typeof object.path === 'string') {
      fabric.loadSVGFromURL(object.path, function (elements) {
        var pathUrl = object.path;
        path = elements[0];
        delete object.path;

        fabric.util.object.extend(path, object);
        path.setSourcePath(pathUrl);

        callback && callback(path);
      });
    }
    else {
      path = new fabric.Path(object.path, object);
      callback && callback(path);
      return path;
    }
  };

  /* _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.Object, /** @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;
      }

      if (options.toBeParsed) {
        this.parseDimensionsFromPaths(options);
        delete options.toBeParsed;
      }
      this.setOptions(options);
      this.setCoords();
      if (options.sourcePath) {
        this.setSourcePath(options.sourcePath);
      }
      if (this.objectCaching) {
        this._createCacheCanvas();
        this.setupState({ propertySet: 'cacheProperties' });
      }
    },

    /**
     * Calculate width and height based on paths contained
     */
    parseDimensionsFromPaths: function (options) {
      var points, p, xC = [], yC = [], path, height, width,
        m;
      for (var j = this.paths.length; j--;) {
        path = this.paths[j];
        height = path.height + path.strokeWidth;
        width = path.width + path.strokeWidth;
        points = [
          { x: path.left, y: path.top },
          { x: path.left + width, y: path.top },
          { x: path.left, y: path.top + height },
          { x: path.left + width, y: path.top + height }
        ];
        m = this.paths[j].transformMatrix;
        for (var i = 0; i < points.length; i++) {
          p = points[i];
          if (m) {
            p = fabric.util.transformPoint(p, m, false);
          }
          xC.push(p.x);
          yC.push(p.y);
        }
      }
      options.width = Math.max.apply(null, xC);
      options.height = Math.max.apply(null, yC);
    },

    /**
     * Execute the drawing operation for an object on a specified context
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} [noTransform] When true, context is not transformed
     */
    drawObject: function (ctx) {
      ctx.save();
      ctx.translate(-this.width / 2, -this.height / 2);
      for (var i = 0, l = this.paths.length; i < l; ++i) {
        this.paths[i].render(ctx, true);
      }
      ctx.restore();
    },

    /**
     * Check if cache is dirty
     */
    isCacheDirty: function () {
      if (this.callSuper('isCacheDirty')) {
        return true
      }
      if (!this.statefullCache) {
        return false;
      }
      for (var i = 0, len = this.paths.length; i < len; i++) {
        if (this.paths[i].isCacheDirty(true)) {
          var dim = this._getNonTransformedDimensions();
          this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
          return true
        }
      }
      return false;
    },

    /**
     * Sets certain property to a certain value
     * @param {String} prop
     * @param {*} 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, ['sourcePath'].concat(propertiesToInclude)), {
        paths: invoke(this.getObjects(), 'toObject', propertiesToInclude)
      });
      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(),
        p = this.getPointByOrigin('left', 'top'),
        translatePart = 'translate(' + p.x + ' ' + p.y + ')',
        markup = this._createBaseSVGMarkup();
      markup.push(
        '<g ', this.getSvgId(),
        'style="', this.getSvgStyles(), '" ',
        'transform="', this.getSvgTransformMatrix(), translatePart, this.getSvgTransform(), '" ',
        '>\n'
      );

      for (var i = 0, len = objects.length; i < len; i++) {
        markup.push('\t', objects[i].toSVG(reviver));
      }
      markup.push('</g>\n');

      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') || '';
      if (typeof firstPathFill !== 'string') {
        return false;
      }
      firstPathFill = firstPathFill.toLowerCase();
      return this.getObjects().every(function (path) {
        var pathFill = path.get('fill') || '';
        return typeof pathFill === 'string' && (pathFill).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) {
    // remove this pattern from 2.0 accepts only object
    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',

    /**
     * Width of stroke
     * @type Number
     * @default
     */
    strokeWidth: 0,

    /**
     * Indicates if click events should also check for subtargets
     * @type Boolean
     * @default
     */
    subTargetCheck: false,

    /**
     * Constructor
     * @param {Object} objects Group objects
     * @param {Object} [options] Options object
     * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already.
     * @return {Object} thisArg
     */
    initialize: function (objects, options, isAlreadyGrouped) {
      options = options || {};

      this._objects = [];
      // if objects enclosed in a group have been grouped already,
      // we cannot change properties of objects.
      // Thus we need to set options to group without objects,
      // because delegatedProperties propagate to objects.
      isAlreadyGrouped && this.callSuper('initialize', options);

      this._objects = objects || [];
      for (var i = this._objects.length; i--;) {
        this._objects[i].group = this;
      }

      this.originalState = {};

      if (options.originX) {
        this.originX = options.originX;
      }
      if (options.originY) {
        this.originY = options.originY;
      }

      if (isAlreadyGrouped) {
        // do not change coordinate of objects enclosed in a group,
        // because objects coordinate system have been group coodinate system already.
        this._updateObjectsCoords(true);
      }
      else {
        this._calcBounds();
        this._updateObjectsCoords();
        this.callSuper('initialize', options);
      }

      this.setCoords();
      this.saveCoords();
    },

    /**
     * @private
     * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change
     */
    _updateObjectsCoords: function (skipCoordsChange) {
      var center = this.getCenterPoint();
      for (var i = this._objects.length; i--;) {
        this._updateObjectCoords(this._objects[i], center, skipCoordsChange);
      }
    },

    /**
     * @private
     * @param {Object} object
     * @param {fabric.Point} center, current center of group.
     * @param {Boolean} [skipCoordsChange] if true, coordinates of object dose not change
     */
    _updateObjectCoords: function (object, center, skipCoordsChange) {
      // do not display corners of objects enclosed in a group
      object.__origHasControls = object.hasControls;
      object.hasControls = false;

      if (skipCoordsChange) {
        return;
      }

      var objectLeft = object.getLeft(),
        objectTop = object.getTop(),
        ignoreZoom = true;

      object.set({
        originalLeft: objectLeft,
        originalTop: objectTop,
        left: objectLeft - center.x,
        top: objectTop - center.y
      });
      object.setCoords(ignoreZoom);
    },

    /**
     * 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();
      fabric.util.resetObjectTransform(this);
      if (object) {
        this._objects.push(object);
        object.group = this;
        object._set('canvas', this.canvas);
      }
      // since _restoreObjectsState set objects inactive
      this.forEachObject(this._setObjectActive, this);
      this._calcBounds();
      this._updateObjectsCoords();
      this.dirty = true;
      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._restoreObjectsState();
      fabric.util.resetObjectTransform(this);
      // since _restoreObjectsState set objects inactive
      this.forEachObject(this._setObjectActive, this);

      this.remove(object);
      this._calcBounds();
      this._updateObjectsCoords();
      this.dirty = true;
      return this;
    },

    /**
     * @private
     */
    _onObjectAdded: function (object) {
      this.dirty = true;
      object.group = this;
      object._set('canvas', this.canvas);
    },

    /**
     * @private
     */
    _onObjectRemoved: function (object) {
      this.dirty = true;
      delete object.group;
      object.set('active', false);
    },

    /**
     * Properties that are delegated to group objects when reading/writing
     * @param {Object} delegatedProperties
     */
    delegatedProperties: {
      fill: true,
      stroke: true,
      strokeWidth: true,
      fontFamily: true,
      fontWeight: true,
      fontSize: true,
      fontStyle: true,
      lineHeight: true,
      textDecoration: true,
      textAlign: true,
      backgroundColor: true
    },

    /**
     * @private
     */
    _set: function (key, value) {
      var i = this._objects.length;

      if (this.delegatedProperties[key] || key === 'canvas') {
        while (i--) {
          this._objects[i].set(key, value);
        }
      }
      else {
        while (i--) {
          this._objects[i].setOnGroup(key, value);
        }
      }

      this.callSuper('_set', 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) {
      this._transformDone = true;
      this.callSuper('render', ctx);
      this._transformDone = false;
    },

    /**
     * Execute the drawing operation for an object on a specified context
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} [noTransform] When true, context is not transformed
     */
    drawObject: function (ctx) {
      for (var i = 0, len = this._objects.length; i < len; i++) {
        this._renderObject(this._objects[i], ctx);
      }
    },

    /**
     * Check if cache is dirty
     */
    isCacheDirty: function () {
      if (this.callSuper('isCacheDirty')) {
        return true
      }
      if (!this.statefullCache) {
        return false;
      }
      for (var i = 0, len = this._objects.length; i < len; i++) {
        if (this._objects[i].isCacheDirty(true)) {
          var dim = this._getNonTransformedDimensions();
          this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
          return true
        }
      }
      return false;
    },

    /**
     * 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) {
      ctx.save();
      ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
      this.callSuper('_renderControls', ctx, noTransform);
      for (var i = 0, len = this._objects.length; i < len; i++) {
        this._objects[i]._renderControls(ctx);
      }
      ctx.restore();
    },

    /**
     * @private
     */
    _renderObject: function (object, ctx) {
      // do not render if object is not visible
      if (!object.visible) {
        return;
      }

      var originalHasRotatingPoint = object.hasRotatingPoint;
      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;
    },

    /**
     * Realises the transform from this group onto the supplied object
     * i.e. it tells you what would happen if the supplied object was in
     * the group, and then the group was destroyed. It mutates the supplied
     * object.
     * @param {fabric.Object} object
     * @return {fabric.Object} transformedObject
     */
    realizeTransform: function (object) {
      var matrix = object.calcTransformMatrix(),
        options = fabric.util.qrDecompose(matrix),
        center = new fabric.Point(options.translateX, options.translateY);
      object.flipX = false;
      object.flipY = false;
      object.set('scaleX', options.scaleX);
      object.set('scaleY', options.scaleY);
      object.skewX = options.skewX;
      object.skewY = options.skewY;
      object.angle = options.angle;
      object.setPositionByOrigin(center, 'center', 'center');
      return object;
    },

    /**
     * Restores original state of a specified object in group
     * @private
     * @param {fabric.Object} object
     * @return {fabric.Group} thisArg
     */
    _restoreObjectState: function (object) {
      this.realizeTransform(object);
      object.setCoords();
      object.hasControls = object.__origHasControls;
      delete object.__origHasControls;
      object.set('active', false);
      delete object.group;

      return this;
    },

    /**
     * Destroys a group (restoring state of its objects)
     * @return {fabric.Group} thisArg
     * @chainable
     */
    destroy: function () {
      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 () {
      var ignoreZoom = true;
      this.forEachObject(function (object) {
        object.setCoords(ignoreZoom);
      });
      return this;
    },

    /**
     * @private
     */
    _calcBounds: function (onlyWidthHeight) {
      var aX = [],
        aY = [],
        o, prop,
        props = ['tr', 'br', 'bl', 'tl'],
        i = 0, iLen = this._objects.length,
        j, jLen = props.length,
        ignoreZoom = true;

      for (; i < iLen; ++i) {
        o = this._objects[i];
        o.setCoords(ignoreZoom);
        for (j = 0; j < jLen; j++) {
          prop = props[j];
          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 minXY = new fabric.Point(min(aX), min(aY)),
        maxXY = new fabric.Point(max(aX), max(aY)),
        obj = {
          width: (maxXY.x - minXY.x) || 0,
          height: (maxXY.y - minXY.y) || 0
        };

      if (!onlyWidthHeight) {
        obj.left = minXY.x || 0;
        obj.top = minXY.y || 0;
        if (this.originX === 'center') {
          obj.left += obj.width / 2;
        }
        if (this.originX === 'right') {
          obj.left += obj.width;
        }
        if (this.originY === 'center') {
          obj.top += obj.height / 2;
        }
        if (this.originY === 'bottom') {
          obj.top += obj.height;
        }
      }
      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 = this._createBaseSVGMarkup();
      markup.push(
        '<g ', this.getSvgId(), 'transform="',
        /* avoiding styles intentionally */
        this.getSvgTransform(),
        this.getSvgTransformMatrix(),
        '" style="',
        this.getSvgFilter(),
        '">\n'
      );

      for (var i = 0, len = this._objects.length; i < len; i++) {
        markup.push('\t', this._objects[i].toSVG(reviver));
      }

      markup.push('</g>\n');

      return reviver ? reviver(markup.join('')) : markup.join('');
    },
    /* _TO_SVG_END_ */

    /**
     * Returns requested property
     * @param {String} prop Property to get
     * @return {*}
     */
    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
   */
  fabric.Group.fromObject = function (object, callback) {
    fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) {
      delete object.objects;
      callback && callback(new fabric.Group(enlivenedObjects, object, true));
    });
  };

  /**
   * 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;
  }

  var stateProperties = fabric.Object.prototype.stateProperties.concat();
  stateProperties.push(
    'alignX',
    'alignY',
    'meetOrSlice'
  );

  /**
   * 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", "use-credentials")
     * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes
     * @type String
     * @default
     */
    crossOrigin: '',

    /**
     * AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
     * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
     * This parameter defines how the picture is aligned to its viewport when image element width differs from image width.
     * @type String
     * @default
     */
    alignX: 'none',

    /**
     * AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
     * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
     * This parameter defines how the picture is aligned to its viewport when image element height differs from image height.
     * @type String
     * @default
     */
    alignY: 'none',

    /**
     * meetOrSlice value, part of preserveAspectRatio  (one of "meet", "slice").
     * if meet the image is always fully visibile, if slice the viewport is always filled with image.
     * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
     * @type String
     * @default
     */
    meetOrSlice: 'meet',

    /**
     * Width of a stroke.
     * For image quality a stroke multiple of 2 gives better results.
     * @type Number
     * @default
     */
    strokeWidth: 0,

    /**
     * private
     * contains last value of scaleX to detect
     * if the Image got resized after the last Render
     * @type Number
     */
    _lastScaleX: 1,

    /**
     * private
     * contains last value of scaleY to detect
     * if the Image got resized after the last Render
     * @type Number
     */
    _lastScaleY: 1,

    /**
     * minimum scale factor under which any resizeFilter is triggered to resize the image
     * 0 will disable the automatic resize. 1 will trigger automatically always.
     * number bigger than 1 can be used in case we want to scale with some filter above
     * the natural image dimensions
     * @type Number
     */
    minimumScaleTrigger: 0.5,

    /**
     * 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 `true`, object is cached on an additional canvas.
     * default to false for images
     * since 1.7.0
     * @type Boolean
     * @default
     */
    objectCaching: false,

    /**
     * Constructor
     * @param {HTMLImageElement | String} element Image element
     * @param {Object} [options] Options object
     * @param {function} [callback] callback function to call after eventual filters applied.
     * @return {fabric.Image} thisArg
     */
    initialize: function (element, options, callback) {
      options || (options = {});
      this.filters = [];
      this.resizeFilters = [];
      this.callSuper('initialize', options);
      this._initElement(element, options, callback);
    },

    /**
     * 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
     * @param {Object} [options] Options object
     * @return {fabric.Image} thisArg
     * @chainable
     */
    setElement: function (element, callback, options) {

      var _callback, _this;

      this._element = element;
      this._originalElement = element;
      this._initConfig(options);

      if (this.resizeFilters.length === 0) {
        _callback = callback;
      }
      else {
        _this = this;
        _callback = function () {
          _this.applyFilters(callback, _this.resizeFilters, _this._filteredEl || _this._originalElement, true);
        };
      }

      if (this.filters.length !== 0) {
        this.applyFilters(_callback);
      }
      else if (_callback) {
        _callback(this);
      }

      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
      };
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _stroke: function (ctx) {
      if (!this.stroke || this.strokeWidth === 0) {
        return;
      }
      var w = this.width / 2, h = this.height / 2;
      ctx.beginPath();
      ctx.moveTo(-w, -h);
      ctx.lineTo(w, -h);
      ctx.lineTo(w, h);
      ctx.lineTo(-w, h);
      ctx.lineTo(-w, -h);
      ctx.closePath();
    },

    /**
     * @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) {
      var filters = [], resizeFilters = [],
        scaleX = 1, scaleY = 1;

      this.filters.forEach(function (filterObj) {
        if (filterObj) {
          if (filterObj.type === 'Resize') {
            scaleX *= filterObj.scaleX;
            scaleY *= filterObj.scaleY;
          }
          filters.push(filterObj.toObject());
        }
      });

      this.resizeFilters.forEach(function (filterObj) {
        filterObj && resizeFilters.push(filterObj.toObject());
      });
      var object = extend(
        this.callSuper(
          'toObject',
          ['crossOrigin', 'alignX', 'alignY', 'meetOrSlice'].concat(propertiesToInclude)
        ), {
        src: this.getSrc(),
        filters: filters,
        resizeFilters: resizeFilters,
      });

      object.width /= scaleX;
      object.height /= scaleY;

      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(), x = -this.width / 2, y = -this.height / 2,
        preserveAspectRatio = 'none', filtered = true;
      if (this.group && this.group.type === 'path-group') {
        x = this.left;
        y = this.top;
      }
      if (this.alignX !== 'none' && this.alignY !== 'none') {
        preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice;
      }
      markup.push(
        '<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
        '<image ', this.getSvgId(), 'xlink:href="', this.getSvgSrc(filtered),
        '" x="', x, '" y="', y,
        '" 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
        '" width="', this.width,
        '" height="', this.height,
        '" preserveAspectRatio="', preserveAspectRatio, '"',
        '></image>\n'
      );

      if (this.stroke || this.strokeDashArray) {
        var origFill = this.fill;
        this.fill = null;
        markup.push(
          '<rect ',
          'x="', x, '" y="', y,
          '" width="', this.width, '" height="', this.height,
          '" style="', this.getSvgStyles(),
          '"/>\n'
        );
        this.fill = origFill;
      }

      markup.push('</g>\n');

      return reviver ? reviver(markup.join('')) : markup.join('');
    },
    /* _TO_SVG_END_ */

    /**
     * Returns source of an image
     * @param {Boolean} filtered indicates if the src is needed for svg
     * @return {String} Source of an image
     */
    getSrc: function (filtered) {
      var element = filtered ? this._element : this._originalElement;
      if (element) {
        return fabric.isLikelyNode ? element._src : element.src;
      }
      else {
        return this.src || '';
      }
    },

    /**
     * Sets source of an image
     * @param {String} src Source string (URL)
     * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied)
     * @param {Object} [options] Options object
     * @return {fabric.Image} thisArg
     * @chainable
     */
    setSrc: function (src, callback, options) {
      fabric.util.loadImage(src, function (img) {
        return this.setElement(img, callback, options);
      }, this, options && options.crossOrigin);
    },

    /**
     * Returns string representation of an instance
     * @return {String} String representation of an instance
     */
    toString: function () {
      return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';
    },

    /**
     * Applies filters assigned to this image (from "filters" array)
     * @method applyFilters
     * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated
     * @param {Array} filters to be applied
     * @param {fabric.Image} imgElement image to filter ( default to this._element )
     * @param {Boolean} forResizing
     * @return {CanvasElement} canvasEl to be drawn immediately
     * @chainable
     */
    applyFilters: function (callback, filters, imgElement, forResizing) {

      filters = filters || this.filters;
      imgElement = imgElement || this._originalElement;

      if (!imgElement) {
        return;
      }

      var replacement = fabric.util.createImage(),
        retinaScaling = this.canvas ? this.canvas.getRetinaScaling() : fabric.devicePixelRatio,
        minimumScale = this.minimumScaleTrigger / retinaScaling,
        _this = this, scaleX, scaleY;

      if (filters.length === 0) {
        this._element = imgElement;
        callback && callback(this);
        return imgElement;
      }

      var canvasEl = fabric.util.createCanvasElement();
      canvasEl.width = imgElement.width;
      canvasEl.height = imgElement.height;
      canvasEl.getContext('2d').drawImage(imgElement, 0, 0, imgElement.width, imgElement.height);

      filters.forEach(function (filter) {
        if (!filter) {
          return;
        }
        if (forResizing) {
          scaleX = _this.scaleX < minimumScale ? _this.scaleX : 1;
          scaleY = _this.scaleY < minimumScale ? _this.scaleY : 1;
          if (scaleX * retinaScaling < 1) {
            scaleX *= retinaScaling;
          }
          if (scaleY * retinaScaling < 1) {
            scaleY *= retinaScaling;
          }
        }
        else {
          scaleX = filter.scaleX;
          scaleY = filter.scaleY;
        }
        filter.applyTo(canvasEl, scaleX, scaleY);
        if (!forResizing && filter.type === 'Resize') {
          _this.width *= filter.scaleX;
          _this.height *= filter.scaleY;
        }
      });

      /** @ignore */
      replacement.width = canvasEl.width;
      replacement.height = canvasEl.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;
        !forResizing && (_this._filteredEl = replacement);
        callback && callback(_this);
      }
      else {
        replacement.onload = function () {
          _this._element = replacement;
          !forResizing && (_this._filteredEl = replacement);
          callback && callback(_this);
          replacement.onload = canvasEl = null;
        };
        replacement.src = canvasEl.toDataURL('image/png');
      }
      return canvasEl;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} noTransform
     */
    _render: function (ctx, noTransform) {
      var x, y, imageMargins = this._findMargins(), elementToDraw;

      x = (noTransform ? this.left : -this.width / 2);
      y = (noTransform ? this.top : -this.height / 2);

      if (this.meetOrSlice === 'slice') {
        ctx.beginPath();
        ctx.rect(x, y, this.width, this.height);
        ctx.clip();
      }

      if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) {
        this._lastScaleX = this.scaleX;
        this._lastScaleY = this.scaleY;
        elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true);
      }
      else {
        elementToDraw = this._element;
      }
      elementToDraw && ctx.drawImage(elementToDraw,
        x + imageMargins.marginX,
        y + imageMargins.marginY,
        imageMargins.width,
        imageMargins.height
      );

      this._stroke(ctx);
      this._renderStroke(ctx);
    },

    /**
     * @private, needed to check if image needs resize
     */
    _needsResize: function () {
      return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY);
    },

    /**
     * @private
     */
    _findMargins: function () {
      var width = this.width, height = this.height, scales,
        scale, marginX = 0, marginY = 0;

      if (this.alignX !== 'none' || this.alignY !== 'none') {
        scales = [this.width / this._element.width, this.height / this._element.height];
        scale = this.meetOrSlice === 'meet'
          ? Math.min.apply(null, scales) : Math.max.apply(null, scales);
        width = this._element.width * scale;
        height = this._element.height * scale;
        if (this.alignX === 'Mid') {
          marginX = (this.width - width) / 2;
        }
        if (this.alignX === 'Max') {
          marginX = this.width - width;
        }
        if (this.alignY === 'Mid') {
          marginY = (this.height - height) / 2;
        }
        if (this.alignY === 'Max') {
          marginY = this.height - height;
        }
      }
      return {
        width: width,
        height: height,
        marginX: marginX,
        marginY: marginY
      };
    },

    /**
     * @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
     * @param {Object} [options] Options object
     */
    _initElement: function (element, options, callback) {
      this.setElement(fabric.util.getById(element), callback, options);
      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 {Array} filters to be initialized
     * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created
     */
    _initFilters: function (filters, callback) {
      if (filters && filters.length) {
        fabric.util.enlivenObjects(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.filters, function (filters) {
        object.filters = filters || [];
        fabric.Image.prototype._initFilters.call(object, object.resizeFilters, function (resizeFilters) {
          object.resizeFilters = resizeFilters || [];
          return new fabric.Image(img, object, callback);
        });
      });
    }, 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 && 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 preserveAspectRatio 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),
      preserveAR;

    if (parsedAttributes.preserveAspectRatio) {
      preserveAR = fabric.util.parsePreserveAspectRatioAttribute(parsedAttributes.preserveAspectRatio);
      extend(parsedAttributes, preserveAR);
    }

    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',

  /**
   * Constructor
   * @param {Object} [options] Options object
   */
  initialize: function (options) {
    if (options) {
      this.setOptions(options);
    }
  },

  /**
   * Sets filter's properties from options
   * @param {Object} [options] Options object
   */
  setOptions: function (options) {
    for (var prop in options) {
      this[prop] = options[prop];
    }
  },

  /**
   * 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,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.Brightness = createClass(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 (-255..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,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.Convolute = createClass(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
      ];
    },

    /**
     * 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,
        output = context.createImageData(sw, sh),
        dst = output.data,
        // go through the destination image pixels
        alphaFac = this.opaque ? 1 : 0,
        r, g, b, a, dstOff,
        scx, scy, srcOff, wt;

      for (var y = 0; y < sh; y++) {
        for (var x = 0; x < sw; x++) {
          dstOff = (y * sw + 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++) {
              scy = y + cy - halfSide;
              scx = x + cx - halfSide;

              // eslint-disable-next-line max-depth
              if (scy < 0 || scy > sh || scx < 0 || scx > sw) {
                continue;
              }

              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,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  // eslint-disable-next-line max-len
  filters.GradientTransparency = createClass(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 = {}),
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.Grayscale = createClass(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 = {}),
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.Invert = createClass(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,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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
   */
  filters.Mask = createClass(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 = canvasEl.width;
      maskCanvasEl.height = canvasEl.height;

      maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, canvasEl.width, canvasEl.height);

      var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, canvasEl.width, canvasEl.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,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.Noise = createClass(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,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.Pixelate = createClass(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,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.RemoveWhite = createClass(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] = 0;
        }
      }

      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 = {}),
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.Sepia = createClass(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 = {}),
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.Sepia2 = createClass(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.140;
      }

      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,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.Tint = createClass(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,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * 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));
   */
  filters.Multiply = createClass(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,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * Color Blend filter class
   * @class fabric.Image.filter.Blend
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @example
   * var filter = new fabric.Image.filters.Blend({
   *  color: '#000',
   *  mode: 'multiply'
   * });
   *
   * var filter = new fabric.Image.filters.Blend({
   *  image: fabricImageObject,
   *  mode: 'multiply',
   *  alpha: 0.5
   * });

   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */

  filters.Blend = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Blend.prototype */ {
    type: 'Blend',

    initialize: function (options) {
      options = options || {};
      this.color = options.color || '#000';
      this.image = options.image || false;
      this.mode = options.mode || 'multiply';
      this.alpha = options.alpha || 1;
    },

    applyTo: function (canvasEl) {
      var context = canvasEl.getContext('2d'),
        imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
        data = imageData.data,
        tr, tg, tb,
        r, g, b,
        _r, _g, _b,
        source,
        isImage = false;

      if (this.image) {
        // Blend images
        isImage = true;

        var _el = fabric.util.createCanvasElement();
        _el.width = this.image.width;
        _el.height = this.image.height;

        var tmpCanvas = new fabric.StaticCanvas(_el);
        tmpCanvas.add(this.image);
        var context2 = tmpCanvas.getContext('2d');
        source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data;
      }
      else {
        // Blend color
        source = new fabric.Color(this.color).getSource();

        tr = source[0] * this.alpha;
        tg = source[1] * this.alpha;
        tb = source[2] * this.alpha;
      }

      for (var i = 0, len = data.length; i < len; i += 4) {

        r = data[i];
        g = data[i + 1];
        b = data[i + 2];

        if (isImage) {
          tr = source[i] * this.alpha;
          tg = source[i + 1] * this.alpha;
          tb = source[i + 2] * this.alpha;
        }

        switch (this.mode) {
          case 'multiply':
            data[i] = r * tr / 255;
            data[i + 1] = g * tg / 255;
            data[i + 2] = b * tb / 255;
            break;
          case 'screen':
            data[i] = 1 - (1 - r) * (1 - tr);
            data[i + 1] = 1 - (1 - g) * (1 - tg);
            data[i + 2] = 1 - (1 - b) * (1 - tb);
            break;
          case 'add':
            data[i] = Math.min(255, r + tr);
            data[i + 1] = Math.min(255, g + tg);
            data[i + 2] = Math.min(255, b + tb);
            break;
          case 'diff':
          case 'difference':
            data[i] = Math.abs(r - tr);
            data[i + 1] = Math.abs(g - tg);
            data[i + 2] = Math.abs(b - tb);
            break;
          case 'subtract':
            _r = r - tr;
            _g = g - tg;
            _b = b - tb;

            data[i] = (_r < 0) ? 0 : _r;
            data[i + 1] = (_g < 0) ? 0 : _g;
            data[i + 2] = (_b < 0) ? 0 : _b;
            break;
          case 'darken':
            data[i] = Math.min(r, tr);
            data[i + 1] = Math.min(g, tg);
            data[i + 2] = Math.min(b, tb);
            break;
          case 'lighten':
            data[i] = Math.max(r, tr);
            data[i + 1] = Math.max(g, tg);
            data[i + 2] = Math.max(b, tb);
            break;
        }
      }

      context.putImageData(imageData, 0, 0);
    },

    /**
     * Returns object representation of an instance
     * @return {Object} Object representation of an instance
     */
    toObject: function () {
      return {
        color: this.color,
        image: this.image,
        mode: this.mode,
        alpha: this.alpha
      };
    }
  });

  fabric.Image.filters.Blend.fromObject = function (object) {
    return new fabric.Image.filters.Blend(object);
  };
})(typeof exports !== 'undefined' ? exports : this);


(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}), pow = Math.pow, floor = Math.floor,
    sqrt = Math.sqrt, abs = Math.abs, max = Math.max, round = Math.round, sin = Math.sin,
    ceil = Math.ceil,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * Resize image filter class
   * @class fabric.Image.filters.Resize
   * @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.Resize();
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  filters.Resize = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ {

    /**
     * Filter type
     * @param {String} type
     * @default
     */
    type: 'Resize',

    /**
     * Resize type
     * @param {String} resizeType
     * @default
     */
    resizeType: 'hermite',

    /**
     * Scale factor for resizing, x axis
     * @param {Number} scaleX
     * @default
     */
    scaleX: 0,

    /**
     * Scale factor for resizing, y axis
     * @param {Number} scaleY
     * @default
     */
    scaleY: 0,

    /**
     * LanczosLobes parameter for lanczos filter
     * @param {Number} lanczosLobes
     * @default
     */
    lanczosLobes: 3,

    /**
     * Applies filter to canvas element
     * @memberOf fabric.Image.filters.Resize.prototype
     * @param {Object} canvasEl Canvas element to apply filter to
     * @param {Number} scaleX
     * @param {Number} scaleY
     */
    applyTo: function (canvasEl, scaleX, scaleY) {
      if (scaleX === 1 && scaleY === 1) {
        return;
      }

      this.rcpScaleX = 1 / scaleX;
      this.rcpScaleY = 1 / scaleY;

      var oW = canvasEl.width, oH = canvasEl.height,
        dW = round(oW * scaleX), dH = round(oH * scaleY),
        imageData;

      if (this.resizeType === 'sliceHack') {
        imageData = this.sliceByTwo(canvasEl, oW, oH, dW, dH);
      }
      if (this.resizeType === 'hermite') {
        imageData = this.hermiteFastResize(canvasEl, oW, oH, dW, dH);
      }
      if (this.resizeType === 'bilinear') {
        imageData = this.bilinearFiltering(canvasEl, oW, oH, dW, dH);
      }
      if (this.resizeType === 'lanczos') {
        imageData = this.lanczosResize(canvasEl, oW, oH, dW, dH);
      }
      canvasEl.width = dW;
      canvasEl.height = dH;
      canvasEl.getContext('2d').putImageData(imageData, 0, 0);
    },

    /**
     * Filter sliceByTwo
     * @param {Object} canvasEl Canvas element to apply filter to
     * @param {Number} oW Original Width
     * @param {Number} oH Original Height
     * @param {Number} dW Destination Width
     * @param {Number} dH Destination Height
     * @returns {ImageData}
     */
    sliceByTwo: function (canvasEl, oW, oH, dW, dH) {
      var context = canvasEl.getContext('2d'), imageData,
        multW = 0.5, multH = 0.5, signW = 1, signH = 1,
        doneW = false, doneH = false, stepW = oW, stepH = oH,
        tmpCanvas = fabric.util.createCanvasElement(),
        tmpCtx = tmpCanvas.getContext('2d');
      dW = floor(dW);
      dH = floor(dH);
      tmpCanvas.width = max(dW, oW);
      tmpCanvas.height = max(dH, oH);

      if (dW > oW) {
        multW = 2;
        signW = -1;
      }
      if (dH > oH) {
        multH = 2;
        signH = -1;
      }
      imageData = context.getImageData(0, 0, oW, oH);
      canvasEl.width = max(dW, oW);
      canvasEl.height = max(dH, oH);
      context.putImageData(imageData, 0, 0);

      while (!doneW || !doneH) {
        oW = stepW;
        oH = stepH;
        if (dW * signW < floor(stepW * multW * signW)) {
          stepW = floor(stepW * multW);
        }
        else {
          stepW = dW;
          doneW = true;
        }
        if (dH * signH < floor(stepH * multH * signH)) {
          stepH = floor(stepH * multH);
        }
        else {
          stepH = dH;
          doneH = true;
        }
        imageData = context.getImageData(0, 0, oW, oH);
        tmpCtx.putImageData(imageData, 0, 0);
        context.clearRect(0, 0, stepW, stepH);
        context.drawImage(tmpCanvas, 0, 0, oW, oH, 0, 0, stepW, stepH);
      }
      return context.getImageData(0, 0, dW, dH);
    },

    /**
     * Filter lanczosResize
     * @param {Object} canvasEl Canvas element to apply filter to
     * @param {Number} oW Original Width
     * @param {Number} oH Original Height
     * @param {Number} dW Destination Width
     * @param {Number} dH Destination Height
     * @returns {ImageData}
     */
    lanczosResize: function (canvasEl, oW, oH, dW, dH) {

      function lanczosCreate(lobes) {
        return function (x) {
          if (x > lobes) {
            return 0;
          }
          x *= Math.PI;
          if (abs(x) < 1e-16) {
            return 1;
          }
          var xx = x / lobes;
          return sin(x) * sin(xx) / x / xx;
        };
      }

      function process(u) {
        var v, i, weight, idx, a, red, green,
          blue, alpha, fX, fY;
        center.x = (u + 0.5) * ratioX;
        icenter.x = floor(center.x);
        for (v = 0; v < dH; v++) {
          center.y = (v + 0.5) * ratioY;
          icenter.y = floor(center.y);
          a = 0; red = 0; green = 0; blue = 0; alpha = 0;
          for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) {
            if (i < 0 || i >= oW) {
              continue;
            }
            fX = floor(1000 * abs(i - center.x));
            if (!cacheLanc[fX]) {
              cacheLanc[fX] = {};
            }
            for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) {
              if (j < 0 || j >= oH) {
                continue;
              }
              fY = floor(1000 * abs(j - center.y));
              if (!cacheLanc[fX][fY]) {
                cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000);
              }
              weight = cacheLanc[fX][fY];
              if (weight > 0) {
                idx = (j * oW + i) * 4;
                a += weight;
                red += weight * srcData[idx];
                green += weight * srcData[idx + 1];
                blue += weight * srcData[idx + 2];
                alpha += weight * srcData[idx + 3];
              }
            }
          }
          idx = (v * dW + u) * 4;
          destData[idx] = red / a;
          destData[idx + 1] = green / a;
          destData[idx + 2] = blue / a;
          destData[idx + 3] = alpha / a;
        }

        if (++u < dW) {
          return process(u);
        }
        else {
          return destImg;
        }
      }

      var context = canvasEl.getContext('2d'),
        srcImg = context.getImageData(0, 0, oW, oH),
        destImg = context.getImageData(0, 0, dW, dH),
        srcData = srcImg.data, destData = destImg.data,
        lanczos = lanczosCreate(this.lanczosLobes),
        ratioX = this.rcpScaleX, ratioY = this.rcpScaleY,
        rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY,
        range2X = ceil(ratioX * this.lanczosLobes / 2),
        range2Y = ceil(ratioY * this.lanczosLobes / 2),
        cacheLanc = {}, center = {}, icenter = {};

      return process(0);
    },

    /**
     * bilinearFiltering
     * @param {Object} canvasEl Canvas element to apply filter to
     * @param {Number} oW Original Width
     * @param {Number} oH Original Height
     * @param {Number} dW Destination Width
     * @param {Number} dH Destination Height
     * @returns {ImageData}
     */
    bilinearFiltering: function (canvasEl, oW, oH, dW, dH) {
      var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl,
        color, offset = 0, origPix, ratioX = this.rcpScaleX,
        ratioY = this.rcpScaleY, context = canvasEl.getContext('2d'),
        w4 = 4 * (oW - 1), img = context.getImageData(0, 0, oW, oH),
        pixels = img.data, destImage = context.getImageData(0, 0, dW, dH),
        destPixels = destImage.data;
      for (i = 0; i < dH; i++) {
        for (j = 0; j < dW; j++) {
          x = floor(ratioX * j);
          y = floor(ratioY * i);
          xDiff = ratioX * j - x;
          yDiff = ratioY * i - y;
          origPix = 4 * (y * oW + x);

          for (chnl = 0; chnl < 4; chnl++) {
            a = pixels[origPix + chnl];
            b = pixels[origPix + 4 + chnl];
            c = pixels[origPix + w4 + chnl];
            d = pixels[origPix + w4 + 4 + chnl];
            color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) +
              c * yDiff * (1 - xDiff) + d * xDiff * yDiff;
            destPixels[offset++] = color;
          }
        }
      }
      return destImage;
    },

    /**
     * hermiteFastResize
     * @param {Object} canvasEl Canvas element to apply filter to
     * @param {Number} oW Original Width
     * @param {Number} oH Original Height
     * @param {Number} dW Destination Width
     * @param {Number} dH Destination Height
     * @returns {ImageData}
     */
    hermiteFastResize: function (canvasEl, oW, oH, dW, dH) {
      var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY,
        ratioWHalf = ceil(ratioW / 2),
        ratioHHalf = ceil(ratioH / 2),
        context = canvasEl.getContext('2d'),
        img = context.getImageData(0, 0, oW, oH), data = img.data,
        img2 = context.getImageData(0, 0, dW, dH), data2 = img2.data;
      for (var j = 0; j < dH; j++) {
        for (var i = 0; i < dW; i++) {
          var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0,
            gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH;
          for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) {
            var dy = abs(centerY - (yy + 0.5)) / ratioHHalf,
              centerX = (i + 0.5) * ratioW, w0 = dy * dy;
            for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) {
              var dx = abs(centerX - (xx + 0.5)) / ratioWHalf,
                w = sqrt(w0 + dx * dx);
              /* eslint-disable max-depth */
              if (w > 1 && w < -1) {
                continue;
              }
              //hermite filter
              weight = 2 * w * w * w - 3 * w * w + 1;
              if (weight > 0) {
                dx = 4 * (xx + yy * oW);
                //alpha
                gxA += weight * data[dx + 3];
                weightsAlpha += weight;
                //colors
                if (data[dx + 3] < 255) {
                  weight = weight * data[dx + 3] / 250;
                }
                gxR += weight * data[dx];
                gxG += weight * data[dx + 1];
                gxB += weight * data[dx + 2];
                weights += weight;
              }
              /* eslint-enable max-depth */
            }
          }
          data2[x2] = gxR / weights;
          data2[x2 + 1] = gxG / weights;
          data2[x2 + 2] = gxB / weights;
          data2[x2 + 3] = gxA / weightsAlpha;
        }
      }
      return img2;
    },

    /**
     * Returns object representation of an instance
     * @return {Object} Object representation of an instance
     */
    toObject: function () {
      return {
        type: this.type,
        scaleX: this.scaleX,
        scaleY: this.scaleY,
        resizeType: this.resizeType,
        lanczosLobes: this.lanczosLobes
      };
    }
  });

  /**
   * Returns filter instance from an object representation
   * @static
   * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize
   */
  fabric.Image.filters.Resize.fromObject = function (object) {
    return new fabric.Image.filters.Resize(object);
  };

})(typeof exports !== 'undefined' ? exports : this);


(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * Color Matrix filter class
   * @class fabric.Image.filters.ColorMatrix
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.ColorMatrix#initialize} for constructor definition
   * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
   * @see {@Link http://www.webwasp.co.uk/tutorials/219/Color_Matrix_Filter.php}
   * @see {@Link http://phoboslab.org/log/2013/11/fast-image-filters-with-webgl}
   * @example <caption>Kodachrome filter</caption>
   * var filter = new fabric.Image.filters.ColorMatrix({
   *  matrix: [
       1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502,
       -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203,
       -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946,
       0, 0, 0, 1, 0
      ]
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  filters.ColorMatrix = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.ColorMatrix.prototype */ {

    /**
     * Filter type
     * @param {String} type
     * @default
     */
    type: 'ColorMatrix',

    /**
     * Constructor
     * @memberOf fabric.Image.filters.ColorMatrix.prototype
     * @param {Object} [options] Options object
     * @param {Array} [options.matrix] Color Matrix to modify the image data with
     */
    initialize: function (options) {
      options || (options = {});
      this.matrix = options.matrix || [
        1, 0, 0, 0, 0,
        0, 1, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 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,
        iLen = data.length,
        i,
        r,
        g,
        b,
        a,
        m = this.matrix;

      for (i = 0; i < iLen; i += 4) {
        r = data[i];
        g = data[i + 1];
        b = data[i + 2];
        a = data[i + 3];

        data[i] = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4];
        data[i + 1] = r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9];
        data[i + 2] = r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14];
        data[i + 3] = r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19];
      }

      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'), {
        type: this.type,
        matrix: this.matrix
      });
    }
  });

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.ColorMatrix} Instance of fabric.Image.filters.ColorMatrix
   */
  fabric.Image.filters.ColorMatrix.fromObject = function (object) {
    return new fabric.Image.filters.ColorMatrix(object);
  };

})(typeof exports !== 'undefined' ? exports : this);


(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * Contrast filter class
   * @class fabric.Image.filters.Contrast
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.Contrast#initialize} for constructor definition
   * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.Contrast({
   *   contrast: 40
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  filters.Contrast = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Contrast.prototype */ {

    /**
     * Filter type
     * @param {String} type
     * @default
     */
    type: 'Contrast',

    /**
     * Constructor
     * @memberOf fabric.Image.filters.Contrast.prototype
     * @param {Object} [options] Options object
     * @param {Number} [options.contrast=0] Value to contrast the image up (-255...255)
     */
    initialize: function (options) {
      options = options || {};
      this.contrast = options.contrast || 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,
        contrastF = 259 * (this.contrast + 255) / (255 * (259 - this.contrast));

      for (var i = 0, len = data.length; i < len; i += 4) {
        data[i] = contrastF * (data[i] - 128) + 128;
        data[i + 1] = contrastF * (data[i + 1] - 128) + 128;
        data[i + 2] = contrastF * (data[i + 2] - 128) + 128;
      }

      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'), {
        contrast: this.contrast
      });
    }
  });

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.Contrast} Instance of fabric.Image.filters.Contrast
   */
  fabric.Image.filters.Contrast.fromObject = function (object) {
    return new fabric.Image.filters.Contrast(object);
  };

})(typeof exports !== 'undefined' ? exports : this);


(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend,
    filters = fabric.Image.filters,
    createClass = fabric.util.createClass;

  /**
   * Saturate filter class
   * @class fabric.Image.filters.Saturate
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.Saturate#initialize} for constructor definition
   * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
   * @example
   * var filter = new fabric.Image.filters.Saturate({
   *   saturate: 100
   * });
   * object.filters.push(filter);
   * object.applyFilters(canvas.renderAll.bind(canvas));
   */
  filters.Saturate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Saturate.prototype */ {

    /**
     * Filter type
     * @param {String} type
     * @default
     */
    type: 'Saturate',

    /**
     * Constructor
     * @memberOf fabric.Image.filters.Saturate.prototype
     * @param {Object} [options] Options object
     * @param {Number} [options.saturate=0] Value to saturate the image (-100...100)
     */
    initialize: function (options) {
      options = options || {};
      this.saturate = options.saturate || 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,
        max, adjust = -this.saturate * 0.01;

      for (var i = 0, len = data.length; i < len; i += 4) {
        max = Math.max(data[i], data[i + 1], data[i + 2]);
        data[i] += max !== data[i] ? (max - data[i]) * adjust : 0;
        data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * adjust : 0;
        data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * adjust : 0;
      }

      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'), {
        saturate: this.saturate
      });
    }
  });

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @return {fabric.Image.filters.Saturate} Instance of fabric.Image.filters.Saturate
   */
  fabric.Image.filters.Saturate.fromObject = function (object) {
    return new fabric.Image.filters.Saturate(object);
  };

})(typeof exports !== 'undefined' ? exports : this);


(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    clone = fabric.util.object.clone,
    toFixed = fabric.util.toFixed,
    NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
    MIN_TEXT_WIDTH = 2;

  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',
    'charSpacing'
  );

  var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
  cacheProperties.push(
    'fontFamily',
    'fontWeight',
    'fontSize',
    'text',
    'textDecoration',
    'textAlign',
    'fontStyle',
    'lineHeight',
    'textBackgroundColor',
    'charSpacing',
    'styles'
  );
  /**
   * 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',
      'fontWeight',
      'fontFamily',
      'fontStyle',
      'lineHeight',
      'text',
      'charSpacing',
      'textAlign'
    ],

    /**
     * @private
     */
    _reNewline: /\r?\n/,

    /**
     * Use this regular expression to filter for whitespace that is not a new line.
     * Mostly used when text is 'justify' aligned.
     * @private
     */
    _reSpacesAndTabs: /[ \t\r]+/g,

    /**
     * Retrieves object's fontSize
     * @method getFontSize
     * @memberOf fabric.Text.prototype
     * @return {String} Font size (in pixels)
     */

    /**
     * Sets object's fontSize
     * Does not update the object .width and .height,
     * call ._initDimensions() to update the values.
     * @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
     * Does not update the object .width and .height,
     * call ._initDimensions() to update the values.
     * @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
     * Does not update the object .width and .height,
     * call ._initDimensions() to update the values.
     * @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
     * Does not update the object .width and .height,
     * call ._initDimensions() to update the values.
     * @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
     * Does not update the object .width and .height,
     * call ._initDimensions() to update the values.
     * @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", "right" or "justify".
     * @type String
     * @default
     */
    textAlign: 'left',

    /**
     * Font style . Possible values: "", "normal", "italic" or "oblique".
     * @type String
     * @default
     */
    fontStyle: '',

    /**
     * Line height
     * @type Number
     * @default
     */
    lineHeight: 1.16,

    /**
     * Background color of text lines
     * @type String
     * @default
     */
    textBackgroundColor: '',

    /**
     * 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,

    /**
     * List of properties to consider when checking if cache needs refresh
     * @type Array
     */
    cacheProperties: cacheProperties,

    /**
     * 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,

    /**
     * @private
     */
    _fontSizeFraction: 0.25,

    /**
     * Text Line proportion to font Size (in pixels)
     * @type Number
     * @default
     */
    _fontSizeMult: 1.13,

    /**
     * additional space between characters
     * expressed in thousands of em unit
     * @type Number
     * @default
     */
    charSpacing: 0,

    /**
     * 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.callSuper('initialize', options);
      this.__skipDimension = false;
      this._initDimensions();
      this.setupState({ propertySet: '_dimensionAffectingProps' });
    },

    /**
     * Initialize text dimensions. Render all text on given context
     * or on a offscreen canvas to get the text width with measureText.
     * Updates this.width and this.height with the proper values.
     * Does not return dimensions.
     * @param {CanvasRenderingContext2D} [ctx] Context to render on
     * @private
     */
    _initDimensions: function (ctx) {
      if (this.__skipDimension) {
        return;
      }
      if (!ctx) {
        ctx = fabric.util.createCanvasElement().getContext('2d');
        this._setTextStyles(ctx);
      }
      this._textLines = this._splitTextIntoLines();
      this._clearCache();
      this.width = this._getTextWidth(ctx) || this.cursorWidth || MIN_TEXT_WIDTH;
      this.height = this._getTextHeight(ctx);
    },

    /**
     * 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) {
      this._setTextStyles(ctx);
      if (this.group && this.group.type === 'path-group') {
        ctx.translate(this.left, this.top);
      }
      this._renderTextLinesBackground(ctx);
      this._renderText(ctx);
      this._renderTextDecoration(ctx);
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderText: function (ctx) {
      this._renderTextFill(ctx);
      this._renderTextStroke(ctx);
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _setTextStyles: function (ctx) {
      ctx.textBaseline = 'alphabetic';
      ctx.font = this._getFontDeclaration();
    },

    /**
     * @private
     * @return {Number} Height of fabric.Text object
     */
    _getTextHeight: function () {
      return this._getHeightOfSingleLine() + (this._textLines.length - 1) * this._getHeightOfLine();
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @return {Number} Maximum width of fabric.Text object
     */
    _getTextWidth: function (ctx) {
      var maxWidth = this._getLineWidth(ctx, 0);

      for (var i = 1, len = this._textLines.length; i < len; i++) {
        var currentLineWidth = this._getLineWidth(ctx, i);
        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) {
      // remove Text word from method var
      var shortM = method.slice(0, -4), char, width;
      if (this[shortM].toLive) {
        var offsetX = -this.width / 2 + this[shortM].offsetX || 0,
          offsetY = -this.height / 2 + this[shortM].offsetY || 0;
        ctx.save();
        ctx.translate(offsetX, offsetY);
        left -= offsetX;
        top -= offsetY;
      }
      if (this.charSpacing !== 0) {
        var additionalSpace = this._getWidthOfCharSpacing();
        chars = chars.split('');
        for (var i = 0, len = chars.length; i < len; i++) {
          char = chars[i];
          width = ctx.measureText(char).width + additionalSpace;
          ctx[method](char, left, top);
          left += width > 0 ? width : 0;
        }
      }
      else {
        ctx[method](chars, left, top);
      }
      this[shortM].toLive && ctx.restore();
    },

    /**
     * @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 * this._fontSizeFraction;

      // short-circuit
      var lineWidth = this._getLineWidth(ctx, lineIndex);
      if (this.textAlign !== 'justify' || this.width < lineWidth) {
        this._renderChars(method, ctx, line, left, top, lineIndex);
        return;
      }

      // stretch the line
      var words = line.split(/\s+/),
        charOffset = 0,
        wordsWidth = this._getWidthOfWords(ctx, words.join(' '), lineIndex, 0),
        widthDiff = this.width - wordsWidth,
        numSpaces = words.length - 1,
        spaceWidth = numSpaces > 0 ? widthDiff / numSpaces : 0,
        leftOffset = 0, word;

      for (var i = 0, len = words.length; i < len; i++) {
        while (line[charOffset] === ' ' && charOffset < line.length) {
          charOffset++;
        }
        word = words[i];
        this._renderChars(method, ctx, word, left + leftOffset, top, lineIndex, charOffset);
        leftOffset += this._getWidthOfWords(ctx, word, lineIndex, charOffset) + spaceWidth;
        charOffset += word.length;
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {String} word
     */
    _getWidthOfWords: function (ctx, word) {
      var width = ctx.measureText(word).width, charCount, additionalSpace;
      if (this.charSpacing !== 0) {
        charCount = word.split('').length;
        additionalSpace = charCount * this._getWidthOfCharSpacing();
        width += additionalSpace;
      }
      return width > 0 ? width : 0;
    },

    /**
     * @private
     * @return {Number} Left offset
     */
    _getLeftOffset: function () {
      return -this.width / 2;
    },

    /**
     * @private
     * @return {Number} Top offset
     */
    _getTopOffset: function () {
      return -this.height / 2;
    },

    /**
     * Returns true because text has no style
     */
    isEmptyStyles: function () {
      return true;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {String} method Method name ("fillText" or "strokeText")
     */
    _renderTextCommon: function (ctx, method) {

      var lineHeights = 0, left = this._getLeftOffset(), top = this._getTopOffset();

      for (var i = 0, len = this._textLines.length; i < len; i++) {
        var heightOfLine = this._getHeightOfLine(ctx, i),
          maxHeight = heightOfLine / this.lineHeight,
          lineWidth = this._getLineWidth(ctx, i),
          leftOffset = this._getLineLeftOffset(lineWidth);
        this._renderTextLine(
          method,
          ctx,
          this._textLines[i],
          left + leftOffset,
          top + lineHeights + maxHeight,
          i
        );
        lineHeights += heightOfLine;
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderTextFill: function (ctx) {
      if (!this.fill && this.isEmptyStyles()) {
        return;
      }

      this._renderTextCommon(ctx, 'fillText');
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderTextStroke: function (ctx) {
      if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) {
        return;
      }

      if (this.shadow && !this.shadow.affectStroke) {
        this._removeShadow(ctx);
      }

      ctx.save();
      this._setLineDash(ctx, this.strokeDashArray);
      ctx.beginPath();
      this._renderTextCommon(ctx, 'strokeText');
      ctx.closePath();
      ctx.restore();
    },

    /**
     * @private
     * @return {Number} height of line
     */
    _getHeightOfLine: function () {
      return this._getHeightOfSingleLine() * this.lineHeight;
    },

    /**
     * @private
     * @return {Number} height of line without lineHeight
     */
    _getHeightOfSingleLine: function () {
      return this.fontSize * this._fontSizeMult;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderTextLinesBackground: function (ctx) {
      if (!this.textBackgroundColor) {
        return;
      }
      var lineTopOffset = 0, heightOfLine,
        lineWidth, lineLeftOffset, originalFill = ctx.fillStyle;

      ctx.fillStyle = this.textBackgroundColor;
      for (var i = 0, len = this._textLines.length; i < len; i++) {
        heightOfLine = this._getHeightOfLine(ctx, i);
        lineWidth = this._getLineWidth(ctx, i);
        if (lineWidth > 0) {
          lineLeftOffset = this._getLineLeftOffset(lineWidth);
          ctx.fillRect(
            this._getLeftOffset() + lineLeftOffset,
            this._getTopOffset() + lineTopOffset,
            lineWidth,
            heightOfLine / this.lineHeight
          );
        }
        lineTopOffset += heightOfLine;
      }
      ctx.fillStyle = originalFill;
      // if there is text background color no
      // other shadows should be casted
      this._removeShadow(ctx);
    },

    /**
     * @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
     */
    _clearCache: function () {
      this.__lineWidths = [];
      this.__lineHeights = [];
    },

    /**
     * @private
     */
    _shouldClearDimensionCache: function () {
      var shouldClear = false;
      if (this._forceClearCache) {
        this._forceClearCache = false;
        this.dirty = true;
        return true;
      }
      shouldClear = this.hasStateChanged('_dimensionAffectingProps');
      if (shouldClear) {
        this.saveState({ propertySet: '_dimensionAffectingProps' });
        this.dirty = true;
      }
      return shouldClear;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Number} lineIndex line number
     * @return {Number} Line width
     */
    _getLineWidth: function (ctx, lineIndex) {
      if (this.__lineWidths[lineIndex]) {
        return this.__lineWidths[lineIndex] === -1 ? this.width : this.__lineWidths[lineIndex];
      }

      var width, wordCount, line = this._textLines[lineIndex];

      if (line === '') {
        width = 0;
      }
      else {
        width = this._measureLine(ctx, lineIndex);
      }
      this.__lineWidths[lineIndex] = width;

      if (width && this.textAlign === 'justify') {
        wordCount = line.split(/\s+/);
        if (wordCount.length > 1) {
          this.__lineWidths[lineIndex] = -1;
        }
      }
      return width;
    },

    _getWidthOfCharSpacing: function () {
      if (this.charSpacing !== 0) {
        return this.fontSize * this.charSpacing / 1000;
      }
      return 0;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Number} lineIndex line number
     * @return {Number} Line width
     */
    _measureLine: function (ctx, lineIndex) {
      var line = this._textLines[lineIndex],
        width = ctx.measureText(line).width,
        additionalSpace = 0, charCount, finalWidth;
      if (this.charSpacing !== 0) {
        charCount = line.split('').length;
        additionalSpace = (charCount - 1) * this._getWidthOfCharSpacing();
      }
      finalWidth = width + additionalSpace;
      return finalWidth > 0 ? finalWidth : 0;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderTextDecoration: function (ctx) {
      if (!this.textDecoration) {
        return;
      }
      var halfOfVerticalBox = this.height / 2,
        _this = this, offsets = [];

      /** @ignore */
      function renderLinesAtOffset(offsets) {
        var i, lineHeight = 0, len, j, oLen, lineWidth,
          lineLeftOffset, heightOfLine;

        for (i = 0, len = _this._textLines.length; i < len; i++) {

          lineWidth = _this._getLineWidth(ctx, i);
          lineLeftOffset = _this._getLineLeftOffset(lineWidth);
          heightOfLine = _this._getHeightOfLine(ctx, i);

          for (j = 0, oLen = offsets.length; j < oLen; j++) {
            ctx.fillRect(
              _this._getLeftOffset() + lineLeftOffset,
              lineHeight + (_this._fontSizeMult - 1 + offsets[j]) * _this.fontSize - halfOfVerticalBox,
              lineWidth,
              _this.fontSize / 15);
          }
          lineHeight += heightOfLine;
        }
      }

      if (this.textDecoration.indexOf('underline') > -1) {
        offsets.push(0.85); // 1 - 3/16
      }
      if (this.textDecoration.indexOf('line-through') > -1) {
        offsets.push(0.43);
      }
      if (this.textDecoration.indexOf('overline') > -1) {
        offsets.push(-0.12);
      }
      if (offsets.length > 0) {
        renderLinesAtOffset(offsets);
      }
    },

    /**
     * return font declaration string for canvas context
     * @returns {String} font declaration formatted for canvas context.
     */
    _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
     * @param {Boolean} noTransform
     */
    render: function (ctx, noTransform) {
      // do not render if object is not visible
      if (!this.visible) {
        return;
      }
      if (this._shouldClearDimensionCache()) {
        this._setTextStyles(ctx);
        this._initDimensions(ctx);
      }
      this.callSuper('render', ctx, noTransform);
    },

    /**
     * Returns the text as an array of lines.
     * @returns {Array} Lines in the text
     */
    _splitTextIntoLines: function () {
      return this.text.split(this._reNewline);
    },

    /**
     * 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 additionalProperties = [
        'text',
        'fontSize',
        'fontWeight',
        'fontFamily',
        'fontStyle',
        'lineHeight',
        'textDecoration',
        'textAlign',
        'textBackgroundColor',
        'charSpacing'
      ].concat(propertiesToInclude);
      return this.callSuper('toObject', additionalProperties);
    },

    /* _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) {
      if (!this.ctx) {
        this.ctx = fabric.util.createCanvasElement().getContext('2d');
      }
      var markup = this._createBaseSVGMarkup(),
        offsets = this._getSVGLeftTopOffsets(this.ctx),
        textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);
      this._wrapSVGTextAndBg(markup, textAndBg);

      return reviver ? reviver(markup.join('')) : markup.join('');
    },

    /**
     * @private
     */
    _getSVGLeftTopOffsets: function (ctx) {
      var lineTop = this._getHeightOfLine(ctx, 0),
        textLeft = -this.width / 2,
        textTop = 0;

      return {
        textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0),
        textTop: textTop + (this.group && this.group.type === 'path-group' ? -this.top : 0),
        lineTop: lineTop
      };
    },

    /**
     * @private
     */
    _wrapSVGTextAndBg: function (markup, textAndBg) {
      var noShadow = true, filter = this.getSvgFilter(),
        style = filter === '' ? '' : ' style="' + filter + '"';

      markup.push(
        '\t<g ', this.getSvgId(), 'transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '"',
        style, '>\n',
        textAndBg.textBgRects.join(''),
        '\t\t<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(noShadow), '" >\n',
        textAndBg.textSpans.join(''),
        '\t\t</text>\n',
        '\t</g>\n'
      );
    },

    /**
     * @private
     * @param {Number} textTopOffset Text top offset
     * @param {Number} textLeftOffset Text left offset
     * @return {Object}
     */
    _getSVGTextAndBg: function (textTopOffset, textLeftOffset) {
      var textSpans = [],
        textBgRects = [],
        height = 0;
      // bounding-box background
      this._setSVGBg(textBgRects);

      // text and text-background
      for (var i = 0, len = this._textLines.length; i < len; i++) {
        if (this.textBackgroundColor) {
          this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height);
        }
        this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects);
        height += this._getHeightOfLine(this.ctx, i);
      }

      return {
        textSpans: textSpans,
        textBgRects: textBgRects
      };
    },

    _setSVGTextLineText: function (i, textSpans, height, textLeftOffset, textTopOffset) {
      var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction)
        - textTopOffset + height - this.height / 2;
      if (this.textAlign === 'justify') {
        // i call from here to do not intefere with IText
        this._setSVGTextLineJustifed(i, textSpans, yPos, textLeftOffset);
        return;
      }
      textSpans.push(
        '\t\t\t<tspan x="',
        toFixed(textLeftOffset + this._getLineLeftOffset(this._getLineWidth(this.ctx, i)), NUM_FRACTION_DIGITS), '" ',
        'y="',
        toFixed(yPos, NUM_FRACTION_DIGITS),
        '" ',
        // 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(this._textLines[i]),
        '</tspan>\n'
      );
    },

    _setSVGTextLineJustifed: function (i, textSpans, yPos, textLeftOffset) {
      var ctx = fabric.util.createCanvasElement().getContext('2d');

      this._setTextStyles(ctx);

      var line = this._textLines[i],
        words = line.split(/\s+/),
        wordsWidth = this._getWidthOfWords(ctx, words.join('')),
        widthDiff = this.width - wordsWidth,
        numSpaces = words.length - 1,
        spaceWidth = numSpaces > 0 ? widthDiff / numSpaces : 0,
        word, attributes = this._getFillAttributes(this.fill),
        len;

      textLeftOffset += this._getLineLeftOffset(this._getLineWidth(ctx, i));

      for (i = 0, len = words.length; i < len; i++) {
        word = words[i];
        textSpans.push(
          '\t\t\t<tspan x="',
          toFixed(textLeftOffset, NUM_FRACTION_DIGITS), '" ',
          'y="',
          toFixed(yPos, NUM_FRACTION_DIGITS),
          '" ',
          // doing this on <tspan> elements since setting opacity
          // on containing <text> one doesn't work in Illustrator
          attributes, '>',
          fabric.util.string.escapeXml(word),
          '</tspan>\n'
        );
        textLeftOffset += this._getWidthOfWords(ctx, word) + spaceWidth;
      }
    },

    _setSVGTextLineBg: function (textBgRects, i, textLeftOffset, textTopOffset, height) {
      textBgRects.push(
        '\t\t<rect ',
        this._getFillAttributes(this.textBackgroundColor),
        ' x="',
        toFixed(textLeftOffset + this._getLineLeftOffset(this._getLineWidth(this.ctx, i)), NUM_FRACTION_DIGITS),
        '" y="',
        toFixed(height - this.height / 2, NUM_FRACTION_DIGITS),
        '" width="',
        toFixed(this._getLineWidth(this.ctx, i), NUM_FRACTION_DIGITS),
        '" height="',
        toFixed(this._getHeightOfLine(this.ctx, i) / this.lineHeight, NUM_FRACTION_DIGITS),
        '"></rect>\n');
    },

    _setSVGBg: function (textBgRects) {
      if (this.backgroundColor) {
        textBgRects.push(
          '\t\t<rect ',
          this._getFillAttributes(this.backgroundColor),
          ' x="',
          toFixed(-this.width / 2, NUM_FRACTION_DIGITS),
          '" y="',
          toFixed(-this.height / 2, NUM_FRACTION_DIGITS),
          '" width="',
          toFixed(this.width, NUM_FRACTION_DIGITS),
          '" height="',
          toFixed(this.height, NUM_FRACTION_DIGITS),
          '"></rect>\n');
      }
    },

    /**
     * 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 {*} 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 {*} value
     * @return {fabric.Text} thisArg
     * @chainable
     */
    _set: function (key, value) {
      this.callSuper('_set', key, value);

      if (this._dimensionAffectingProps.indexOf(key) > -1) {
        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);

    options.top = options.top || 0;
    options.left = options.left || 0;
    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 textContent = '';

    // The XML is not properly parsed in IE9 so a workaround to get
    // textContent is through firstChild.data. Another workaround would be
    // to convert XML loaded from a file to be converted using DOMParser (same way loadSVGFromString() does)
    if (!('textContent' in element)) {
      if ('firstChild' in element && element.firstChild !== null) {
        if ('data' in element.firstChild && element.firstChild.data !== null) {
          textContent = element.firstChild.data;
        }
      }
    }
    else {
      textContent = element.textContent;
    }

    textContent = textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' ');

    var text = new fabric.Text(textContent, options),
      textHeightScaleFactor = text.getHeight() / text.height,
      lineHeightDiff = (text.height + text.strokeWidth) * text.lineHeight - text.height,
      scaledDiff = lineHeightDiff * textHeightScaleFactor,
      textHeight = text.getHeight() + scaledDiff,
      offX = 0;
    /*
      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
    */
    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() - textHeight / 2 + text.fontSize * (0.18 + text._fontSizeFraction) / text.lineHeight /* 0.3 is the old lineHeight */
    });

    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
   * @param {Function} [callback] Callback to invoke when an fabric.Text instance is created
   * @return {fabric.Text} Instance of fabric.Text
   */
  fabric.Text.fromObject = function (object, callback) {
    var text = new fabric.Text(object.text, clone(object));
    callback && callback(text);
    return text;
  };

  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 or home, end
   *   Select till start/end of line:  cmd + shift + left, cmd + shift + right or shift + home, shift + end
   *   Jump to start/end of text:      cmd + up, cmd + down
   *   Select till start/end of text:  cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown
   *   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
   *   Quit editing                    tab or esc
   * </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 Number
     * @default
     */
    selectionStart: 0,

    /**
     * Index where text selection ends
     * @type Number
     * @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
     */
    _reSpace: /\s|\n/,

    /**
     * @private
     */
    _currentCursorOpacity: 0,

    /**
     * @private
     */
    _selectionDirection: null,

    /**
     * @private
     */
    _abortCursorAnimation: false,

    /**
     * @private
     */
    __widthOfSpace: [],

    /**
     * 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();
    },

    /**
     * @private
     */
    _clearCache: function () {
      this.callSuper('_clearCache');
      this.__widthOfSpace = [];
    },

    /**
     * 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]) {
          // eslint-disable-next-line no-unused-vars
          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) {
      index = Math.max(index, 0);
      this._updateAndFire('selectionStart', index);
    },

    /**
     * Sets selection end (right boundary of a selection)
     * @param {Number} index Index to set selection end to
     */
    setSelectionEnd: function (index) {
      index = Math.min(index, this.text.length);
      this._updateAndFire('selectionEnd', index);
    },

    /**
     * @private
     * @param {String} property 'selectionStart' or 'selectionEnd'
     * @param {Number} index new position of property
     */
    _updateAndFire: function (property, index) {
      if (this[property] !== index) {
        this._fireSelectionChanged();
        this[property] = index;
      }
      this._updateTextarea();
    },

    /**
     * Fires the even of selection changed
     * @private
     */
    _fireSelectionChanged: function () {
      this.fire('selection:changed');
      this.canvas && this.canvas.fire('text:selection:changed', { target: this });
    },

    /**
     * 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),
        style = this._getStyleDeclaration(loc.lineIndex, loc.charIndex);

      return style || {};
    },

    /**
     * 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);
        }
      }
      /* not included in _extendStyles to avoid clearing cache more than once */
      this._forceClearCache = true;
      return this;
    },

    /**
     * @private
     */
    _extendStyles: function (index, styles) {
      var loc = this.get2DCursorLocation(index);

      if (!this._getLineStyle(loc.lineIndex)) {
        this._setLineStyle(loc.lineIndex, {});
      }

      if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) {
        this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {});
      }

      fabric.util.object.extend(this._getStyleDeclaration(loc.lineIndex, loc.charIndex), styles);
    },

    /**
     * Initialize text dimensions. Render all text on given context
     * or on a offscreen canvas to get the text width with measureText.
     * Updates this.width and this.height with the proper values.
     * Does not return dimensions.
     * @param {CanvasRenderingContext2D} [ctx] Context to render on
     * @private
     */
    _initDimensions: function (ctx) {
      if (!ctx) {
        this.clearContextTop();
      }
      this.callSuper('_initDimensions', ctx);
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Boolean} noTransform
     */
    render: function (ctx, noTransform) {
      this.clearContextTop();
      this.callSuper('render', ctx, noTransform);
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _render: function (ctx) {
      this.callSuper('_render', ctx);
      this.ctx = ctx;
      // clear the cursorOffsetCache, so we ensure to calculate once per renderCursor
      // the correct position but not at every cursor animation.
      this.cursorOffsetCache = {};
      this.renderCursorOrSelection();
    },

    /**
     * Prepare and clean the contextTop
     */
    clearContextTop: function () {
      if (!this.active || !this.isEditing) {
        return;
      }
      if (this.canvas && this.canvas.contextTop) {
        var ctx = this.canvas.contextTop;
        ctx.save();
        ctx.transform.apply(ctx, this.canvas.viewportTransform);
        this.transform(ctx);
        this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix);
        this._clearTextArea(ctx);
        ctx.restore();
      }
    },

    /**
     * Renders cursor or selection (depending on what exists)
     */
    renderCursorOrSelection: function () {
      if (!this.active || !this.isEditing) {
        return;
      }
      var chars = this.text.split(''),
        boundaries, ctx;
      if (this.canvas && this.canvas.contextTop) {
        ctx = this.canvas.contextTop;
        ctx.save();
        ctx.transform.apply(ctx, this.canvas.viewportTransform);
        this.transform(ctx);
        this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix);
        this._clearTextArea(ctx);
      }
      else {
        ctx = this.ctx;
        ctx.save();
      }
      if (this.selectionStart === this.selectionEnd) {
        boundaries = this._getCursorBoundaries(chars, 'cursor');
        this.renderCursor(boundaries, ctx);
      }
      else {
        boundaries = this._getCursorBoundaries(chars, 'selection');
        this.renderSelection(chars, boundaries, ctx);
      }
      ctx.restore();
    },

    _clearTextArea: function (ctx) {
      // we add 4 pixel, to be sure to do not leave any pixel out
      var width = this.width + 4, height = this.height + 4;
      ctx.clearRect(-width / 2, -height / 2, width, height);
    },
    /**
     * 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 len = this._textLines.length;
      for (var i = 0; i < len; i++) {
        if (selectionStart <= this._textLines[i].length) {
          return {
            lineIndex: i,
            charIndex: selectionStart
          };
        }
        selectionStart -= this._textLines[i].length + 1;
      }
      return {
        lineIndex: i - 1,
        charIndex: this._textLines[i - 1].length < selectionStart ? this._textLines[i - 1].length : selectionStart
      };
    },

    /**
     * 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._getStyleDeclaration(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) {
      var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
      return style && style.fontSize ? style.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) {
      var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
      return style && style.fill ? style.fill : this.cursorColor;
    },

    /**
     * Returns cursor boundaries (left, top, leftOffset, topOffset)
     * @private
     * @param {Array} chars Array of characters
     * @param {String} typeOfBoundaries
     */
    _getCursorBoundaries: function (chars, typeOfBoundaries) {

      // left/top are left/top of entire text box
      // leftOffset/topOffset are offset from that left/top point of a text box

      var left = Math.round(this._getLeftOffset()),
        top = this._getTopOffset(),

        offsets = this._getCursorBoundariesOffsets(
          chars, typeOfBoundaries);

      return {
        left: left,
        top: top,
        leftOffset: offsets.left + offsets.lineLeft,
        topOffset: offsets.top
      };
    },

    /**
     * @private
     */
    _getCursorBoundariesOffsets: function (chars, typeOfBoundaries) {
      if (this.cursorOffsetCache && 'top' in this.cursorOffsetCache) {
        return this.cursorOffsetCache;
      }
      var lineLeftOffset = 0,
        lineIndex = 0,
        charIndex = 0,
        topOffset = 0,
        leftOffset = 0,
        boundaries;

      for (var i = 0; i < this.selectionStart; i++) {
        if (chars[i] === '\n') {
          leftOffset = 0;
          topOffset += this._getHeightOfLine(this.ctx, lineIndex);

          lineIndex++;
          charIndex = 0;
        }
        else {
          leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex);
          charIndex++;
        }

        lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex));
      }
      if (typeOfBoundaries === 'cursor') {
        topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight
          - this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction);
      }
      if (this.charSpacing !== 0 && charIndex === this._textLines[lineIndex].length) {
        leftOffset -= this._getWidthOfCharSpacing();
      }
      boundaries = {
        top: topOffset,
        left: leftOffset > 0 ? leftOffset : 0,
        lineLeft: lineLeftOffset
      };
      this.cursorOffsetCache = boundaries;
      return this.cursorOffsetCache;
    },

    /**
     * Renders cursor
     * @param {Object} boundaries
     * @param {CanvasRenderingContext2D} ctx transformed context to draw on
     */
    renderCursor: function (boundaries, ctx) {

      var cursorLocation = this.get2DCursorLocation(),
        lineIndex = cursorLocation.lineIndex,
        charIndex = cursorLocation.charIndex,
        charHeight = this.getCurrentCharFontSize(lineIndex, charIndex),
        leftOffset = (lineIndex === 0 && charIndex === 0)
          ? this._getLineLeftOffset(this._getLineWidth(ctx, lineIndex))
          : boundaries.leftOffset,
        multiplier = this.scaleX * this.canvas.getZoom(),
        cursorWidth = this.cursorWidth / multiplier;

      ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex);
      ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity;

      ctx.fillRect(
        boundaries.left + leftOffset - cursorWidth / 2,
        boundaries.top + boundaries.topOffset,
        cursorWidth,
        charHeight);
    },

    /**
     * Renders text selection
     * @param {Array} chars Array of characters
     * @param {Object} boundaries Object with left/top/leftOffset/topOffset
     * @param {CanvasRenderingContext2D} ctx transformed context to draw on
     */
    renderSelection: function (chars, boundaries, ctx) {

      ctx.fillStyle = this.selectionColor;

      var start = this.get2DCursorLocation(this.selectionStart),
        end = this.get2DCursorLocation(this.selectionEnd),
        startLine = start.lineIndex,
        endLine = end.lineIndex;
      for (var i = startLine; i <= endLine; i++) {
        var lineOffset = this._getLineLeftOffset(this._getLineWidth(ctx, i)) || 0,
          lineHeight = this._getHeightOfLine(this.ctx, i),
          realLineHeight = 0, boxWidth = 0, line = this._textLines[i];

        if (i === startLine) {
          for (var j = 0, len = line.length; j < len; j++) {
            if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) {
              boxWidth += this._getWidthOfChar(ctx, line[j], i, j);
            }
            if (j < start.charIndex) {
              lineOffset += this._getWidthOfChar(ctx, line[j], i, j);
            }
          }
          if (j === line.length) {
            boxWidth -= this._getWidthOfCharSpacing();
          }
        }
        else if (i > startLine && i < endLine) {
          boxWidth += this._getLineWidth(ctx, i) || 5;
        }
        else if (i === endLine) {
          for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) {
            boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2);
          }
          if (end.charIndex === line.length) {
            boxWidth -= this._getWidthOfCharSpacing();
          }
        }
        realLineHeight = lineHeight;
        if (this.lineHeight < 1 || (i === endLine && this.lineHeight > 1)) {
          lineHeight /= this.lineHeight;
        }
        ctx.fillRect(
          boundaries.left + lineOffset,
          boundaries.top + boundaries.topOffset,
          boxWidth > 0 ? boxWidth : 0,
          lineHeight);

        boundaries.topOffset += realLineHeight;
      }
    },

    /**
     * @private
     * @param {String} method
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {String} line Content of the line
     * @param {Number} left
     * @param {Number} top
     * @param {Number} lineIndex
     * @param {Number} charOffset
     */
    _renderChars: function (method, ctx, line, left, top, lineIndex, charOffset) {

      if (this.isEmptyStyles()) {
        return this._renderCharsFast(method, ctx, line, left, top);
      }

      charOffset = charOffset || 0;

      // set proper line offset
      var lineHeight = this._getHeightOfLine(ctx, lineIndex),
        prevStyle,
        thisStyle,
        charsToRender = '';

      ctx.save();
      top -= lineHeight / this.lineHeight * this._fontSizeFraction;
      for (var i = charOffset, len = line.length + charOffset; i <= len; i++) {
        prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i);
        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 += line[i - charOffset];
      }
      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) {

      if (method === 'fillText' && this.fill) {
        this.callSuper('_renderChars', method, ctx, line, left, top);
      }
      if (method === 'strokeText' && ((this.stroke && this.strokeWidth > 0) || this.skipFillStrokeCheck)) {
        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 charWidth, charHeight, shouldFill, shouldStroke,
        decl = this._getStyleDeclaration(lineIndex, i),
        offset, textDecoration, chars, additionalSpace, _charWidth;

      if (decl) {
        charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i);
        shouldStroke = decl.stroke;
        shouldFill = decl.fill;
        textDecoration = decl.textDecoration;
      }
      else {
        charHeight = this.fontSize;
      }

      shouldStroke = (shouldStroke || this.stroke) && method === 'strokeText';
      shouldFill = (shouldFill || this.fill) && method === 'fillText';

      decl && ctx.save();

      charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl || null);
      textDecoration = textDecoration || this.textDecoration;

      if (decl && decl.textBackgroundColor) {
        this._removeShadow(ctx);
      }
      if (this.charSpacing !== 0) {
        additionalSpace = this._getWidthOfCharSpacing();
        chars = _char.split('');
        charWidth = 0;
        for (var j = 0, len = chars.length, char; j < len; j++) {
          char = chars[j];
          shouldFill && ctx.fillText(char, left + charWidth, top);
          shouldStroke && ctx.strokeText(char, left + charWidth, top);
          _charWidth = ctx.measureText(char).width + additionalSpace;
          charWidth += _charWidth > 0 ? _charWidth : 0;
        }
      }
      else {
        shouldFill && ctx.fillText(_char, left, top);
        shouldStroke && ctx.strokeText(_char, left, top);
      }

      if (textDecoration || textDecoration !== '') {
        offset = this._fontSizeFraction * lineHeight / this.lineHeight;
        this._renderCharDecoration(ctx, textDecoration, left, top, offset, charWidth, charHeight);
      }

      decl && ctx.restore();
      ctx.translate(charWidth, 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, textDecoration, left, top, offset, charWidth, charHeight) {

      if (!textDecoration) {
        return;
      }

      var decorationWeight = charHeight / 15,
        positions = {
          underline: top + charHeight / 10,
          'line-through': top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + decorationWeight,
          overline: top - (this._fontSizeMult - this._fontSizeFraction) * charHeight
        },
        decorations = ['underline', 'line-through', 'overline'], i, decoration;

      for (i = 0; i < decorations.length; i++) {
        decoration = decorations[i];
        if (textDecoration.indexOf(decoration) > -1) {
          ctx.fillRect(left, positions[decoration], charWidth, decorationWeight);
        }
      }
    },

    /**
     * @private
     * @param {String} method
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {String} line
     * @param {Number} left
     * @param {Number} top
     * @param {Number} lineIndex
     */
    _renderTextLine: function (method, ctx, line, left, top, lineIndex) {
      // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine
      // the adding 0.03 is just to align text with itext by overlap test
      if (!this.isEmptyStyles()) {
        top += this.fontSize * (this._fontSizeFraction + 0.03);
      }
      this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex);
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderTextDecoration: function (ctx) {
      if (this.isEmptyStyles()) {
        return this.callSuper('_renderTextDecoration', ctx);
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderTextLinesBackground: function (ctx) {
      this.callSuper('_renderTextLinesBackground', ctx);

      var lineTopOffset = 0, heightOfLine,
        lineWidth, lineLeftOffset,
        leftOffset = this._getLeftOffset(),
        topOffset = this._getTopOffset(),
        line, _char, style;
      ctx.save();
      for (var i = 0, len = this._textLines.length; i < len; i++) {
        heightOfLine = this._getHeightOfLine(ctx, i);
        line = this._textLines[i];

        if (line === '' || !this.styles || !this._getLineStyle(i)) {
          lineTopOffset += heightOfLine;
          continue;
        }

        lineWidth = this._getLineWidth(ctx, i);
        lineLeftOffset = this._getLineLeftOffset(lineWidth);

        for (var j = 0, jlen = line.length; j < jlen; j++) {
          style = this._getStyleDeclaration(i, j);
          if (!style || !style.textBackgroundColor) {
            continue;
          }
          _char = line[j];

          ctx.fillStyle = style.textBackgroundColor;

          ctx.fillRect(
            leftOffset + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j),
            topOffset + lineTopOffset,
            this._getWidthOfChar(ctx, _char, i, j),
            heightOfLine / this.lineHeight
          );
        }
        lineTopOffset += heightOfLine;
      }
      ctx.restore();
    },

    /**
     * @private
     */
    _getCacheProp: function (_char, styleDeclaration) {
      return _char +
        styleDeclaration.fontSize +
        styleDeclaration.fontWeight +
        styleDeclaration.fontStyle;
    },

    /**
     * @private
     * @param {String} fontFamily name
     * @return {Object} reference to cache
     */
    _getFontCache: function (fontFamily) {
      if (!fabric.charWidthsCache[fontFamily]) {
        fabric.charWidthsCache[fontFamily] = {};
      }
      return fabric.charWidthsCache[fontFamily];
    },

    /**
     * @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 charDecl = decl || this._getStyleDeclaration(lineIndex, charIndex),
        styleDeclaration = clone(charDecl),
        width, cacheProp, charWidthsCache;

      this._applyFontStyles(styleDeclaration);
      charWidthsCache = this._getFontCache(styleDeclaration.fontFamily);
      cacheProp = this._getCacheProp(_char, styleDeclaration);

      // short-circuit if no styles for this char
      // global style from object is always applyed and handled by save and restore
      if (!charDecl && charWidthsCache[cacheProp] && this.caching) {
        return 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, this)
        : fill;

      if (styleDeclaration.stroke) {
        ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive)
          ? styleDeclaration.stroke.toLive(ctx, this)
          : styleDeclaration.stroke;
      }

      ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth;
      ctx.font = this._getFontDeclaration.call(styleDeclaration);

      //if we want this._setShadow.call to work with styleDeclarion
      //we have to add those references
      if (styleDeclaration.shadow) {
        styleDeclaration.scaleX = this.scaleX;
        styleDeclaration.scaleY = this.scaleY;
        styleDeclaration.canvas = this.canvas;
        styleDeclaration.getObjectScaling = this.getObjectScaling;
        this._setShadow.call(styleDeclaration, ctx);
      }

      if (!this.caching || !charWidthsCache[cacheProp]) {
        width = ctx.measureText(_char).width;
        this.caching && (charWidthsCache[cacheProp] = width);
        return width;
      }

      return 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;
      }
    },

    /**
     * @param {Number} lineIndex
     * @param {Number} charIndex
     * @param {Boolean} [returnCloneOrEmpty=false]
     * @private
     */
    _getStyleDeclaration: function (lineIndex, charIndex, returnCloneOrEmpty) {
      if (returnCloneOrEmpty) {
        return (this.styles[lineIndex] && this.styles[lineIndex][charIndex])
          ? clone(this.styles[lineIndex][charIndex])
          : {};
      }

      return this.styles[lineIndex] && this.styles[lineIndex][charIndex] ? this.styles[lineIndex][charIndex] : null;
    },

    /**
     * @param {Number} lineIndex
     * @param {Number} charIndex
     * @param {Object} style
     * @private
     */
    _setStyleDeclaration: function (lineIndex, charIndex, style) {
      this.styles[lineIndex][charIndex] = style;
    },

    /**
     *
     * @param {Number} lineIndex
     * @param {Number} charIndex
     * @private
     */
    _deleteStyleDeclaration: function (lineIndex, charIndex) {
      delete this.styles[lineIndex][charIndex];
    },

    /**
     * @param {Number} lineIndex
     * @private
     */
    _getLineStyle: function (lineIndex) {
      return this.styles[lineIndex];
    },

    /**
     * @param {Number} lineIndex
     * @param {Object} style
     * @private
     */
    _setLineStyle: function (lineIndex, style) {
      this.styles[lineIndex] = style;
    },

    /**
     * @param {Number} lineIndex
     * @private
     */
    _deleteLineStyle: function (lineIndex) {
      delete this.styles[lineIndex];
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _getWidthOfChar: function (ctx, _char, lineIndex, charIndex) {
      if (!this._isMeasuring && this.textAlign === 'justify' && this._reSpacesAndTabs.test(_char)) {
        return this._getWidthOfSpace(ctx, lineIndex);
      }
      ctx.save();
      var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex);
      if (this.charSpacing !== 0) {
        width += this._getWidthOfCharSpacing();
      }
      ctx.restore();
      return width > 0 ? width : 0
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Number} lineIndex
     * @param {Number} charIndex
     */
    _getHeightOfChar: function (ctx, lineIndex, charIndex) {
      var style = this._getStyleDeclaration(lineIndex, charIndex);
      return style && style.fontSize ? style.fontSize : this.fontSize;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Number} lineIndex
     * @param {Number} charIndex
     */
    _getWidthOfCharsAt: function (ctx, lineIndex, charIndex) {
      var width = 0, i, _char;
      for (i = 0; i < charIndex; i++) {
        _char = this._textLines[lineIndex][i];
        width += this._getWidthOfChar(ctx, _char, lineIndex, i);
      }
      return width;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Number} lineIndex line number
     * @return {Number} Line width
     */
    _measureLine: function (ctx, lineIndex) {
      this._isMeasuring = true;
      var width = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length);
      if (this.charSpacing !== 0) {
        width -= this._getWidthOfCharSpacing();
      }
      this._isMeasuring = false;
      return width > 0 ? width : 0;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Number} lineIndex
     */
    _getWidthOfSpace: function (ctx, lineIndex) {
      if (this.__widthOfSpace[lineIndex]) {
        return this.__widthOfSpace[lineIndex];
      }
      var line = this._textLines[lineIndex],
        wordsWidth = this._getWidthOfWords(ctx, line, lineIndex, 0),
        widthDiff = this.width - wordsWidth,
        numSpaces = line.length - line.replace(this._reSpacesAndTabs, '').length,
        width = Math.max(widthDiff / numSpaces, ctx.measureText(' ').width);
      this.__widthOfSpace[lineIndex] = width;
      return width;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {String} line
     * @param {Number} lineIndex
     * @param {Number} charOffset
     */
    _getWidthOfWords: function (ctx, line, lineIndex, charOffset) {
      var width = 0;

      for (var charIndex = 0; charIndex < line.length; charIndex++) {
        var _char = line[charIndex];

        if (!_char.match(/\s/)) {
          width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex + charOffset);
        }
      }

      return width;
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _getHeightOfLine: function (ctx, lineIndex) {
      if (this.__lineHeights[lineIndex]) {
        return this.__lineHeights[lineIndex];
      }

      var line = this._textLines[lineIndex],
        maxHeight = this._getHeightOfChar(ctx, lineIndex, 0);

      for (var i = 1, len = line.length; i < len; i++) {
        var currentCharHeight = this._getHeightOfChar(ctx, lineIndex, i);
        if (currentCharHeight > maxHeight) {
          maxHeight = currentCharHeight;
        }
      }
      this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult;
      return this.__lineHeights[lineIndex];
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _getTextHeight: function (ctx) {
      var lineHeight, height = 0;
      for (var i = 0, len = this._textLines.length; i < len; i++) {
        lineHeight = this._getHeightOfLine(ctx, i);
        height += (i === len - 1 ? lineHeight / this.lineHeight : lineHeight);
      }
      return height;
    },

    /**
     * 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, true)
      });
    }
  });

  /**
   * Returns fabric.IText instance from an object representation
   * @static
   * @memberOf fabric.IText
   * @param {Object} object Object to create an instance from
   * @param {function} [callback] invoked with new instance as argument
   * @return {fabric.IText} instance of fabric.IText
   */
  fabric.IText.fromObject = function (object, callback) {
    var iText = new fabric.IText(object.text, clone(object));
    callback && callback(iText);
    return iText;
  };
})();


(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.initRemovedHandler();
      this.initCursorSelectionHandlers();
      this.initDoubleClickSimulation();
      this.mouseMoveHandler = this.mouseMoveHandler.bind(this);
    },

    /**
     * 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 () {
      var _this = this;
      this.on('added', function () {
        var canvas = _this.canvas;
        if (canvas) {
          if (!canvas._hasITextHandlers) {
            canvas._hasITextHandlers = true;
            _this._initCanvasHandlers(canvas);
          }
          canvas._iTextInstances = canvas._iTextInstances || [];
          canvas._iTextInstances.push(_this);
        }
      });
    },

    initRemovedHandler: function () {
      var _this = this;
      this.on('removed', function () {
        var canvas = _this.canvas;
        if (canvas) {
          canvas._iTextInstances = canvas._iTextInstances || [];
          fabric.util.removeFromArray(canvas._iTextInstances, _this);
          if (canvas._iTextInstances.length === 0) {
            canvas._hasITextHandlers = false;
            _this._removeCanvasHandlers(canvas);
          }
        }
      });
    },

    /**
     * register canvas event to manage exiting on other instances
     * @private
     */
    _initCanvasHandlers: function (canvas) {
      canvas._canvasITextSelectionClearedHanlder = (function () {
        fabric.IText.prototype.exitEditingOnOthers(canvas);
      }).bind(this);
      canvas._mouseUpITextHandler = (function () {
        if (canvas._iTextInstances) {
          canvas._iTextInstances.forEach(function (obj) {
            obj.__isMousedown = false;
          });
        }
      }).bind(this);
      canvas.on('selection:cleared', canvas._canvasITextSelectionClearedHanlder);
      canvas.on('object:selected', canvas._canvasITextSelectionClearedHanlder);
      canvas.on('mouse:up', canvas._mouseUpITextHandler);
    },

    /**
     * remove canvas event to manage exiting on other instances
     * @private
     */
    _removeCanvasHandlers: function (canvas) {
      canvas.off('selection:cleared', canvas._canvasITextSelectionClearedHanlder);
      canvas.off('object:selected', canvas._canvasITextSelectionClearedHanlder);
      canvas.off('mouse:up', canvas._mouseUpITextHandler);
    },

    /**
     * @private
     */
    _tick: function () {
      this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete');
    },

    /**
     * @private
     */
    _animateCursor: function (obj, targetOpacity, duration, completeMethod) {

      var tickState;

      tickState = {
        isAborted: false,
        abort: function () {
          this.isAborted = true;
        },
      };

      obj.animate('_currentCursorOpacity', targetOpacity, {
        duration: duration,
        onComplete: function () {
          if (!tickState.isAborted) {
            obj[completeMethod]();
          }
        },
        onChange: function () {
          // we do not want to animate a selection, only cursor
          if (obj.canvas && obj.selectionStart === obj.selectionEnd) {
            obj.renderCursorOrSelection();
          }
        },
        abort: function () {
          return tickState.isAborted;
        }
      });
      return tickState;
    },

    /**
     * @private
     */
    _onTickComplete: function () {

      var _this = this;

      if (this._cursorTimeout1) {
        clearTimeout(this._cursorTimeout1);
      }
      this._cursorTimeout1 = setTimeout(function () {
        _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick');
      }, 100);
    },

    /**
     * Initializes delayed cursor
     */
    initDelayedCursor: function (restart) {
      var _this = this,
        delay = restart ? 0 : this.cursorDelay;

      this.abortCursorAnimation();
      this._currentCursorOpacity = 1;
      this._cursorTimeout2 = setTimeout(function () {
        _this._tick();
      }, delay);
    },

    /**
     * Aborts cursor animation and clears all timeouts
     */
    abortCursorAnimation: function () {
      var shouldClear = this._currentTickState || this._currentTickCompleteState;
      this._currentTickState && this._currentTickState.abort();
      this._currentTickCompleteState && this._currentTickCompleteState.abort();

      clearTimeout(this._cursorTimeout1);
      clearTimeout(this._cursorTimeout2);

      this._currentCursorOpacity = 0;
      // to clear just itext area we need to transform the context
      // it may not be worth it
      if (shouldClear) {
        this.canvas && this.canvas.clearContext(this.canvas.contextTop || this.ctx);
      }

    },

    /**
     * Selects entire text
     */
    selectAll: function () {
      this.selectionStart = 0;
      this.selectionEnd = this.text.length;
      this._fireSelectionChanged();
      this._updateTextarea();
    },

    /**
     * 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, len = selectedText.length; i < len; i++) {
        if (selectedText[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) {
      selectionStart = selectionStart || this.selectionStart;
      var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */
        newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */

      this.selectionStart = newSelectionStart;
      this.selectionEnd = newSelectionEnd;
      this._fireSelectionChanged();
      this._updateTextarea();
      this.renderCursorOrSelection();
    },

    /**
     * Selects a line based on the index
     * @param {Number} selectionStart Index of a character
     */
    selectLine: function (selectionStart) {
      selectionStart = selectionStart || this.selectionStart;
      var newSelectionStart = this.findLineBoundaryLeft(selectionStart),
        newSelectionEnd = this.findLineBoundaryRight(selectionStart);

      this.selectionStart = newSelectionStart;
      this.selectionEnd = newSelectionEnd;
      this._fireSelectionChanged();
      this._updateTextarea();
    },

    /**
     * Enters editing state
     * @return {fabric.IText} thisArg
     * @chainable
     */
    enterEditing: function (e) {
      if (this.isEditing || !this.editable) {
        return;
      }

      if (this.canvas) {
        this.exitEditingOnOthers(this.canvas);
      }

      this.isEditing = true;

      this.initHiddenTextarea(e);
      this.hiddenTextarea.focus();
      this._updateTextarea();
      this._saveEditingProps();
      this._setEditingProps();
      this._textBeforeEdit = this.text;

      this._tick();
      this.fire('editing:entered');

      if (!this.canvas) {
        return this;
      }
      this.canvas.fire('text:editing:entered', { target: this });
      this.initMouseMoveHandler();
      this.canvas.renderAll();
      return this;
    },

    exitEditingOnOthers: function (canvas) {
      if (canvas._iTextInstances) {
        canvas._iTextInstances.forEach(function (obj) {
          obj.selected = false;
          if (obj.isEditing) {
            obj.exitEditing();
          }
        });
      }
    },

    /**
     * Initializes "mousemove" event handler
     */
    initMouseMoveHandler: function () {
      this.canvas.on('mouse:move', this.mouseMoveHandler);
    },

    /**
     * @private
     */
    mouseMoveHandler: function (options) {
      if (!this.__isMousedown || !this.isEditing) {
        return;
      }

      var newSelectionStart = this.getSelectionStartFromPointer(options.e),
        currentStart = this.selectionStart,
        currentEnd = this.selectionEnd;
      if (newSelectionStart === this.__selectionStartOnMouseDown) {
        return;
      }
      if (newSelectionStart > this.__selectionStartOnMouseDown) {
        this.selectionStart = this.__selectionStartOnMouseDown;
        this.selectionEnd = newSelectionStart;
      }
      else {
        this.selectionStart = newSelectionStart;
        this.selectionEnd = this.__selectionStartOnMouseDown;
      }
      if (this.selectionStart !== currentStart || this.selectionEnd !== currentEnd) {
        this._fireSelectionChanged();
        this._updateTextarea();
        this.renderCursorOrSelection();
      }
    },

    /**
     * @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 || this.inCompositionMode) {
        return;
      }
      this.cursorOffsetCache = {};
      this.hiddenTextarea.value = this.text;
      this.hiddenTextarea.selectionStart = this.selectionStart;
      this.hiddenTextarea.selectionEnd = this.selectionEnd;
      if (this.selectionStart === this.selectionEnd) {
        var style = this._calcTextareaPosition();
        this.hiddenTextarea.style.left = style.left;
        this.hiddenTextarea.style.top = style.top;
        this.hiddenTextarea.style.fontSize = style.fontSize;
      }
    },

    /**
     * @private
     * @return {Object} style contains style for hiddenTextarea
     */
    _calcTextareaPosition: function () {
      if (!this.canvas) {
        return { x: 1, y: 1 };
      }
      var chars = this.text.split(''),
        boundaries = this._getCursorBoundaries(chars, 'cursor'),
        cursorLocation = this.get2DCursorLocation(),
        lineIndex = cursorLocation.lineIndex,
        charIndex = cursorLocation.charIndex,
        charHeight = this.getCurrentCharFontSize(lineIndex, charIndex),
        leftOffset = (lineIndex === 0 && charIndex === 0)
          ? this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex))
          : boundaries.leftOffset,
        m = this.calcTransformMatrix(),
        p = {
          x: boundaries.left + leftOffset,
          y: boundaries.top + boundaries.topOffset + charHeight
        },
        upperCanvas = this.canvas.upperCanvasEl,
        maxWidth = upperCanvas.width - charHeight,
        maxHeight = upperCanvas.height - charHeight;

      p = fabric.util.transformPoint(p, m);
      p = fabric.util.transformPoint(p, this.canvas.viewportTransform);

      if (p.x < 0) {
        p.x = 0;
      }
      if (p.x > maxWidth) {
        p.x = maxWidth;
      }
      if (p.y < 0) {
        p.y = 0;
      }
      if (p.y > maxHeight) {
        p.y = maxHeight;
      }

      // add canvas offset on document
      p.x += this.canvas._offset.left;
      p.y += this.canvas._offset.top;

      return { left: p.x + 'px', top: p.y + 'px', fontSize: charHeight };
    },

    /**
     * @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 () {
      var isTextChanged = (this._textBeforeEdit !== this.text);
      this.selected = false;
      this.isEditing = false;
      this.selectable = true;

      this.selectionEnd = this.selectionStart;
      this.hiddenTextarea.blur && this.hiddenTextarea.blur();
      this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea);
      this.hiddenTextarea = null;

      this.abortCursorAnimation();
      this._restoreEditingProps();
      this._currentCursorOpacity = 0;

      this.fire('editing:exited');
      isTextChanged && this.fire('modified');
      if (this.canvas) {
        this.canvas.off('mouse:move', this.mouseMoveHandler);
        this.canvas.fire('text:editing:exited', { target: this });
        isTextChanged && this.canvas.fire('object:modified', { target: this });
      }

      return this;
    },

    /**
     * @private
     */
    _removeExtraneousStyles: function () {
      for (var prop in this.styles) {
        if (!this._textLines[prop]) {
          delete this.styles[prop];
        }
      }
    },

    /**
     * @private
     */
    _removeCharsFromTo: function (start, end) {
      while (end !== start) {
        this._removeSingleCharAndStyle(start + 1);
        end--;
      }
      this.selectionStart = start;
      this.selectionEnd = start;
    },

    _removeSingleCharAndStyle: function (index) {
      var isBeginningOfLine = this.text[index - 1] === '\n',
        indexStyle = isBeginningOfLine ? index : index - 1;
      this.removeStyleObject(isBeginningOfLine, indexStyle);
      this.text = this.text.slice(0, index - 1) +
        this.text.slice(index);

      this._textLines = this._splitTextIntoLines();
    },

    /**
     * Inserts characters where cursor is (replacing selection if one exists)
     * @param {String} _chars Characters to insert
     * @param {Boolean} useCopiedStyle use fabric.copiedTextStyle
     */
    insertChars: function (_chars, useCopiedStyle) {
      var style;

      if (this.selectionEnd - this.selectionStart > 1) {
        this._removeCharsFromTo(this.selectionStart, this.selectionEnd);
      }
      //short circuit for block paste
      if (!useCopiedStyle && this.isEmptyStyles()) {
        this.insertChar(_chars, false);
        return;
      }
      for (var i = 0, len = _chars.length; i < len; i++) {
        if (useCopiedStyle) {
          style = fabric.copiedTextStyle[i];
        }
        this.insertChar(_chars[i], i < len - 1, style);
      }
    },

    /**
     * Inserts a character where cursor is
     * @param {String} _char Characters to insert
     * @param {Boolean} skipUpdate trigger rendering and updates at the end of text insert
     * @param {Object} styleObject Style to be inserted for the new char
     */
    insertChar: function (_char, skipUpdate, styleObject) {
      var isEndOfLine = this.text[this.selectionStart] === '\n';
      this.text = this.text.slice(0, this.selectionStart) +
        _char + this.text.slice(this.selectionEnd);
      this._textLines = this._splitTextIntoLines();
      this.insertStyleObjects(_char, isEndOfLine, styleObject);
      this.selectionStart += _char.length;
      this.selectionEnd = this.selectionStart;
      if (skipUpdate) {
        return;
      }
      this._updateTextarea();
      this.setCoords();
      this._fireSelectionChanged();
      this.fire('changed');
      this.canvas && this.canvas.fire('text:changed', { target: this });
      this.canvas && this.canvas.renderAll();
    },

    /**
     * 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 = {},
        newLineStyles = {};

      if (this.styles[lineIndex] && this.styles[lineIndex][charIndex - 1]) {
        currentCharStyle = this.styles[lineIndex][charIndex - 1];
      }

      // 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;
      }
      this._forceClearCache = true;
    },

    /**
     * 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];

          // only delete the style if there was nothing moved there
          if (!currentLineStylesCloned[numericIndex - 1]) {
            delete currentLineStyles[numericIndex];
          }
        }
      }

      this.styles[lineIndex][charIndex] =
        style || clone(currentLineStyles[charIndex - 1]);
      this._forceClearCache = true;
    },

    /**
     * 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 {Object} [styleObject] Style to insert
     */
    insertStyleObjects: function (_chars, isEndOfLine, styleObject) {
      // removed shortcircuit over isEmptyStyles

      var cursorLocation = this.get2DCursorLocation(),
        lineIndex = cursorLocation.lineIndex,
        charIndex = cursorLocation.charIndex;

      if (!this._getLineStyle(lineIndex)) {
        this._setLineStyle(lineIndex, {});
      }

      if (_chars === '\n') {
        this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine);
      }
      else {
        this.insertCharStyleObject(lineIndex, charIndex, styleObject);
      }
    },

    /**
     * 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];
          if (!clonedStyles[numericLine - offset]) {
            delete this.styles[numericLine];
          }
        }
      }
      //TODO: evaluate if delete old style lines with offset -1
    },

    /**
     * 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;

      this._removeStyleObject(isBeginningOfLine, cursorLocation, lineIndex, charIndex);
    },

    _getTextOnPreviousLine: function (lIndex) {
      return this._textLines[lIndex - 1];
    },

    _removeStyleObject: function (isBeginningOfLine, cursorLocation, lineIndex, charIndex) {

      if (isBeginningOfLine) {
        var textOnPreviousLine = this._getTextOnPreviousLine(cursorLocation.lineIndex),
          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(cursorLocation.lineIndex, -1);
      }
      else {
        var currentLineStyles = this.styles[lineIndex];

        if (currentLineStyles) {
          delete currentLineStyles[charIndex];
        }
        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');
    },

    /**
     * Set the selectionStart and selectionEnd according to the ne postion of cursor
     * mimic the key - mouse navigation when shift is pressed.
     */
    setSelectionStartEndWithShift: function (start, end, newSelection) {
      if (newSelection <= start) {
        if (end === start) {
          this._selectionDirection = 'left';
        }
        else if (this._selectionDirection === 'right') {
          this._selectionDirection = 'left';
          this.selectionEnd = start;
        }
        this.selectionStart = newSelection;
      }
      else if (newSelection > start && newSelection < end) {
        if (this._selectionDirection === 'right') {
          this.selectionEnd = newSelection;
        }
        else {
          this.selectionStart = newSelection;
        }
      }
      else {
        // newSelection is > selection start and end
        if (end === start) {
          this._selectionDirection = 'right';
        }
        else if (this._selectionDirection === 'left') {
          this._selectionDirection = 'right';
          this.selectionStart = end;
        }
        this.selectionEnd = newSelection;
      }
    },

    setSelectionInBoundaries: function () {
      var length = this.text.length;
      if (this.selectionStart > length) {
        this.selectionStart = length;
      }
      else if (this.selectionStart < 0) {
        this.selectionStart = 0;
      }
      if (this.selectionEnd > length) {
        this.selectionEnd = length;
      }
      else if (this.selectionEnd < 0) {
        this.selectionEnd = 0;
      }
    }
  });
})();


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.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) {
      if (!this.editable) {
        return;
      }
      var pointer = this.canvas.getPointer(options.e);

      this.__mousedownX = pointer.x;
      this.__mousedownY = pointer.y;
      this.__isMousedown = true;

      if (this.selected) {
        this.setCursorByClick(options.e);
      }

      if (this.isEditing) {
        this.__selectionStartOnMouseDown = this.selectionStart;
        if (this.selectionStart === this.selectionEnd) {
          this.abortCursorAnimation();
        }
        this.renderCursorOrSelection();
      }
    });
  },

  /**
   * @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.editable || this._isObjectMoved(options.e)) {
        return;
      }

      if (this.__lastSelected && !this.__corner) {
        this.enterEditing(options.e);
        if (this.selectionStart === this.selectionEnd) {
          this.initDelayedCursor(true);
        }
        else {
          this.renderCursorOrSelection();
        }
      }
      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 newSelection = this.getSelectionStartFromPointer(e),
      start = this.selectionStart, end = this.selectionEnd;
    if (e.shiftKey) {
      this.setSelectionStartEndWithShift(start, end, newSelection);
    }
    else {
      this.selectionStart = newSelection;
      this.selectionEnd = newSelection;
    }
    this._fireSelectionChanged();
    this._updateTextarea();
  },

  /**
   * 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.getLocalPointer(e),
      prevWidth = 0,
      width = 0,
      height = 0,
      charIndex = 0,
      newSelectionStart,
      line;

    for (var i = 0, len = this._textLines.length; i < len; i++) {
      line = this._textLines[i];
      height += this._getHeightOfLine(this.ctx, i) * this.scaleY;

      var widthOfLine = this._getLineWidth(this.ctx, i),
        lineLeftOffset = this._getLineLeftOffset(widthOfLine);

      width = lineLeftOffset * this.scaleX;

      for (var j = 0, jlen = line.length; j < jlen; j++) {

        prevWidth = width;

        width += this._getWidthOfChar(this.ctx, line[j], 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) {
        //this happens just on end of lines.
        return this._getNewSelectionStartFromOffset(
          mouseOffset, prevWidth, width, charIndex + i - 1, jlen);
      }
    }

    // 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) {

    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;
    }

    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');
    var style = this._calcTextareaPosition();
    this.hiddenTextarea.style.cssText = 'position: absolute; top: ' + style.top + '; left: ' + style.left + ';'
      + ' opacity: 0; width: 0px; height: 0px; z-index: -999;';
    fabric.document.body.appendChild(this.hiddenTextarea);

    fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this));
    fabric.util.addListener(this.hiddenTextarea, 'keyup', this.onKeyUp.bind(this));
    fabric.util.addListener(this.hiddenTextarea, 'input', this.onInput.bind(this));
    fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this));
    fabric.util.addListener(this.hiddenTextarea, 'cut', this.cut.bind(this));
    fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this));
    fabric.util.addListener(this.hiddenTextarea, 'compositionstart', this.onCompositionStart.bind(this));
    fabric.util.addListener(this.hiddenTextarea, 'compositionupdate', this.onCompositionUpdate.bind(this));
    fabric.util.addListener(this.hiddenTextarea, 'compositionend', this.onCompositionEnd.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',
    9: 'exitEditing',
    27: 'exitEditing',
    13: 'insertNewline',
    33: 'moveCursorUp',
    34: 'moveCursorDown',
    35: 'moveCursorRight',
    36: 'moveCursorLeft',
    37: 'moveCursorLeft',
    38: 'moveCursorUp',
    39: 'moveCursorRight',
    40: 'moveCursorDown',
    46: 'forwardDelete'
  },

  /**
   * @private
   */
  _ctrlKeysMapUp: {
    67: 'copy',
    88: 'cut'
  },

  /**
   * @private
   */
  _ctrlKeysMapDown: {
    65: 'selectAll'
  },

  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._ctrlKeysMapDown) && (e.ctrlKey || e.metaKey)) {
      this[this._ctrlKeysMapDown[e.keyCode]](e);
    }
    else {
      return;
    }
    e.stopImmediatePropagation();
    e.preventDefault();
    this.canvas && this.canvas.renderAll();
  },

  /**
   * Handles keyup event
   * We handle KeyUp because ie11 and edge have difficulties copy/pasting
   * if a copy/cut event fired, keyup is dismissed
   * @param {Event} e Event object
   */
  onKeyUp: function (e) {
    if (!this.isEditing || this._copyDone) {
      this._copyDone = false;
      return;
    }
    if ((e.keyCode in this._ctrlKeysMapUp) && (e.ctrlKey || e.metaKey)) {
      this[this._ctrlKeysMapUp[e.keyCode]](e);
    }
    else {
      return;
    }
    e.stopImmediatePropagation();
    e.preventDefault();
    this.canvas && this.canvas.renderAll();
  },

  /**
   * Handles onInput event
   * @param {Event} e Event object
   */
  onInput: function (e) {
    if (!this.isEditing || this.inCompositionMode) {
      return;
    }
    var offset = this.selectionStart || 0,
      offsetEnd = this.selectionEnd || 0,
      textLength = this.text.length,
      newTextLength = this.hiddenTextarea.value.length,
      diff, charsToInsert, start;
    if (newTextLength > textLength) {
      //we added some character
      start = this._selectionDirection === 'left' ? offsetEnd : offset;
      diff = newTextLength - textLength;
      charsToInsert = this.hiddenTextarea.value.slice(start, start + diff);
    }
    else {
      //we selected a portion of text and then input something else.
      //Internet explorer does not trigger this else
      diff = newTextLength - textLength + offsetEnd - offset;
      charsToInsert = this.hiddenTextarea.value.slice(offset, offset + diff);
    }
    this.insertChars(charsToInsert);
    e.stopPropagation();
  },

  /**
   * Composition start
   */
  onCompositionStart: function () {
    this.inCompositionMode = true;
    this.prevCompositionLength = 0;
    this.compositionStart = this.selectionStart;
  },

  /**
   * Composition end
   */
  onCompositionEnd: function () {
    this.inCompositionMode = false;
  },

  /**
   * Composition update
   */
  onCompositionUpdate: function (e) {
    var data = e.data;
    this.selectionStart = this.compositionStart;
    this.selectionEnd = this.selectionEnd === this.selectionStart ?
      this.compositionStart + this.prevCompositionLength : this.selectionEnd;
    this.insertChars(data, false);
    this.prevCompositionLength = data.length;
  },

  /**
   * Forward delete
   */
  forwardDelete: function (e) {
    if (this.selectionStart === this.selectionEnd) {
      if (this.selectionStart === this.text.length) {
        return;
      }
      this.moveCursorRight(e);
    }
    this.removeChars(e);
  },

  /**
   * Copies selected text
   * @param {Event} e Event object
   */
  copy: function (e) {
    if (this.selectionStart === this.selectionEnd) {
      //do not cut-copy if no selection
      return;
    }
    var selectedText = this.getSelectedText(),
      clipboardData = this._getClipboardData(e);

    // Check for backward compatibility with old browsers
    if (clipboardData) {
      clipboardData.setData('text', selectedText);
    }

    fabric.copiedText = selectedText;
    fabric.copiedTextStyle = fabric.util.object.clone(
      this.getSelectionStyles(
        this.selectionStart,
        this.selectionEnd
      )
    );
    e.stopImmediatePropagation();
    e.preventDefault();
    this._copyDone = true;
  },

  /**
   * Pastes text
   * @param {Event} e Event object
   */
  paste: function (e) {
    var copiedText = null,
      clipboardData = this._getClipboardData(e),
      useCopiedStyle = true;

    // Check for backward compatibility with old browsers
    if (clipboardData) {
      copiedText = clipboardData.getData('text').replace(/\r/g, '');
      if (!fabric.copiedTextStyle || fabric.copiedText !== copiedText) {
        useCopiedStyle = false;
      }
    }
    else {
      copiedText = fabric.copiedText;
    }

    if (copiedText) {
      this.insertChars(copiedText, useCopiedStyle);
    }
    e.stopImmediatePropagation();
    e.preventDefault();
  },

  /**
   * Cuts text
   * @param {Event} e Event object
   */
  cut: function (e) {
    if (this.selectionStart === this.selectionEnd) {
      return;
    }

    this.copy(e);
    this.removeChars(e);
  },

  /**
   * @private
   * @param {Event} e Event object
   * @return {Object} Clipboard data object
   */
  _getClipboardData: function (e) {
    return (e && e.clipboardData) || fabric.window.clipboardData;
  },

  /**
   * Finds the width in pixels before the cursor on the same line
   * @private
   * @param {Number} lineIndex
   * @param {Number} charIndex
   * @return {Number} widthBeforeCursor width before cursor
   */
  _getWidthBeforeCursor: function (lineIndex, charIndex) {
    var textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex),
      widthOfLine = this._getLineWidth(this.ctx, lineIndex),
      widthBeforeCursor = this._getLineLeftOffset(widthOfLine), _char;

    for (var i = 0, len = textBeforeCursor.length; i < len; i++) {
      _char = textBeforeCursor[i];
      widthBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i);
    }
    return widthBeforeCursor;
  },

  /**
   * Gets start offset of a selection
   * @param {Event} e Event object
   * @param {Boolean} isRight
   * @return {Number}
   */
  getDownCursorOffset: function (e, isRight) {
    var selectionProp = this._getSelectionForOffset(e, isRight),
      cursorLocation = this.get2DCursorLocation(selectionProp),
      lineIndex = cursorLocation.lineIndex;
    // if on last line, down cursor goes to end of line
    if (lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) {
      // move to the end of a text
      return this.text.length - selectionProp;
    }
    var charIndex = cursorLocation.charIndex,
      widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex),
      indexOnOtherLine = this._getIndexOnLine(lineIndex + 1, widthBeforeCursor),
      textAfterCursor = this._textLines[lineIndex].slice(charIndex);

    return textAfterCursor.length + indexOnOtherLine + 2;
  },

  /**
   * private
   * Helps finding if the offset should be counted from Start or End
   * @param {Event} e Event object
   * @param {Boolean} isRight
   * @return {Number}
   */
  _getSelectionForOffset: function (e, isRight) {
    if (e.shiftKey && this.selectionStart !== this.selectionEnd && isRight) {
      return this.selectionEnd;
    }
    else {
      return this.selectionStart;
    }
  },

  /**
   * @param {Event} e Event object
   * @param {Boolean} isRight
   * @return {Number}
   */
  getUpCursorOffset: function (e, isRight) {
    var selectionProp = this._getSelectionForOffset(e, isRight),
      cursorLocation = this.get2DCursorLocation(selectionProp),
      lineIndex = cursorLocation.lineIndex;
    if (lineIndex === 0 || e.metaKey || e.keyCode === 33) {
      // if on first line, up cursor goes to start of line
      return -selectionProp;
    }
    var charIndex = cursorLocation.charIndex,
      widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex),
      indexOnOtherLine = this._getIndexOnLine(lineIndex - 1, widthBeforeCursor),
      textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex);
    // return a negative offset
    return -this._textLines[lineIndex - 1].length + indexOnOtherLine - textBeforeCursor.length;
  },

  /**
   * find for a given width it founds the matching character.
   * @private
   */
  _getIndexOnLine: function (lineIndex, width) {

    var widthOfLine = this._getLineWidth(this.ctx, lineIndex),
      textOnLine = this._textLines[lineIndex],
      lineLeftOffset = this._getLineLeftOffset(widthOfLine),
      widthOfCharsOnLine = lineLeftOffset,
      indexOnLine = 0,
      foundMatch;

    for (var j = 0, jlen = textOnLine.length; j < jlen; j++) {

      var _char = textOnLine[j],
        widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);

      widthOfCharsOnLine += widthOfChar;

      if (widthOfCharsOnLine > width) {

        foundMatch = true;

        var leftEdge = widthOfCharsOnLine - widthOfChar,
          rightEdge = widthOfCharsOnLine,
          offsetFromLeftEdge = Math.abs(leftEdge - width),
          offsetFromRightEdge = Math.abs(rightEdge - width);

        indexOnLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1);

        break;
      }
    }

    // reached end
    if (!foundMatch) {
      indexOnLine = textOnLine.length - 1;
    }

    return indexOnLine;
  },


  /**
   * Moves cursor down
   * @param {Event} e Event object
   */
  moveCursorDown: function (e) {
    if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) {
      return;
    }
    this._moveCursorUpOrDown('Down', e);
  },

  /**
   * Moves cursor up
   * @param {Event} e Event object
   */
  moveCursorUp: function (e) {
    if (this.selectionStart === 0 && this.selectionEnd === 0) {
      return;
    }
    this._moveCursorUpOrDown('Up', e);
  },

  /**
   * Moves cursor up or down, fires the events
   * @param {String} direction 'Up' or 'Down'
   * @param {Event} e Event object
   */
  _moveCursorUpOrDown: function (direction, e) {
    // getUpCursorOffset
    // getDownCursorOffset
    var action = 'get' + direction + 'CursorOffset',
      offset = this[action](e, this._selectionDirection === 'right');
    if (e.shiftKey) {
      this.moveCursorWithShift(offset);
    }
    else {
      this.moveCursorWithoutShift(offset);
    }
    if (offset !== 0) {
      this.setSelectionInBoundaries();
      this.abortCursorAnimation();
      this._currentCursorOpacity = 1;
      this.initDelayedCursor();
      this._fireSelectionChanged();
      this._updateTextarea();
    }
  },

  /**
   * Moves cursor with shift
   * @param {Number} offset
   */
  moveCursorWithShift: function (offset) {
    var newSelection = this._selectionDirection === 'left'
      ? this.selectionStart + offset
      : this.selectionEnd + offset;
    this.setSelectionStartEndWithShift(this.selectionStart, this.selectionEnd, newSelection);
    return offset !== 0;
  },

  /**
   * Moves cursor up without shift
   * @param {Number} offset
   */
  moveCursorWithoutShift: function (offset) {
    if (offset < 0) {
      this.selectionStart += offset;
      this.selectionEnd = this.selectionStart;
    }
    else {
      this.selectionEnd += offset;
      this.selectionStart = this.selectionEnd;
    }
    return offset !== 0;
  },

  /**
   * Moves cursor left
   * @param {Event} e Event object
   */
  moveCursorLeft: function (e) {
    if (this.selectionStart === 0 && this.selectionEnd === 0) {
      return;
    }
    this._moveCursorLeftOrRight('Left', e);
  },

  /**
   * @private
   * @return {Boolean} true if a change happened
   */
  _move: function (e, prop, direction) {
    var newValue;
    if (e.altKey) {
      newValue = this['findWordBoundary' + direction](this[prop]);
    }
    else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36) {
      newValue = this['findLineBoundary' + direction](this[prop]);
    }
    else {
      this[prop] += direction === 'Left' ? -1 : 1;
      return true;
    }
    if (typeof newValue !== undefined && this[prop] !== newValue) {
      this[prop] = newValue;
      return true;
    }
  },

  /**
   * @private
   */
  _moveLeft: function (e, prop) {
    return this._move(e, prop, 'Left');
  },

  /**
   * @private
   */
  _moveRight: function (e, prop) {
    return this._move(e, prop, 'Right');
  },

  /**
   * Moves cursor left without keeping selection
   * @param {Event} e
   */
  moveCursorLeftWithoutShift: function (e) {
    var change = true;
    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.selectionStart !== 0) {
      change = this._moveLeft(e, 'selectionStart');

    }
    this.selectionEnd = this.selectionStart;
    return change;
  },

  /**
   * Moves cursor left while keeping selection
   * @param {Event} e
   */
  moveCursorLeftWithShift: function (e) {
    if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) {
      return this._moveLeft(e, 'selectionEnd');
    }
    else if (this.selectionStart !== 0) {
      this._selectionDirection = 'left';
      return this._moveLeft(e, 'selectionStart');
    }
  },

  /**
   * Moves cursor right
   * @param {Event} e Event object
   */
  moveCursorRight: function (e) {
    if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) {
      return;
    }
    this._moveCursorLeftOrRight('Right', e);
  },

  /**
   * Moves cursor right or Left, fires event
   * @param {String} direction 'Left', 'Right'
   * @param {Event} e Event object
   */
  _moveCursorLeftOrRight: function (direction, e) {
    var actionName = 'moveCursor' + direction + 'With';
    this._currentCursorOpacity = 1;

    if (e.shiftKey) {
      actionName += 'Shift';
    }
    else {
      actionName += 'outShift';
    }
    if (this[actionName](e)) {
      this.abortCursorAnimation();
      this.initDelayedCursor();
      this._fireSelectionChanged();
      this._updateTextarea();
    }
  },

  /**
   * Moves cursor right while keeping selection
   * @param {Event} e
   */
  moveCursorRightWithShift: function (e) {
    if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) {
      return this._moveRight(e, 'selectionStart');
    }
    else if (this.selectionEnd !== this.text.length) {
      this._selectionDirection = 'right';
      return this._moveRight(e, 'selectionEnd');
    }
  },

  /**
   * Moves cursor right without keeping selection
   * @param {Event} e Event object
   */
  moveCursorRightWithoutShift: function (e) {
    var changed = true;
    this._selectionDirection = 'right';

    if (this.selectionStart === this.selectionEnd) {
      changed = this._moveRight(e, 'selectionStart');
      this.selectionEnd = this.selectionStart;
    }
    else {
      this.selectionStart = this.selectionEnd;
    }
    return changed;
  },

  /**
   * Removes characters selected by selection
   * @param {Event} e Event object
   */
  removeChars: function (e) {
    if (this.selectionStart === this.selectionEnd) {
      this._removeCharsNearCursor(e);
    }
    else {
      this._removeCharsFromTo(this.selectionStart, this.selectionEnd);
    }

    this.setSelectionEnd(this.selectionStart);

    this._removeExtraneousStyles();

    this.canvas && this.canvas.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) {
      return;
    }
    if (e.metaKey) {
      // remove all till the start of current line
      var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart);

      this._removeCharsFromTo(leftLineBoundary, this.selectionStart);
      this.setSelectionStart(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.setSelectionStart(leftWordBoundary);
    }
    else {
      this._removeSingleCharAndStyle(this.selectionStart);
      this.setSelectionStart(this.selectionStart - 1);
    }
  }
});


/* _TO_SVG_START_ */
(function () {
  var toFixed = fabric.util.toFixed,
    NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;

  fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {

    /**
     * @private
     */
    _setSVGTextLineText: function (lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) {
      if (!this._getLineStyle(lineIndex)) {
        fabric.Text.prototype._setSVGTextLineText.call(this,
          lineIndex, textSpans, height, textLeftOffset, textTopOffset);
      }
      else {
        this._setSVGTextLineChars(
          lineIndex, textSpans, height, textLeftOffset, textBgRects);
      }
    },

    /**
     * @private
     */
    _setSVGTextLineChars: function (lineIndex, textSpans, height, textLeftOffset, textBgRects) {

      var chars = this._textLines[lineIndex],
        charOffset = 0,
        lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex)) - this.width / 2,
        lineOffset = this._getSVGLineTopOffset(lineIndex),
        heightOfLine = this._getHeightOfLine(this.ctx, lineIndex);

      for (var i = 0, len = chars.length; i < len; i++) {
        var styleDecl = this._getStyleDeclaration(lineIndex, i) || {};

        textSpans.push(
          this._createTextCharSpan(
            chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset));

        var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i);

        if (styleDecl.textBackgroundColor) {
          textBgRects.push(
            this._createTextCharBg(
              styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset));
        }

        charOffset += charWidth;
      }
    },

    /**
     * @private
     */
    _getSVGLineTopOffset: function (lineIndex) {
      var lineTopOffset = 0, lastHeight = 0;
      for (var j = 0; j < lineIndex; j++) {
        lineTopOffset += this._getHeightOfLine(this.ctx, j);
      }
      lastHeight = this._getHeightOfLine(this.ctx, j);
      return {
        lineTop: lineTopOffset,
        offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult)
      };
    },

    /**
     * @private
     */
    _createTextCharBg: function (styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) {
      return [
        '\t\t<rect fill="', styleDecl.textBackgroundColor,
        '" x="', toFixed(lineLeftOffset + charOffset, NUM_FRACTION_DIGITS),
        '" y="', toFixed(lineTopOffset - this.height / 2, NUM_FRACTION_DIGITS),
        '" width="', toFixed(charWidth, NUM_FRACTION_DIGITS),
        '" height="', toFixed(heightOfLine / this.lineHeight, NUM_FRACTION_DIGITS),
        '"></rect>\n'
      ].join('');
    },

    /**
     * @private
     */
    _createTextCharSpan: function (_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) {

      var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({
        visible: true,
        fill: this.fill,
        stroke: this.stroke,
        type: 'text',
        getSvgFilter: fabric.Object.prototype.getSvgFilter
      }, styleDecl));

      return [
        '\t\t\t<tspan x="', toFixed(lineLeftOffset + charOffset, NUM_FRACTION_DIGITS), '" y="',
        toFixed(lineTopOffset - this.height / 2, NUM_FRACTION_DIGITS), '" ',
        (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>\n'
      ].join('');
    }
  });
})();
/* _TO_SVG_END_ */


(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    clone = fabric.util.object.clone;

  /**
   * Textbox class, based on IText, allows the user to resize the text rectangle
   * and wraps lines automatically. Textboxes have their Y scaling locked, the
   * user can only change width. Height is adjusted automatically based on the
   * wrapping of lines.
   * @class fabric.Textbox
   * @extends fabric.IText
   * @mixes fabric.Observable
   * @return {fabric.Textbox} thisArg
   * @see {@link fabric.Textbox#initialize} for constructor definition
   */
  fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, {

    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'textbox',

    /**
     * Minimum width of textbox, in pixels.
     * @type Number
     * @default
     */
    minWidth: 20,

    /**
     * Minimum calculated width of a textbox, in pixels.
     * fixed to 2 so that an empty textbox cannot go to 0
     * and is still selectable without text.
     * @type Number
     * @default
     */
    dynamicMinWidth: 2,

    /**
     * Cached array of text wrapping.
     * @type Array
     */
    __cachedLines: null,

    /**
     * Override standard Object class values
     */
    lockScalingY: true,

    /**
     * Override standard Object class values
     */
    lockScalingFlip: true,

    /**
     * Override standard Object class values
     * Textbox needs this on false
     */
    noScaleCache: false,

    /**
     * Constructor. Some scaling related property values are forced. Visibility
     * of controls is also fixed; only the rotation and width controls are
     * made available.
     * @param {String} text Text string
     * @param {Object} [options] Options object
     * @return {fabric.Textbox} thisArg
     */
    initialize: function (text, options) {

      this.callSuper('initialize', text, options);
      this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
      this.ctx = this.objectCaching ? this._cacheContext : fabric.util.createCanvasElement().getContext('2d');
      // add width to this list of props that effect line wrapping.
      this._dimensionAffectingProps.push('width');
    },

    /**
     * Unlike superclass's version of this function, Textbox does not update
     * its width.
     * @param {CanvasRenderingContext2D} ctx Context to use for measurements
     * @private
     * @override
     */
    _initDimensions: function (ctx) {
      if (this.__skipDimension) {
        return;
      }

      if (!ctx) {
        ctx = fabric.util.createCanvasElement().getContext('2d');
        this._setTextStyles(ctx);
        this.clearContextTop();
      }

      // clear dynamicMinWidth as it will be different after we re-wrap line
      this.dynamicMinWidth = 0;

      // wrap lines
      this._textLines = this._splitTextIntoLines(ctx);
      // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap
      if (this.dynamicMinWidth > this.width) {
        this._set('width', this.dynamicMinWidth);
      }

      // clear cache and re-calculate height
      this._clearCache();
      this.height = this._getTextHeight(ctx);
    },

    /**
     * Generate an object that translates the style object so that it is
     * broken up by visual lines (new lines and automatic wrapping).
     * The original text styles object is broken up by actual lines (new lines only),
     * which is only sufficient for Text / IText
     * @private
     */
    _generateStyleMap: function () {
      var realLineCount = 0,
        realLineCharCount = 0,
        charCount = 0,
        map = {};

      for (var i = 0; i < this._textLines.length; i++) {
        if (this.text[charCount] === '\n' && i > 0) {
          realLineCharCount = 0;
          charCount++;
          realLineCount++;
        }
        else if (this.text[charCount] === ' ' && i > 0) {
          // this case deals with space's that are removed from end of lines when wrapping
          realLineCharCount++;
          charCount++;
        }

        map[i] = { line: realLineCount, offset: realLineCharCount };

        charCount += this._textLines[i].length;
        realLineCharCount += this._textLines[i].length;
      }

      return map;
    },

    /**
     * @param {Number} lineIndex
     * @param {Number} charIndex
     * @param {Boolean} [returnCloneOrEmpty=false]
     * @private
     */
    _getStyleDeclaration: function (lineIndex, charIndex, returnCloneOrEmpty) {
      if (this._styleMap) {
        var map = this._styleMap[lineIndex];
        if (!map) {
          return returnCloneOrEmpty ? {} : null;
        }
        lineIndex = map.line;
        charIndex = map.offset + charIndex;
      }
      return this.callSuper('_getStyleDeclaration', lineIndex, charIndex, returnCloneOrEmpty);
    },

    /**
     * @param {Number} lineIndex
     * @param {Number} charIndex
     * @param {Object} style
     * @private
     */
    _setStyleDeclaration: function (lineIndex, charIndex, style) {
      var map = this._styleMap[lineIndex];
      lineIndex = map.line;
      charIndex = map.offset + charIndex;

      this.styles[lineIndex][charIndex] = style;
    },

    /**
     * @param {Number} lineIndex
     * @param {Number} charIndex
     * @private
     */
    _deleteStyleDeclaration: function (lineIndex, charIndex) {
      var map = this._styleMap[lineIndex];
      lineIndex = map.line;
      charIndex = map.offset + charIndex;

      delete this.styles[lineIndex][charIndex];
    },

    /**
     * @param {Number} lineIndex
     * @private
     */
    _getLineStyle: function (lineIndex) {
      var map = this._styleMap[lineIndex];
      return this.styles[map.line];
    },

    /**
     * @param {Number} lineIndex
     * @param {Object} style
     * @private
     */
    _setLineStyle: function (lineIndex, style) {
      var map = this._styleMap[lineIndex];
      this.styles[map.line] = style;
    },

    /**
     * @param {Number} lineIndex
     * @private
     */
    _deleteLineStyle: function (lineIndex) {
      var map = this._styleMap[lineIndex];
      delete this.styles[map.line];
    },

    /**
     * Wraps text using the 'width' property of Textbox. First this function
     * splits text on newlines, so we preserve newlines entered by the user.
     * Then it wraps each line using the width of the Textbox by calling
     * _wrapLine().
     * @param {CanvasRenderingContext2D} ctx Context to use for measurements
     * @param {String} text The string of text that is split into lines
     * @returns {Array} Array of lines
     */
    _wrapText: function (ctx, text) {
      var lines = text.split(this._reNewline), wrapped = [], i;

      for (i = 0; i < lines.length; i++) {
        wrapped = wrapped.concat(this._wrapLine(ctx, lines[i], i));
      }

      return wrapped;
    },

    /**
     * Helper function to measure a string of text, given its lineIndex and charIndex offset
     *
     * @param {CanvasRenderingContext2D} ctx
     * @param {String} text
     * @param {number} lineIndex
     * @param {number} charOffset
     * @returns {number}
     * @private
     */
    _measureText: function (ctx, text, lineIndex, charOffset) {
      var width = 0;
      charOffset = charOffset || 0;
      for (var i = 0, len = text.length; i < len; i++) {
        width += this._getWidthOfChar(ctx, text[i], lineIndex, i + charOffset);
      }
      return width;
    },

    /**
     * Wraps a line of text using the width of the Textbox and a context.
     * @param {CanvasRenderingContext2D} ctx Context to use for measurements
     * @param {String} text The string of text to split into lines
     * @param {Number} lineIndex
     * @returns {Array} Array of line(s) into which the given text is wrapped
     * to.
     */
    _wrapLine: function (ctx, text, lineIndex) {
      var lineWidth = 0,
        lines = [],
        line = '',
        words = text.split(' '),
        word = '',
        offset = 0,
        infix = ' ',
        wordWidth = 0,
        infixWidth = 0,
        largestWordWidth = 0,
        lineJustStarted = true,
        additionalSpace = this._getWidthOfCharSpacing();

      for (var i = 0; i < words.length; i++) {
        word = words[i];
        wordWidth = this._measureText(ctx, word, lineIndex, offset);

        offset += word.length;

        lineWidth += infixWidth + wordWidth - additionalSpace;

        if (lineWidth >= this.width && !lineJustStarted) {
          lines.push(line);
          line = '';
          lineWidth = wordWidth;
          lineJustStarted = true;
        }
        else {
          lineWidth += additionalSpace;
        }

        if (!lineJustStarted) {
          line += infix;
        }
        line += word;

        infixWidth = this._measureText(ctx, infix, lineIndex, offset);
        offset++;
        lineJustStarted = false;
        // keep track of largest word
        if (wordWidth > largestWordWidth) {
          largestWordWidth = wordWidth;
        }
      }

      i && lines.push(line);

      if (largestWordWidth > this.dynamicMinWidth) {
        this.dynamicMinWidth = largestWordWidth - additionalSpace;
      }

      return lines;
    },
    /**
     * Gets lines of text to render in the Textbox. This function calculates
     * text wrapping on the fly everytime it is called.
     * @returns {Array} Array of lines in the Textbox.
     * @override
     */
    _splitTextIntoLines: function (ctx) {
      ctx = ctx || this.ctx;
      var originalAlign = this.textAlign;
      ctx.save();
      this._setTextStyles(ctx);
      this.textAlign = 'left';
      var lines = this._wrapText(ctx, this.text);
      this.textAlign = originalAlign;
      ctx.restore();
      this._textLines = lines;
      this._styleMap = this._generateStyleMap();
      return lines;
    },

    /**
     * When part of a group, we don't want the Textbox's scale to increase if
     * the group's increases. That's why we reduce the scale of the Textbox by
     * the amount that the group's increases. This is to maintain the effective
     * scale of the Textbox at 1, so that font-size values make sense. Otherwise
     * the same font-size value would result in different actual size depending
     * on the value of the scale.
     * @param {String} key
     * @param {*} value
     */
    setOnGroup: function (key, value) {
      if (key === 'scaleX') {
        this.set('scaleX', Math.abs(1 / value));
        this.set('width', (this.get('width') * value) /
          (typeof this.__oldScaleX === 'undefined' ? 1 : this.__oldScaleX));
        this.__oldScaleX = value;
      }
    },

    /**
     * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start).
     * Overrides the superclass function to take into account text wrapping.
     *
     * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used.
     */
    get2DCursorLocation: function (selectionStart) {
      if (typeof selectionStart === 'undefined') {
        selectionStart = this.selectionStart;
      }

      var numLines = this._textLines.length,
        removed = 0;

      for (var i = 0; i < numLines; i++) {
        var line = this._textLines[i],
          lineLen = line.length;

        if (selectionStart <= removed + lineLen) {
          return {
            lineIndex: i,
            charIndex: selectionStart - removed
          };
        }

        removed += lineLen;

        if (this.text[removed] === '\n' || this.text[removed] === ' ') {
          removed++;
        }
      }

      return {
        lineIndex: numLines - 1,
        charIndex: this._textLines[numLines - 1].length
      };
    },

    /**
     * Overrides superclass function and uses text wrapping data to get cursor
     * boundary offsets instead of the array of chars.
     * @param {Array} chars Unused
     * @param {String} typeOfBoundaries Can be 'cursor' or 'selection'
     * @returns {Object} Object with 'top', 'left', and 'lineLeft' properties set.
     */
    _getCursorBoundariesOffsets: function (chars, typeOfBoundaries) {
      var topOffset = 0,
        leftOffset = 0,
        cursorLocation = this.get2DCursorLocation(),
        lineChars = this._textLines[cursorLocation.lineIndex].split(''),
        lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, cursorLocation.lineIndex));

      for (var i = 0; i < cursorLocation.charIndex; i++) {
        leftOffset += this._getWidthOfChar(this.ctx, lineChars[i], cursorLocation.lineIndex, i);
      }

      for (i = 0; i < cursorLocation.lineIndex; i++) {
        topOffset += this._getHeightOfLine(this.ctx, i);
      }

      if (typeOfBoundaries === 'cursor') {
        topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, cursorLocation.lineIndex)
          / this.lineHeight - this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex)
          * (1 - this._fontSizeFraction);
      }

      return {
        top: topOffset,
        left: leftOffset,
        lineLeft: lineLeftOffset
      };
    },

    getMinWidth: function () {
      return Math.max(this.minWidth, this.dynamicMinWidth);
    },

    /**
     * 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 this.callSuper('toObject', ['minWidth'].concat(propertiesToInclude));
    }
  });
  /**
   * Returns fabric.Textbox instance from an object representation
   * @static
   * @memberOf fabric.Textbox
   * @param {Object} object Object to create an instance from
   * @param {Function} [callback] Callback to invoke when an fabric.Textbox instance is created
   * @return {fabric.Textbox} instance of fabric.Textbox
   */
  fabric.Textbox.fromObject = function (object, callback) {
    var textbox = new fabric.Textbox(object.text, clone(object));
    callback && callback(textbox);
    return textbox;
  };
  /**
   * Returns the default controls visibility required for Textboxes.
   * @returns {Object}
   */
  fabric.Textbox.getTextboxControlVisibility = function () {
    return {
      tl: false,
      tr: false,
      br: false,
      bl: false,
      ml: true,
      mt: false,
      mr: true,
      mb: false,
      mtr: true
    };
  };

})(typeof exports !== 'undefined' ? exports : this);


(function () {

  /**
   * Override _setObjectScale and add Textbox specific resizing behavior. Resizing
   * a Textbox doesn't scale text, it only changes width and makes text wrap automatically.
   */
  var setObjectScaleOverridden = fabric.Canvas.prototype._setObjectScale;

  fabric.Canvas.prototype._setObjectScale = function (localMouse, transform,
    lockScalingX, lockScalingY, by, lockScalingFlip, _dim) {

    var t = transform.target;
    if (t instanceof fabric.Textbox) {
      var w = t.width * ((localMouse.x / transform.scaleX) / (t.width + t.strokeWidth));
      if (w >= t.getMinWidth()) {
        t.set('width', w);
        return true;
      }
    }
    else {
      return setObjectScaleOverridden.call(fabric.Canvas.prototype, localMouse, transform,
        lockScalingX, lockScalingY, by, lockScalingFlip, _dim);
    }
  };

  /**
   * Sets controls of this group to the Textbox's special configuration if
   * one is present in the group. Deletes _controlsVisibility otherwise, so that
   * it gets initialized to default value at runtime.
   */
  fabric.Group.prototype._refreshControlsVisibility = function () {
    if (typeof fabric.Textbox === 'undefined') {
      return;
    }
    for (var i = this._objects.length; i--;) {
      if (this._objects[i] instanceof fabric.Textbox) {
        this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
        return;
      }
    }
  };

  var clone = fabric.util.object.clone;

  fabric.util.object.extend(fabric.Textbox.prototype, /** @lends fabric.IText.prototype */ {
    /**
     * @private
     */
    _removeExtraneousStyles: function () {
      for (var prop in this._styleMap) {
        if (!this._textLines[prop]) {
          delete this.styles[this._styleMap[prop].line];
        }
      }
    },

    /**
     * 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) {
      // adjust lineIndex and charIndex
      var map = this._styleMap[lineIndex];
      lineIndex = map.line;
      charIndex = map.offset + charIndex;

      fabric.IText.prototype.insertCharStyleObject.apply(this, [lineIndex, charIndex, style]);
    },

    /**
     * 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) {
      // adjust lineIndex and charIndex
      var map = this._styleMap[lineIndex];
      lineIndex = map.line;
      charIndex = map.offset + charIndex;

      fabric.IText.prototype.insertNewlineStyleObject.apply(this, [lineIndex, charIndex, isEndOfLine]);
    },

    /**
     * Shifts line styles up or down. This function is slightly different than the one in
     * itext_behaviour as it takes into account the styleMap.
     *
     * @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),
        map = this._styleMap[lineIndex];

      // adjust line index
      lineIndex = map.line;

      for (var line in this.styles) {
        var numericLine = parseInt(line, 10);

        if (numericLine > lineIndex) {
          this.styles[numericLine + offset] = clonedStyles[numericLine];

          if (!clonedStyles[numericLine - offset]) {
            delete this.styles[numericLine];
          }
        }
      }
      //TODO: evaluate if delete old style lines with offset -1
    },

    /**
     * Figure out programatically the text on previous actual line (actual = separated by \n);
     *
     * @param {Number} lIndex
     * @returns {String}
     * @private
     */
    _getTextOnPreviousLine: function (lIndex) {
      var textOnPreviousLine = this._textLines[lIndex - 1];

      while (this._styleMap[lIndex - 2] && this._styleMap[lIndex - 2].line === this._styleMap[lIndex - 1].line) {
        textOnPreviousLine = this._textLines[lIndex - 2] + textOnPreviousLine;

        lIndex--;
      }

      return textOnPreviousLine;
    },

    /**
     * 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),
        map = this._styleMap[cursorLocation.lineIndex],
        lineIndex = map.line,
        charIndex = map.offset + cursorLocation.charIndex;
      this._removeStyleObject(isBeginningOfLine, cursorLocation, lineIndex, charIndex);
    }
  });
})();


(function () {
  var override = fabric.IText.prototype._getNewSelectionStartFromOffset;
  /**
   * Overrides the IText implementation and adjusts character index as there is not always a linebreak
   *
   * @param {Number} mouseOffset
   * @param {Number} prevWidth
   * @param {Number} width
   * @param {Number} index
   * @param {Number} jlen
   * @returns {Number}
   */
  fabric.IText.prototype._getNewSelectionStartFromOffset = function (mouseOffset, prevWidth, width, index, jlen) {
    index = override.call(this, mouseOffset, prevWidth, width, index, jlen);

    // the index passed into the function is padded by the amount of lines from _textLines (to account for \n)
    // we need to remove this padding, and pad it by actual lines, and / or spaces that are meant to be there
    var tmp = 0,
      removed = 0;

    // account for removed characters
    for (var i = 0; i < this._textLines.length; i++) {
      tmp += this._textLines[i].length;

      if (tmp + removed >= index) {
        break;
      }

      if (this.text[tmp + removed] === '\n' || this.text[tmp + removed] === ' ') {
        removed++;
      }
    }

    return index - i + removed;
  };
})();


(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.protocol.indexOf('https:') === 0) ? 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);
      }
      callback(null);
    });

    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) {
      if (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);
      }
      else {
        img = null;
        callback && callback.call(context, null, true);
      }
    }
    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) {
      // eslint-disable-next-line no-eval
      eval(body);
      callback && callback();
    });
  };

  // fabric.util.createCanvasElement = function(_, width, height) {
  //   return new Canvas(width, height);
  // }

  /**
   * Only available when running fabric on node.js
   * @param {Number} width Canvas width
   * @param {Number} height Canvas height
   * @param {Object} [options] Options to pass to FabricCanvas.
   * @param {Object} [nodeCanvasOptions] 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),
      nodeCacheCanvas = 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;
    options = options || {};
    options.nodeCanvas = nodeCanvas;
    options.nodeCacheCanvas = nodeCacheCanvas;
    var FabricCanvas = fabric.Canvas || fabric.StaticCanvas,
      fabricCanvas = new FabricCanvas(canvasEl, options);
    fabricCanvas.nodeCanvas = nodeCanvas;
    fabricCanvas.nodeCacheCanvas = nodeCacheCanvas;
    fabricCanvas.contextContainer = nodeCanvas.getContext('2d');
    fabricCanvas.contextCache = nodeCacheCanvas.getContext('2d');
    fabricCanvas.Font = Canvas.Font;
    return fabricCanvas;
  };

  var originaInitStatic = fabric.StaticCanvas.prototype._initStatic;
  fabric.StaticCanvas.prototype._initStatic = function (el, options) {
    el = el || fabric.document.createElement('canvas');
    this.nodeCanvas = new Canvas(el.width, el.height);
    this.nodeCacheCanvas = new Canvas(el.width, el.height);
    originaInitStatic.call(this, el, options);
    this.contextContainer = this.nodeCanvas.getContext('2d');
    this.contextCache = this.nodeCacheCanvas.getContext('2d');
    this.Font = Canvas.Font;
  }

  /** @ignore */
  fabric.StaticCanvas.prototype.createPNGStream = function () {
    return this.nodeCanvas.createPNGStream();
  };

  fabric.StaticCanvas.prototype.createJPEGStream = function (opts) {
    return this.nodeCanvas.createJPEGStream(opts);
  };

  fabric.StaticCanvas.prototype._initRetinaScaling = function () {
    if (!this._isRetinaScaling()) {
      return;
    }

    this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio);
    this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio);
    this.nodeCanvas.width = this.width * fabric.devicePixelRatio;
    this.nodeCanvas.height = this.height * fabric.devicePixelRatio;
    this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio);
    return this;
  };
  if (fabric.Canvas) {
    fabric.Canvas.prototype._initRetinaScaling = fabric.StaticCanvas.prototype._initRetinaScaling;
  }

  var origSetBackstoreDimension = fabric.StaticCanvas.prototype._setBackstoreDimension;
  fabric.StaticCanvas.prototype._setBackstoreDimension = function (prop, value) {
    origSetBackstoreDimension.call(this, prop, value);
    this.nodeCanvas[prop] = value;
    return this;
  };
  if (fabric.Canvas) {
    fabric.Canvas.prototype._setBackstoreDimension = fabric.StaticCanvas.prototype._setBackstoreDimension;
  }

})();


DrawerJs = {

  /**
   * All available tool options
   * @memberof DrawerJs
   * @namespace options
   */


  /**
   * Global namespace for all DrawerJs plugins.
   *
   * @memberof DrawerJs
   * @namespace DrawerJs.plugins
   */
  plugins: {},

  /**
   * Global namespace for all DrawerJs utility functions.
   *
   * @memberof DrawerJs
   * @namespace DrawerJs.util
   */
  util: {},

  /**
   * Global namespace for all DrawerJs utility plugins.
   *
   * @memberof DrawerJs
   * @namespace DrawerJs.utilPlugins
   */
  utilPlugins: {},

  /**
   * All drawer brush classes.
   *
   * @memberof DrawerJs
   * @namespace DrawerJs.brushes
   */
  brushes: {}
};

DrawerJs.texts = {
  'Add Drawer': 'Add Drawer',
  'Insert Drawer': 'Insert Drawer',
  'Insert': 'Insert',
  'Free drawing mode': 'Free drawing mode',
  'SimpleWhiteEraser': 'SimpleWhiteEraser',
  'Eraser': 'Eraser',
  'Delete this canvas': 'Delete this canvas',
  'Are you sure want to delete this canvas?': 'Are you sure want to delete this canvas?',

  // canvas properties popup
  'Size (px)': 'Size (px)',
  'Position': 'Position',
  'Inline': 'Inline',
  'Left': 'Left',
  'Center': 'Center',
  'Right': 'Right',
  'Floating': 'Floating',
  'Canvas properties': 'Canvas properties',
  'Background': 'Background',
  'transparent': 'transparent',
  'Cancel': 'Cancel',
  'Save': 'Save',

  // Fullscreen plugin
  'Enter fullscreen mode': 'Enter fullscreen mode',
  'Exit fullscreen mode': 'Exit fullscreen mode',

  // shape context menu plugin
  'Bring forward': 'Bring forward',
  'Send backwards': 'Send backwards',
  'Bring to front': 'Bring to front',
  'Send to back': 'Send to back',
  'Duplicate': 'Duplicate',
  'Remove': 'Remove',

  // brush size plugin
  'Size:': 'Size:',

  // colorpicker plugin
  'Fill:': 'Fill:',
  'Transparent': 'Transparent',

  // shape border plugin
  'Border:': 'Border:',
  'None': 'None',

  // arrow plugin
  'Draw an arrow': 'Draw an arrow',
  'Draw a two-sided arrow': 'Draw a two-sided arrow',
  'Lines and arrows': 'Lines and arrows',

  // circle plugin
  'Draw a circle': 'Draw a circle',

  // line plugin
  'Draw a line': 'Draw a line',

  // rectangle plugin
  'Draw a rectangle': 'Draw a rectangle',

  // triangle plugin
  'Draw a triangle': 'Draw a triangle',

  // polygon plugin
  'Draw a Polygon': 'Draw a Polygon',
  'Stop drawing a polygon': 'Stop drawing a polygon (esc)',
  'Click to start a new line': 'Click to start a new line',

  // text plugin
  'Draw a text': 'Draw a text',
  'Click to place a text': 'Click to place a text',
  'Font:': 'Font:',

  // movable floating mode plugin
  'Move canvas': 'Move canvas',

  // base shape
  'Click to start drawing a ': 'Click to start drawing a '
};

(function (namespace, util) {

  'use strict';

  var getPointer = fabric.util.getPointer,
    degreesToRadians = fabric.util.degreesToRadians,
    radiansToDegrees = fabric.util.radiansToDegrees,
    atan2 = Math.atan2,
    abs = Math.abs;

  /**
   * Fabricjs canvas class with customizations to allow
   * eraser paths, fixed rotations etc.
   *
   * @constructor
   * @memberof DrawerJs
   */
  namespace.Canvas = fabric.util.createClass(fabric.Canvas, /** @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 ? options : {};
      this.preserveObjectStacking = true;

      this.callSuper('initialize', el, options);
    },


    disableSelection: function () {
      var obj = null;
      var objects = this.getObjects();
      this.deactivateAll();

      for (var i = 0; i < objects.length; i++) {
        obj = objects[i];
        if (obj.__evented === undefined) {
          obj.__evented = obj.get('evented');
          obj.set('evented', false);
        }

        if (obj.__selectable === undefined) {
          obj.__selectable = obj.get('selectable');
          obj.set('selectable', false);
        }
      }
    },

    restoreSelection: function () {
      var obj = null;
      var objects = this.getObjects();

      for (var i = 0; i < objects.length; i++) {
        obj = objects[i];

        if (obj.__evented !== undefined) {
          obj.set('evented', obj.__evented);
          delete obj.__evented;
        }

        if (obj.__selectable !== undefined) {
          obj.set('selectable', obj.__selectable);
          delete obj.__selectable;
        }
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     * @param {Array} objectsToRender
     */
    _renderObjects: function (ctx, objectsToRender) {
      var i, length, obj;

      var tempCanvas = util.getTemporaryCanvas(this);
      var tempContext = tempCanvas.getContext('2d');
      tempContext.clearRect(0, 0, this.width, this.height);

      this.eraserPaths = [];

      for (i = 0, length = objectsToRender.length; i < length; ++i) {
        obj = objectsToRender[i];

        // we do not render eraser paths
        if (obj instanceof fabric.EraserPath)
          continue;

        // obj.render(tempContext);
        obj.render(ctx);
      }

      // ctx.drawImage(tempCanvas, 0, 0);
      // tempContext.clearRect(0, 0, this.width, this.height);

    },


    _applyEraserPath: function (pathObject, ctx) {
      pathObject.visible = true;
      pathObject.globalCompositeOperation = 'destination-out';
      pathObject.render(ctx);
      pathObject.visible = false;
    },

    /**
     * @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) {

          if (this._objects[i] instanceof fabric.EraserPath) {
            continue;
          }

          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;
    },
    /**
     * @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) {

          if (this._objects[i] instanceof fabric.EraserPath) {
            continue;
          }

          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;
    },

    /**
     * This override is needed to properly call object.set() method
     * instead of simple assigment.
     *
     * @private
     * @param {Number} x pointer's x coordinate
     * @param {Number} y pointer's y coordinate
     *
     * @return {boolean} always returns true, except case when _currentTransform.get('lockRotation') returns true
     */
    _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.set('angle', angle % 360);
      return true;
    }
  });
})(DrawerJs, DrawerJs.util);
(function ($, utilNamespace) {
  'use strict';

  /**
   * Contains url to a folder of drawer files.
   *
   * @type {null|string}
   * @private
   */
  var _drawerFolderUrl = null;

  utilNamespace.setDrawerFolderUrl = function (url) {
    _drawerFolderUrl = url.replace(/\/*$/g, '') + '/';
  };

  /**
   * This function finds an url from which drawer file was loaded
   *
   * @function
   * @memberof DrawerJs.util
   * @returns {*}
   */
  utilNamespace.getDrawerFolderUrl = function () {
    if (_drawerFolderUrl) {
      return _drawerFolderUrl;
    }

    // try to find a folder from which this script is included
    var scripts = document.getElementsByTagName("script");
    var drawerJsFilenamePattern = /dist\/(drawer.+\.js)+$/;

    for (var i = 0; i < scripts.length; i++) {
      var s = scripts.item(i);

      if (s.src) {
        var match = s.src.match(drawerJsFilenamePattern);
        if (match) {
          var pathToDrawerFolder = s.src.replace(match[1], '');
          return pathToDrawerFolder;
        }
      }
    }

    return '';
  };

  /**
   * Removes all click events with specified namespace bound to element.
   *
   * @param {jQuery} element
   * @param {String} namespace
   */
  utilNamespace.unbindClick = function (element, namespace) {
    var ns = namespace + 'drawerBindClick';

    $(element).off('click.' + ns);
    $(element).off('touchstart.' + ns);
    $(element).off('touchend.' + ns);
  };

  utilNamespace.bindClick = function (element, namespace, handler) {
    var ns = namespace + 'drawerBindClick';

    $(element).on('click.' + ns, function (event) {
      var elem = this;
      var result = null;

      if (elem.__lastClickTime) {
        var lastClickDiff = Date.now() - elem.__lastClickTime;
        if (lastClickDiff > 500) {
          try {
            result = handler.apply(elem, [event]);
          } catch (err) {
            var errorName = 'Catched error - ' + 'click.' + ns;
            console.groupCollapsed(errorName);
            console.log('%c[' + 'Event name' + ']', 'color: green', 'click.' + ns);
            console.log('%c[' + 'Element' + ']', 'color: green', element);
            console.error(err);
            console.groupEnd(errorName);
          }

          if (result === false) {
            event.stopPropagation();
            event.preventDefault();
            return false;
          }
        } else {
          // seems that we have already triggered this click on touchend event
        }
      } else {
        try {
          result = handler.apply(elem, [event]);
        } catch (err) {
          var errorName = 'Catched error - ' + 'click.' + ns;
          console.groupCollapsed(errorName);
          console.log('%c[' + 'Event name' + ']', 'color: green', 'click.' + ns);
          console.log('%c[' + 'Element' + ']', 'color: green', element);
          console.error(err);
          console.groupEnd(errorName);
        }

        if (result === false) {
          event.stopPropagation();
          event.preventDefault();
          return false;
        }
      }
    });
    $(element).on('touchstart.' + ns, function (event) {
      var elem = this;

      elem.__drawerTouchStartEvent = event;

      // disable click entirely since we do everything with touch events
      $(element).off('click.' + ns);
    });
    $(element).on('touchend.' + ns, function (event) {
      var elem = this,
        result;

      if (elem.__drawerTouchStartEvent) {
        var tsDiff = Math.abs(
          elem.__drawerTouchStartEvent.timeStamp - event.timeStamp
        );

        if (tsDiff < 300) {
          elem.__lastClickTime = Date.now();
          try {
            result = handler.apply(elem, [event]);
          } catch (err) {
            var errorName = 'Catched error - ' + 'touchend.' + ns;
            console.groupCollapsed(errorName);
            console.log('%c[' + 'Event name' + ']', 'color: green', 'touchend.' + ns);
            console.log('%c[' + 'Element' + ']', 'color: green', elem);
            console.error(err);
            console.groupEnd(errorName);
          }
          if (result === false) {
            event.stopPropagation();
            event.preventDefault();
            return false;
          }
        }
        delete elem.__drawerTouchStartEvent;
      }
    });
  };

  utilNamespace.bindDoubleTap = function (element, namespace,
    handler) {
    var timeWindow = 500;
    var positionWindow = 20;

    $(element).on('touchend.' + namespace, function (event) {
      var eventElem = this,
        eventPos;
      if (eventElem.__touchEndTime) {
        var diff = Date.now() - eventElem.__touchEndTime;
        eventPos = utilNamespace.getEventPosition(event);
        var xDiff = Math.abs(eventElem.__touchEndX - eventPos.left);
        var yDiff = Math.abs(eventElem.__touchEndY - eventPos.top);
        var result;

        if (diff < timeWindow &&
          xDiff < positionWindow &&
          yDiff < positionWindow) {

          delete eventElem.__touchEndTime;
          delete eventElem.__touchEndX;
          delete eventElem.__touchEndY;
          try {
            result = handler.apply(eventElem, [event]);
          } catch (err) {
            var errorName = 'Catched error - ' + 'touchend(doubleTap).' + namespace;
            console.groupCollapsed(errorName);
            console.log('%c[' + 'Event name' + ']', 'color: green', 'touchend(doubleTap).' + namespace);
            console.log('%c[' + 'Element' + ']', 'color: green', eventElem);
            console.error(err);
            console.groupEnd(errorName);
          }
          if (result === false) {
            event.stopPropagation();
            event.preventDefault();
            return false;
          }
        } else {
          delete eventElem.__touchEndTime;
          delete eventElem.__touchEndX;
          delete eventElem.__touchEndY;
        }
      } else {
        eventElem.__touchEndTime = Date.now();
        eventPos = utilNamespace.getEventPosition(event);
        eventElem.__touchEndX = eventPos.left;
        eventElem.__touchEndY = eventPos.top;
        utilNamespace.setTimeout(function () {
          delete eventElem.__touchEndTime;
          delete eventElem.__touchEndX;
          delete eventElem.__touchEndY;
        }, timeWindow);
      }
    });
  };

  utilNamespace.bindLongPress = function (element, namespace,
    handler) {
    var logTag = 'drawerBindLongPress';
    var ns = namespace + logTag;

    $(element).on('touchstart.' + ns, function (event) {
      var elem = this;

      elem.__touchStartTime = Date.now();
      var eventPos = utilNamespace.getEventPosition(event);
      elem.__touchStartX = eventPos.left;
      elem.__touchStartY = eventPos.top;

      if (elem.__longPressCheckTimeout) {
        clearTimeout(elem.__longPressCheckTimeout);
      }

      var cleanHandlers = function () {

        delete elem.__touchStartTime;
        delete elem.__touchStartX;
        delete elem.__touchStartY;

        $(elem).off('touchmove.' + ns);
        $(elem).off('touchend.' + ns);
      };

      $(elem).on('touchmove.' + ns, function (moveEvent) {
        var eventPos = utilNamespace.getEventPosition(moveEvent);
        if (elem.__touchStartTime) {
          var xDiff = Math.abs(
            elem.__touchStartX - eventPos.left
          );
          var yDiff = Math.abs(
            elem.__touchStartY - eventPos.top
          );

          if (xDiff > 10 || yDiff > 10) {
            cleanHandlers();
          }
        }
      });

      $(elem).on('touchend.' + ns, function (endEvent) {
        cleanHandlers();
      });

      elem.__longPressCheckTimeout = setTimeout(function () {
        if (elem.__touchStartTime) {
          cleanHandlers();
          try {
            var result = handler.apply(elem, [event]);
          } catch (err) {
            var errorName = 'Catched error - ' + 'touchstart(bindLongPress).' + ns;
            console.groupCollapsed(errorName);
            console.log('%c[' + 'Event name' + ']', 'color: green', 'touchstart(bindLongPress).' + ns);
            console.log('%c[' + 'Element' + ']', 'color: green', elem);
            console.error(err);
            console.groupEnd(errorName);
          }
        }
      }, 1000);

      return true;
    });
  };

  utilNamespace.unbindLongPress = function (element, namespace) {
    var logTag = 'drawerBindLongPress';
    var ns = namespace + logTag;

    $(element).off('touchstart.' + ns);
    $(element).off('touchmove.' + ns);
    $(element).off('touchend.' + ns);
  };

  utilNamespace.mouseDown = function (namespace) {
    return 'mousedown.' + namespace + this.id +
      ' touchstart.' + namespace + this.id;
  };

  utilNamespace.mouseMove = function (namespace) {
    return 'mousemove.' + namespace + this.id +
      ' touchmove.' + namespace + this.id;
  };

  utilNamespace.mouseUp = function (namespace) {
    return 'mouseup.' + namespace + this.id +
      ' touchend.' + namespace + this.id;
  };

  utilNamespace.getTransitionDuration = function (el, with_delay) {
    var style = window.getComputedStyle(el),
      duration = style.webkitTransitionDuration,
      delay = style.webkitTransitionDelay;

    // fix miliseconds vs seconds
    duration = (duration.indexOf("ms") > -1) ?
      parseFloat(duration) : parseFloat(duration) * 1000;
    delay = (delay.indexOf("ms") > -1) ?
      parseFloat(delay) : parseFloat(delay) * 1000;

    if (with_delay) return (duration + delay);

    else return duration;
  };

  utilNamespace.getEventPosition = function (event, touchIndex) {
    var result = {};
    var searchTouchEvent = function (innerEvent, fromInner) {
      var touchEvent;
      if (innerEvent) {
        var isTouch = innerEvent.type.indexOf('touch') > -1,
          haveTouches = isTouch && innerEvent.touches && innerEvent.touches.length,
          eventCoordsAreValid = utilNamespace.eventCoordsAreValid(innerEvent),
          getFromTouch = isTouch && haveTouches && !eventCoordsAreValid;

        touchEvent = getFromTouch ? innerEvent : (!fromInner && searchTouchEvent(innerEvent.originalEvent, true));
      }
      return touchEvent;
    };
    if (event) {
      touchIndex = touchIndex || 0;
      var touchEvent = searchTouchEvent(event),
        touchCoordsAreValid = touchEvent && utilNamespace.eventCoordsAreValid(touchEvent.touches[touchIndex]),
        eventCoordsAreValid = utilNamespace.eventCoordsAreValid(event),
        originalEventCoordsAreValid = event && event.originalEvent && utilNamespace.eventCoordsAreValid(event.originalEvent);

      var coordsHolder = (touchCoordsAreValid && touchEvent.touches[touchIndex]) || (eventCoordsAreValid && event) || (originalEventCoordsAreValid && event.originalEvent);
      if (coordsHolder) {
        result = {
          left: coordsHolder.pageX,
          top: coordsHolder.pageY
        };
      }
    }
    return result;
  };

  utilNamespace.eventCoordsAreValid = function (event) {
    var isValid;
    if (event) {
      var areNotZero = event.pageX !== 0 && event.pageY !== 0,
        areNumbers = typeof event.pageX === 'number' && typeof event.pageY === 'number',
        areNotNan = areNumbers && isFinite(event.pageX) && isFinite(event.pageY);
      isValid = areNotZero && areNumbers && areNotNan;
    }
    return isValid;
  };

  utilNamespace.isShape = function (fabricObject) {
    var isShape = false;

    if (fabricObject.type &&
      (fabricObject.type == 'line' ||
        fabricObject.type == 'arrow')) {
      isShape = false;
    }
    else if (fabricObject.path) { // free drawing shape
      isShape = false;
    } else {
      isShape = true;
    }

    return isShape;
  };


  utilNamespace.__temporaryCanvas = null;
  utilNamespace.__latestValidCanvasWidth = null;
  utilNamespace.__latestValidCanvasHeight = null;
  utilNamespace.getTemporaryCanvas = function (originalCanvas) {
    if (!utilNamespace.__temporaryCanvas) {
      utilNamespace.__temporaryCanvas = document.createElement('canvas');
    }

    var canvasWidth = originalCanvas.width,
      canvasHeight = originalCanvas.height,
      resultCanvasWidth = canvasWidth || utilNamespace.__latestValidCanvasWidth || 1,
      resultCanvasHeight = canvasHeight || utilNamespace.__latestValidCanvasHeight || 1;

    utilNamespace.__temporaryCanvas.setAttribute('width', resultCanvasWidth);
    utilNamespace.__temporaryCanvas.setAttribute('height', resultCanvasHeight);

    utilNamespace.__latestValidCanvasWidth = resultCanvasWidth;
    utilNamespace.__latestValidCanvasHeight = resultCanvasHeight;

    return utilNamespace.__temporaryCanvas;
  };

  utilNamespace.LastCoordsQueue = function () {
    this.coordsQueue = [];
    this.length = 10;

    this.pushCoords = function (x, y) {
      if (this.coordsQueue.length > this.length) {
        this.coordsQueue =
          this.coordsQueue.slice(this.coordsQueue.length - this.length);
      }

      this.coordsQueue.push({ x: x, y: y });
    };

    this.getInterpolatedValues = function () {
      if (this.coordsQueue.length === 0) {
        return [];
      }

      if (this.coordsQueue.length === 1) {
        return [{ x: this.coordsQueue[0].x, y: this.coordsQueue[0].y }];
      }

      var interpolatedCoords = [];

      var prevX = this.coordsQueue[this.coordsQueue.length - 2].x;
      var prevY = this.coordsQueue[this.coordsQueue.length - 2].y;

      var currX = this.coordsQueue[this.coordsQueue.length - 1].x;
      var currY = this.coordsQueue[this.coordsQueue.length - 1].y;

      var xDiff = currX - prevX;
      var yDiff = currY - prevY;

      var xDiffAbs = Math.abs(xDiff);
      var yDiffAbs = Math.abs(yDiff);

      var iterations = xDiffAbs > yDiffAbs ? xDiffAbs : yDiffAbs;

      for (var ii = 0; ii < iterations; ii++) {
        interpolatedCoords.push({
          x: prevX + ((xDiff / iterations) * ii),
          y: prevY + ((yDiff / iterations) * ii)
        });
      }

      return interpolatedCoords;
    };
  };

  /**
   * Add css rule to sheet
   * @param {Node} sheet - sheet element
   * @param {String} selector - selector for css rule
   * @param {String} rules - text of css rules
   * @param {Number} [index] - index of rule
   */
  utilNamespace.addCSSRule = function addCSSRule(sheet, selector, rules, index) {
    if (sheet) {
      if ("insertRule" in sheet) {
        sheet.insertRule(selector + "{" + rules + "}", index);
      } else {
        if ("addRule" in sheet) {
          sheet.addRule(selector, rules, index);
        }
      }
    }
  };

  /**
   * Add css rule to style sheet
   * @param {String} styleSelector - selector for css rule
   * @param {String} styleRule - text of css rules
   * @param {jQuery|String|Node} styleSheet - stylesheet element
   * @param {Boolean} [createElement] - create stylesheet element if cant find matched
   * @param {Boolean} [force] - need to insert css rules even if there no stylesheet element
   */
  utilNamespace.addStyleToStyleSheet = function (styleSelector, styleRule, styleSheet, createElement, force) {
    var isJqueryEl = styleSheet && styleSheet instanceof jQuery && styleSheet.length,
      isSelector = typeof styleSheet === 'string' && $(styleSheet).length && $(styleSheet),
      isNode = styleSheet instanceof Node && $(styleSheet).length && $(styleSheet),
      $styleSheet = isJqueryEl || isSelector || isNode;
    if ($styleSheet) {
      utilNamespace.addCSSRule($styleSheet[0].sheet, styleSelector, styleRule);
    } else {
      if (createElement) {
        var head = document.head || document.getElementsByTagName('head')[0],
          style = document.createElement('style');

        style.type = 'text/css';
        style.id = styleSheet;
        head.appendChild(style);
        utilNamespace.addCSSRule(style.sheet, styleSelector, styleRule);
      } else {
        if (force) {
          var css = styleSelector + '{' + styleRule + '}';
          utilNamespace.addStyle(css);
        }
      }
    }
  };

  utilNamespace.addStyle = function (css) {
    var head = document.head || document.getElementsByTagName('head')[0],
      style = document.createElement('style');

    style.type = 'text/css';
    if (style.styleSheet) {
      style.styleSheet.cssText = css;
    } else {
      style.appendChild(document.createTextNode(css));
    }

    head.appendChild(style);
  };


  utilNamespace.isString = function (str) {
    return typeof (str) == 'string' || str instanceof String;
  };

  utilNamespace.getScrollTopFromElement = function ($element) {
    var $currElement = $element,
      result = {
        left: 0,
        top: 0
      },
      needToContinue = true;
    while (needToContinue) {
      needToContinue = $currElement && $currElement.length && !$currElement.is('body');
      var currScrollTop = $currElement.scrollTop(), currScrollLeft = $currElement.scrollLeft();
      result.top += currScrollTop;
      result.left += currScrollLeft;
      $currElement = $currElement.parent();
    }
    return result;
  };


  /**
   * Debounce function
   * @param {Function} func
   * @param {Number} [wait]
   * @param {Boolean} [immediate]
   * @returns {Function}
   */
  utilNamespace.debounce = function (func, wait, immediate) {
    wait = wait || 0;
    var timeout;
    return function () {
      var context = this, args = arguments;
      var later = function () {
        timeout = null;
        if (!immediate) func.apply(context, args);
      };
      var callNow = immediate && !timeout;
      clearTimeout(timeout);
      timeout = utilNamespace.setTimeout(later, wait);
      if (callNow) func.apply(context, args);
    };
  };

  /**
   *
   * @param src
   * @param {Node} [imageEl]
   * @param {Object} [data]
   * @returns {Promise}
   */
  utilNamespace.loadImage = function (src, imageEl, data, ignoreCrossOrigin) {
    data = data || {};
    var img = imageEl || new Image();
    img.style.opacity = 0;

    return new Promise(function (resolve) {
      if (img.src === src) {
        // If image source hasn't changed resolve immediately
        resolve(img, data);
      } else {
        img.removeAttribute('crossOrigin');
        if (!ignoreCrossOrigin && src.match(/^https?:\/\/|^\/\//)) {
          img.setAttribute('crossOrigin', 'anonymous');
        }
        img.onload = function () {
          utilNamespace.setTimeout(function () {
            resolve(img, data);
          }, 1);
        };
        img.src = src;
      }
    });
  };

  utilNamespace.checkBrowser = function (browser) {
    var ua = navigator.userAgent.toLowerCase();
    var match = /(opr)[\/]([\w.]+)/.exec(ua) ||
      /(chrome)[ \/]([\w.]+)/.exec(ua) ||
      /(webkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) ||
      /(webkit)[ \/]([\w.]+)/.exec(ua) ||
      /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
      /(msie) ([\w.]+)/.exec(ua) ||
      ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec(ua) ||
      ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
      [];

    if (browser == 'safari') return (typeof match[3] != 'undefined') ? match[3] == 'safari' : false;
    if (browser == 'version') return match[2];
    if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'opr' || match[1] == 'webkit');
    if (match[1] == 'rv') return browser == 'msie';
    if (match[1] == 'opr') return browser == 'webkit';

    return browser == match[1];
  };

  /**
   *
   * @param {jQuery} $element
   */
  utilNamespace.getScrollOffset = function ($element) {
    var result = {},
      needContinue = true,
      currElement = $element,
      offsetX = 0,
      offsetY = 0;
    if ($element && $element.length) {
      while (needContinue) {
        currElement = currElement.parent();
        needContinue = currElement && currElement.length && !currElement.is('body');
        offsetX += currElement.scrollLeft();
        offsetY += currElement.scrollTop();
      }
    }
    result.left = offsetX;
    result.top = offsetY;
    return result;
  };


  /**
   * Custom setTimeout function with advanced error handling
   @param {String|Function} func
   @param {number} [delay]
   @param {...*} [arguments]
   @return {number}
   */
  utilNamespace.setTimeout = function (func, delay, args) {
    var funcWrapper,
      timer,
      tempError = new Error('Parent stack'),
      stack = tempError.stack;
    funcWrapper = function advancedSetTimeout() {
      try {
        func.apply(window, arguments);
      } catch (err) {
        var errorName = 'Catched error - ' + 'setTimeout';
        console.groupCollapsed(errorName);
        console.log('%c[' + 'Parent stack' + ']', 'color: green', stack);
        console.log('%c[' + 'Error stack' + ']', 'color: green', err.stack);
        console.error(err);
        console.groupEnd(errorName);
      }
    };
    timer = window.setTimeout(funcWrapper, delay, args);
    return timer;
  };

  /**
   * Mousewheel handler across browser from developer.mozilla.org
   */
  (function () {
    var prefix = "",
      _addEventListener,
      support,
      addWheelListener;

    // detect event model
    if (window.addEventListener) {
      _addEventListener = "addEventListener";
    } else {
      _addEventListener = "attachEvent";
      prefix = "on";
    }

    // detect available wheel event
    var support_modern = "onwheel" in document.createElement("div") && "wheel", // Modern browsers support "wheel"
      support_WebkitIe = document.onmousewheel !== undefined && "mousewheel",// Webkit and IE support at least "mousewheel"
      support_remaining = "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox

    support = support_modern || support_WebkitIe || support_remaining;

    var eventHandlerForOldBrowsers = function (originalEvent) {
      originalEvent = originalEvent || window.event;

      // create a normalized event object
      var event = {
        // keep a ref to the original event object
        originalEvent: originalEvent,
        target: originalEvent.target || originalEvent.srcElement,
        type: "wheel",
        deltaMode: originalEvent.type == "MozMousePixelScroll" ? 0 : 1,
        deltaX: 0,
        deltaY: 0,
        deltaZ: 0,
        preventDefault: function () {
          if (originalEvent.preventDefault) {
            originalEvent.preventDefault();
          } else {
            originalEvent.returnValue = false;
          }
        }
      };

      // calculate deltaY (and deltaX) according to the event
      if (support == "mousewheel") {
        event.deltaY = -1 / 40 * originalEvent.wheelDelta;
        // Webkit also support wheelDeltaX
        if (originalEvent.wheelDeltaX) {
          event.deltaX = -1 / 40 * originalEvent.wheelDeltaX;
        }
      } else {
        event.deltaY = originalEvent.deltaY || originalEvent.detail;
      }

      // it's time to fire the callback
      return callback(event);

    },
      _addWheelListener = function (elem, eventName, callback, useCapture) {
        var eventNameFull = prefix + eventName,
          eventHandler;

        eventHandler = support == "wheel" ? callback : eventHandlerForOldBrowsers;

        elem[_addEventListener](eventNameFull, eventHandler, useCapture || false);
      };

    /**
     *
     * @param elem
     * @param {Function} callback
     * @param {Boolean} useCapture
     */
    utilNamespace.addWheelListener = function (elem, callback, useCapture) {
      _addWheelListener(elem, support, callback, useCapture);

      // handle MozMousePixelScroll in older Firefox
      if (support == "DOMMouseScroll") {
        _addWheelListener(elem, "MozMousePixelScroll", callback, useCapture);
      }
    };

    utilNamespace.requestAnimationFrame = window.requestAnimationFrame.bind(window) ||
      window.mozRequestAnimationFrame.bind(window) ||
      window.webkitRequestAnimationFrame.bind(window) ||
      window.msRequestAnimationFrame.bind(window) ||
      function (f) {
        f();
      };

    utilNamespace.cancelAnimationFrame = window.cancelAnimationFrame.bind(window) ||
      window.mozCancelAnimationFrame.bind(window) ||
      function () { };
  }());

}(jQuery, DrawerJs.util));
(function () {
  /**
   *
   * @param {DrawerJs.Drawer} drawer - instance of drawer
   * @memberOf DrawerJs
   * @constructor
   */
  var DrawerApi = function (drawer) {
    if (!drawer) {
      throw new Error('DrawerApi(): no drawer is provided!');
    }
    this.drawer = drawer;
  };

  DrawerApi.prototype.drawer = null;

  // Drawer core API
  ////////////////////////////////////////////////////////////////////////

  /**
   * Starts editing mode.
   * If already in this mode - do nothing.
   */
  DrawerApi.prototype.checkIsActive = function () {
    if (this.drawer.mode != this.drawer.MODE_ACTIVE) {
      throw new Error("Drawer is not active!");
    }
  };


  /**
   * Starts editing mode.
   * If already in this mode - do nothing.
   */
  DrawerApi.prototype.startEditing = function () {
    this.drawer._startEditing();
  };

  /**
   * Stops editing.
   * If already stopped, ie. in INACTIVE_MODE - do nothing.
   */
  DrawerApi.prototype.stopEditing = function () {
    this.drawer._stopEditing();
  };



  /**
   * Get serialized in JSON string canvas data.
   * @returns [String]
   */
  DrawerApi.prototype.getCanvasAsJSON = function () {
    this.drawer.api.checkIsActive();
    return this.drawer.getSerializedCanvas();
  };


  /**
   * Save canvas.
   * Syncs drawer canvas data with storages, defined in options
   */
  DrawerApi.prototype.saveCanvas = function () {
    this.drawer.api.checkIsActive();
    this.drawer.syncCanvasData();
  };


  /**
   * Load canvas.
   * Loads canvas
   */
  DrawerApi.prototype.loadCanvasFromData = function (data) {
    this.drawer.loadCanvas(data);
  };



  /**
    * Returns data-url with image encoded to base64.
    *
    * @see Drawer.Storage.js getImageData() for details
    * @returns {String} image data encoded in base64/png.
    */
  DrawerApi.prototype.getCanvasAsImage = function () {
    return this.drawer.getImageData();
  };


  /**
   * Save canvas as image in storages, as defined in config
   */
  DrawerApi.prototype.saveCanvasImage = function () {
    this.drawer.api.checkIsActive();
    this.drawer.syncImageData();
  };

  /**
   * List of all available options for each mode of each toolbar
   * @typedef {Object} sizesOfDrawer
   * @memberOf DrawerJs.DrawerApi
   * @property {Number} width - width of Drawer
   * @property {Number} height - height of drawer
   * @property {Number} scrollTop - "Top" position including scrollTop value of parent elements
   * @property {Number} scrollLeft - "Left" position including scrollLeft value of parent elements
   * @property {Number} top - Absolute value of "top" position
   * @property {Number} left - Absolute value of "left" position
   */


  /**
     * Get sizes of drawer
     * @returns {DrawerJs.DrawerApi.sizesOfDrawer}
     */
  DrawerApi.prototype.getSize = function () {
    var sizes = this.drawer.getSize();
    return sizes;
  };

  /**
     * Sets drawer size.
     */
  DrawerApi.prototype.setSize = function (width, height) {
    this.drawer.setSize(width, height);
  };

  /**
   * Set active color
   * @param {String} color - New color value (HEX)
   */
  DrawerApi.prototype.setActiveColor = function (color) {
    this.drawer.setActiveColor(color);
  };

  /**@
   * Create text object
   * @param {Number} [positionX=0] - left offset of new text object
   * @param {Number} [positionY=0] - top offset of new text object
   * @param {String} [text="Text"] - text of new object
   * @param {Object} [styles] - styles for new text object
   */
  DrawerApi.prototype.createText = function (positionX, positionY, text, styles) {
    this.drawer._pluginsInstances.Text.addShape(positionX, positionY, text, styles);
  };


  /**
   * Update current options.
   * If optionsToUpdate has plugins key, plugins will be reloaded
   *
   * @param  {Object} optionsToUpdate options object
   */
  DrawerApi.prototype.updateOptions = function (optionsToUpdate) {
    this.drawer.updateOptions(optionsToUpdate);
  };


  /**
   * Update current options.
   * All plugins will be reloaded
   *
   * @param  {Object} optionsToUpdate options object
   */
  DrawerApi.prototype.setOptions = function (newOptions) {
    this.drawer.setOptions(newOptions);
  };


  /**
   * Load plugin by name.
   * Name must exists in DrawerJs namespace.
   * If plugin is already loaded, error will be thrown
   *
   * @param  {String} pluginName plugin name
   */
  DrawerApi.prototype.loadPlugin = function (pluginName) {
    this.drawer.loadPlugin(pluginName);
  };


  /**
   * Unload plugin by name.
   * If plugin is not loaded, nothing happens.
   *
   * @param  {String} pluginName plugin name
   */
  DrawerApi.prototype.unloadPlugin = function (pluginName) {
    this.drawer.unloadPlugin(pluginName);
  };



  DrawerJs.DrawerApi = DrawerApi;
})(DrawerJs);

(function (DrawerApi) {
  /**
   * Checks if obj is set and is fabric.Object
   *
   * @param  {} obj [description]
   * @throw {Error}  if obj is falsie or object is not fabric.Object
   */
  DrawerApi.prototype._checkObject = function (obj) {
    if (!obj) {
      throw new Error('[Drawer API]  no object provided!');
    }
    if (!(obj instanceof fabric.Object)) {
      throw new Error("[Drawer API]  object type is not 'fabric.Object!'");
    }
  };


  /**
   * Returns currently active object.
   * @return {fabric.Object}   currently active object
   */
  DrawerApi.prototype.getSelectedObject = function () {
    return this.drawer.fCanvas.getActiveObject();
  };


  /**
   * Bring object closer to front in objects stack.
   *
   * @param  {fabric.Object} fabricItem   object to reposition
   * @return {fabric.Object} returns   same object
   */
  DrawerApi.prototype.bringObjectForward = function (fabricItem) {
    this._checkObject(fabricItem);

    this.drawer.fCanvas.bringForward(fabricItem, true);
    this.drawer.syncCanvasData();
    return fabricItem;
  };


  /**
   * Bring object closer to bottom in objects stack.
   *
   * @param  {fabric.Object} fabricItem   object to reposition
   * @return {fabric.Object} returns   same object
   */
  DrawerApi.prototype.sendObjectBackwards = function (fabricItem) {
    this._checkObject(fabricItem);
    this.drawer.fCanvas.sendBackwards(fabricItem, true);
    this.drawer.syncCanvasData();
    return fabricItem;
  };


  /**
   * Move object the top object in stack.
   *
   * @param  {fabric.Object} fabricItem   object to reposition
   * @return {fabric.Object}   returns same object
   */
  DrawerApi.prototype.bringObjectToFront = function (fabricItem) {
    this._checkObject(fabricItem);

    this.drawer.fCanvas.bringToFront(fabricItem, true);
    this.drawer.syncCanvasData();
    return fabricItem;
  };


  /**
   * Move object the bottom object in stack.
   *
   * @param  {fabric.Object} fabricItem   object to reposition
   * @return {fabric.Object}   returns same object
   */
  DrawerApi.prototype.sendObjectToBack = function (fabricItem) {
    this._checkObject(fabricItem);

    this.drawer.fCanvas.sendToBack(fabricItem);
    this.drawer.syncCanvasData();
    return fabricItem;
  };

  /**
   * Remove object from canvas.
   *
   * @param  {fabric.Object} fabricItem  object to reposition
   */
  DrawerApi.prototype.removeObject = function (fabricItem) {
    this._checkObject(fabricItem);

    fabricItem.remove();
    this.drawer.fCanvas.renderAll();
  };


  /**
   * Duplicate given object.
   * If object is not 'async' - it will be returned.
   * If 'callback' is provided - it will be called after cloning,
   *  with cloned object as argument
   *
   * @param  {fabric.Object}  fabricItem  object to be cloned
   * @param  {Function}       callback    will be called after cloning with cloned object as argument
   * @return {fabric.Object}              cloned object, if objject is not 'async'
   */
  DrawerApi.prototype.duplicateObject = function (fabricItem, callback) {
    this._checkObject(fabricItem);

    var _this = this;
    var onCloned = function (clonedObj) {
      if (!clonedObj) {
        throw new Error("[Drawer API] duplicateObject() : Clone failed! Clone source: " + fabricItem.toString());
      }

      clonedObj.set('left', fabricItem.get('left') + 20);
      clonedObj.set('top', fabricItem.get('top') + 20);
      _this.drawer.fCanvas.add(clonedObj);
      _this.drawer.fCanvas.renderAll();

      // call callback with new object
      if (callback) {
        callback(clonedObj);
      }
      return clonedObj;
    };

    // sync and async objects cloning is different
    if (fabricItem.async) {
      // call clone with callback
      fabricItem.clone(onCloned);
    } else {
      // direct call function
      return onCloned(fabricItem.clone());
    }
  };

})(DrawerJs.DrawerApi);

(function ($, namespace, pluginsNamespace, util, texts) {
  'use strict';

  /**
   * Canvas editable element.
   *
   * @param redactorInstance {Object|null} Redactor's instance.
   *                         Could be null for standalone version.
   *
   * @param options {Object} Object with configuration parameters.
   *
   * @param {String} options.borderCss
   * Canvas border css styles.
   * <br><br>
   * Example:
   * <code>1px dashed rgb(195, 194, 194)</code>
   *
   * @param {String} options.borderCssEditMode
   * Canvas border css styles which will be applied when in edit mode.
   * <br><br>
   * Example:
   * <code>1px dashed rgb(195, 194, 194)</code>
   *
   * @param {String} options.backgroundCss
   * Canvas background css style which will be applied to canvas container.
   * <br><br>
   * Example:
   * <code>
   *   url(/redactor.plugin.drawer/dist/assets/transparent.png) repeat
   * </code>
   *
   * @param {String} options.defaultImageUrl
   * Image url that will be drawn on canvas when it's just created and
   * nothing has been drawn on it.
   *
   * @param {number} options.toolbarSize=35
   * Specifies drawer's toolbar buttons size in px.
   *
   * @param {number} options.toolbarSizeTouch=43
   * Specifies drawer's toolbar buttons size in px when running on touch device.
   *
   * @param {Object} options.toolbars - Configuration for each toolbar.
   * Each toolbar have next configuration object:
   * <code><pre>
   *   {
   *
   *      // All of {@link DrawerToolbar.defaultSetOfOptions}
   *      hidden: false,
   *      position: 'top',
   *      positionType: 'outside',
   *      compactType: 'scrollable',
   *      toggleVisibilityButton: false,
   *      customAnchorSelector: '#custom-toolbar-here',
   *
   *      fullscreenMode: {
   *          // All of {@link DrawerToolbar.defaultSetOfOptions}
   *          hidden: false,
   *          position: 'top',
   *          positionType: 'outside',
   *          compactType: 'scrollable',
   *          toggleVisibilityButton: false,
   *          customAnchorSelector: '#custom-toolbar-here',
   *      }
   *   }
   * </pre></code>
   *
   * @param {Boolean} options.toolbars.popupButtonAlwaysVisible=true - Always display popup button(If any toolbar uses it)
   *
   * @param {DrawerToolbar.defaultToolbarOptions} options.toolbars.drawingTools - Configuration of "Drawing tools" toolbar
   *
   * @param {DrawerToolbar.defaultToolbarOptions} options.toolbars.toolOptions - Configuration of "Tool options" toolbar
   *
   * @param {DrawerToolbar.defaultToolbarOptions} options.toolbars.settings - Configuration of "Drawer settings" toolbar
   *
   * @param {Object} options.tooltipCss
   * Allows css customizations of buttons tooltips. Could be any valid css
   * object that will be passed directly to jQuery.css method.
   * <br><br>
   * Example:
   * <code><pre>
   *   {
   *    background: 'green',
   *    color: 'red'
   *   }
   * </pre></code>
   *
   * @param {String} options.activeColor=#19A6FD
   * Specifies default active color for drawer(the one that will be selected on
   * initialization).
   *
   * @param {String} options.align  left|right|center|inline|floating
   * Specifies drawer align.
   * Drawer can be moved via MovableFloatingMode only if align is 'floating'
   * <br><br>
   *
   * @param {Boolean} options.transparentBackground
   * Specifies if drawer canvas has transparent background.
   * <br><br>
   *
   * @param {String} options.texts
   * Object containing strings with translations/texts to use.
   * <br>
   * String keys could be found in Localization_en.js file.
   * <br><br>
   * Example:
   * <code><pre>
   *   {
   *    'Free drawing mode': 'Pencil',
   *   }
   * </pre></code>
   *
   * @param {Array} options.plugins
   * List of plugin names that drawer will use. Please see a full list here:
   * {@link DrawerJs.plugins}
   *
   * Example:
   * <code><pre>
     * plugins: [
   *  // Drawing tools
   *  'Pencil',
   *  'Eraser',
   *  'Text',
   *  'Line',
   *  'ArrowOneSide',
   *  'ArrowTwoSide',
   *  'Triangle',
   *  'Rectangle',
   *  'Circle',
   *
   *  // Drawing options
   *  'ColorpickerRedactor',
   *  'BrushSize',
   *  'Resize'
   * ],
   * </pre></code>
   *
   * @param {Object} options.pluginsConfig
   * Each Drawer plugin could have its own configuration and could be
   * configured by this section.
   * <br><br>
   *
   * This is the object which keys are plugin names and values are objects
   * with plugin configuration fields.
   * <br><br>
   *
   * Plugins names and documentation about each plugin could be found here:
   * {@link DrawerJs.plugins}.
   * Look at plugin's constructor `<code>options</code>` argument.
   * <br><br>
   *
   * For example we would like to configure
   * {@link DrawerJs.plugins.Text} and {@link DrawerJs.plugins.Eraser}
   * plugins:
   * <code><pre class="prettyprint">
   * pluginsConfig: {
   *   "Text": {
   *     "fonts": {
   *       "Georgia": 'Georgia, serif',
   *       "Palatino": "'Palatino Linotype', Palatino, serif"
   *     },
   *     "defaultFont": 'Georgia'
   *   },
   *   "Eraser": {
   *     "cursorUrl": "url(assets/eraser_cursor.cur), default"
   *   }
   * }
   * </code></pre>
   *
   * @param {Object} options.defaultActivePlugin
   * Activates default tool, if options.defaultActivePlugin is set;
   * MUST contain keys:
   * name - name of plugin
   * mode - one of : ['always', 'onNew', 'lastUsed']
   * Depending on options.defaultActivePlugin.mode :
   * 'always'   - same plugin will be always actived
   * 'onNew'    - default plugin is actiavted, only if canvas is empty
   * 'lastUsed' - on first drawer run plugin options.defaultActivePlugin.name, on consecutive - last used tool
   *
   * @param {Object} options.canvasProperties
   * Specifies fabricjs options that will be passed directly to fabricjs
   * canvas instance on creation.
   *
   * @param {String} options.canvasProperties.selectionColor
   * Specifies color of selection rectangle.
   *
   *
   * @param {number[]} options.canvasProperties.selectionDashArray
   * What it allows us to do is make selection lines dashed.
   * <br><br>
   *
   * The way to define dash pattern is by specifying intervals via an array.
   * <br><br>
   * So to create a pattern where there's one long dash followed by one short
   * dash, we could use something like <code>[10, 5]</code>
   * as "selectionDashArray".
   * <br>
   *
   * This will draw a line that's 10px long, then skip 5px, draw 10px line
   * again, and so on.<br>
   * If we were to use <code>[2, 4, 6]</code> array, the pattern would be
   * created by drawing 2px line, then skipping 4px, then drawing 6px line,
   * then skipping 2px, then drawing 4px line, then skipping 6px, and so on.
   * You get the point.
   *
   *
   * @param {number} options.canvasProperties.selectionLineWidth
   * Specifies selection line width in pixels.
   *
   *
   * @param {String} options.canvasProperties.selectionBorderColor
   * Specifies selection line color.
   *
   *
   * @param {Object} options.objectControls
   * Specifies the appearance of selected object controls.
   *
   *
   * @param {String} options.objectControls.borderColor
   * Specifies selected object border color.
   *
   *
   * @param {float} options.objectControls.borderOpacityWhenMoving
   * Specifies border opacity when object is selected and in moving process.
   *
   *
   * @param {String} options.objectControls.cornerColor
   * Specified color of control corners for resizing/rotating.
   *
   *
   * @param {number} options.objectControls.cornerSize
   * Specifies size of corners for resizing/rotating.
   *
   *
   * @param {boolean} options.objectControls.hasBorders
   * Toggles visibility of selected object border.
   *
   * @param {Object} options.objectControlsTouch
   * The same as <code>objectControls</code> but will be used
   * when touch device is detected.
   *
   * @param {Function} options.detectTouch
   * A custom function that will be used by drawer to determine whether it is
   * running on touch device or not.
   * <br><br>
   *
   * This function must return <code>true</code> or <code>false</code>.
   * <br><br>
   *
   * <code>true</code> means that touch device is detected and drawer should
   * adjust its toolbar sized, add touch events etc.
   * <br><br>
   *
   * Note that if this function is not specified, drawer will use its own
   * detection mechanism.
   * <br><br>
   *
   * To disable any detection simply set this parameter to such function:
   * <code><pre>function() { return false; }</pre></code>
   *
   *
   * @param {Object} options.contentConfig
   * Specifies data-management configuration and controls where canvas element
   * will store its information.
   *
   *
   * @param {number} options.contentConfig.saveAfterInactiveSec
   * Specifies number of seconds to wait after user interaction.
   * If nothing happens in that time - canvas will be saved.
   * Any interaction resets the timer.
   *
   * @param {boolean} options.contentConfig.saveInHtml
   * Controls whether a drawer will save its content to underlying
   * image element.
   * <br><br>
   *
   * If <code>true</code>, canvas objects data will be serialized to JSON and
   * appended to image's <code>data-canvas-serialized</code> attribute.
   * <br><br>
   *
   * Note that JSON could be huge on canvases with a lot of objects and
   * freedrawings.
   *
   *
   * @param {String|jQuery} options.contentConfig.imagesContainer
   * Specifies external data container for canvas images data.
   * <br><br>
   *
   * Canvas will serialize itself into base64 encoded png image and
   * store it as json-encoded text to that container.
   * <br><br>
   *
   * This could be used for storing rendered images separately from content.
   * <br><br>
   *
   * JSON example:
   * <code><pre>
   * {
   *    'canvas_id': 'base64/png .....'
   * }
   * </pre></code>
   *
   *
   * @param {String|jQuery} options.contentConfig.canvasDataContainer
   * Specifies external data container for canvas data.
   * <br><br>
   *
   * Canvas will serialize itself into json object containing all the vector
   * objects on canvas and its parameters like angle, color etc.
   * <br><br>
   *
   * This could be used for storing canvas vector data separately from content.
   * <br><br>
   *
   * JSON example:
   * <code><pre>
   * {
   *    'canvas_id': {
   *        'objects': []
   *    }
   * }
   * </pre></code>
   *
   * @param {Function} options.contentConfig.loadCanvasData
   * Specifies a function that will be called when editable canvas needs to
   * load its fabricjs data.
   * <br><br>
   * <code>function(canvasId)</code>
   *
   * @param {Function} options.contentConfig.saveCanvasData
   * Specifies a function that will be called when editable canvas needs to
   * store its fabricjs data.
   * <br><br>
   * <code>function(canvasId, canvasData)</code>
   *
   * @param {Function} options.contentConfig.loadImageData
   * Specifies a function that will be called when editable canvas needs to
   * load base64/png image data data.
   * <br><br>
   * <code>function(canvasId)</code>
   *
   * @param {Function} options.contentConfig.saveImageData
   * Specifies a function that will be called when editable canvas needs to
   * save base64/png image data data.
   * <br><br>
   * <code>function(canvasId, imageData)</code>
   *
   * @param {string} options.basePath
   * Base web url from which all needed drawer files (assets basically)
   * will be loaded. If null, drawer will try to determine it by itself by
   * parsing it's script 'src' tag.
   *
   * @param width {number}   Width in px
   * @param height {number}  Height in px
   *
   * @memberof DrawerJs
   * @constructor
   */
  var Drawer = function DrawerConstructor(redactorInstance, options,
    width, height) {
    var _this = this;
    this.mode = this.MODE_INACTIVE;
    this.redactorInstance = redactorInstance;
    this.id = Math.random().toString().replace('0.', '');
    this.width = width || 0;
    this.height = height || 0;

    /**
     * Use jQuery's event system to dispatch events
     * @type {jQuery}
     * @private
     */

    this._eventEmitter = $({});

    this.api = new namespace.DrawerApi(this);

    this.setOptions(options);


    /**
     * Image element for previewing canvas.
     * @type {jQuery}
     */
    this.$imageElement = null;

    /**
     * Container for all editing controls.
     * @type {jQuery}
     */
    this.$canvasEditContainer = null;

    /**
     * Aligment css riles for this canvas.
     * Those rules will be applied to image when edit mode is off,
     * and to canvas edit box when edit mode is on.
     * @type {{}}
     */
    this.aligmentCss = {};

    // toolbars manager
    this.toolbars = new DrawerToolbarManager(this);
    // this.loadPlugins();

    if (_this.options.detectTouch) {
      if (_this.options.detectTouch.constructor.name !== 'Function') {
        throw new Error('detectTouch should be a function which will be ' +
          'called when Drawer needs to determine whether it is working ' +
          'on touch device');
      }

      _this.touchDevice = _this.options.detectTouch(_this);
    } else {
      _this.touchDevice = /(iPhone|iPod|iPad|BlackBerry|Android)/
        .test(navigator.userAgent);

      $('body').on('touchstart.DrawerTouchCheck', function () {
        _this.touchDevice = true;
        $('body').off('touchstart.DrawerTouchCheck');
        _this.log('touch', 'Found touch screen');
      });
    }

    _this.$canvasDataContainer = null;
    if (_this.options.contentConfig.canvasDataContainer) {
      _this.$canvasDataContainer =
        $(_this.options.contentConfig.canvasDataContainer);

      if (_this.$canvasDataContainer.length < 1) {
        _this.$canvasDataContainer = null;
        throw new Error('contentConfig.canvasDataContainer provided but ' +
          'not found in DOM: ' +
          _this.options.contentConfig.canvasDataContainer);
      }
    }

    _this.$imagesContainer = null;
    if (_this.options.contentConfig.imagesContainer) {
      _this.$imagesContainer =
        $(_this.options.contentConfig.imagesContainer);

      if (_this.$imagesContainer.length < 1) {
        _this.$imagesContainer = null;
        throw new Error('contentConfig.imagesContainer provided but ' +
          'not found in DOM: ' +
          _this.options.contentConfig.imagesContainer);
      }
    }

    var inlineStyles = '' +
      '.editable-canvas-not-edited {' +
      'background: url(' + this.options.defaultImageUrl + ') no-repeat !important;' +
      'background-size: contain !important;' +
      '}';

    util.addStyle(inlineStyles);

    return this;
  };


  Drawer.prototype.api = {};

  Drawer.prototype.MODE_PREPARING = 'mode:preparing';
  Drawer.prototype.MODE_ACTIVE = 'mode:active';
  Drawer.prototype.MODE_INACTIVE = 'mode:inactive';



  /**
   * Sets new  drawer options. Reloads plugins.
   * @param {Object} options
   */
  Drawer.prototype.setOptions = function (options) {
    this.options = $.extend(true, this.defaultOptions || {}, options || {});

    this.onOptionsUpdated(true);
  };

  /**
   * Get config of plugin
   * @param {String} name - name of plugin
   * @returns {Object} - config of plugin
   */
  Drawer.prototype.getPluginConfig = function (name) {
    var result = {},
      nameIsValid = name && typeof name === 'string' && name.length,
      pluginConfig = nameIsValid && this.options.pluginsConfig[name],
      textPluginConfig = nameIsValid && this.options.pluginsConfig.Text && this.options.pluginsConfig.Text[name];

    if (pluginConfig) {
      result = $.extend(true, result, pluginConfig);
    }
    if (textPluginConfig) {
      result = $.extend(true, result, textPluginConfig);
    }

    return result;
  };

  /**
   * Update current options.
   * If optionsToUpdate has plugins key, plugins will be reloaded
   *
   * @param  {Object} optionsToUpdate options object
   */
  Drawer.prototype.updateOptions = function (optionsToUpdate) {
    optionsToUpdate = optionsToUpdate || {};
    this.options = $.extend(true, this.options, optionsToUpdate);

    var doReloadPlugins = false;
    if (optionsToUpdate.plugins) {
      // replace old plugin list with new, not extend
      this.options.plugins = optionsToUpdate.plugins;
      doReloadPlugins = true;
    }

    this.onOptionsUpdated(doReloadPlugins);
  };




  Drawer.prototype.onOptionsUpdated = function (reloadPlugins) {
    if (this.options.basePath) {
      util.setDrawerFolderUrl(this.options.basePath);
    }

    // hotfix for paths containing drawer folder
    this.options.canvasProperties.rotationCursor = 'url(' + util.getDrawerFolderUrl() + 'src/cursor-fa-rotate-right.cur), default';

    if (!this.activeColor) {
      this.activeColor = this.options.activeColor || this.defaultOptions.activeColor;
    }
    if (this.activeOpacity === undefined) {
      this.activeOpacity = this.options.activeOpacity || this.defaultOptions.activeOpacity;
    }

    if (reloadPlugins) {
      this.loadPlugins();
      if (this.toolbars) {
        this.toolbars.resetAllToolbars();
      }
    }
  };

  /**
   * Unloads all plugins,
   */
  Drawer.prototype.unloadPlugins = function () {
    for (var key in this._pluginsInstances) {
      this.unloadPlugin(key);
    }
  };


  /**
   * Unload plugin by name.
   */
  Drawer.prototype.unloadPlugin = function (pluginName) {
    if (this._pluginsInstances.hasOwnProperty(pluginName)) {
      var tool = this._pluginsInstances[pluginName];
      if (tool.removeTool) {
        tool.removeTool(true);
      }
      delete this._pluginsInstances[pluginName];
    }
  };

  /**
   * Load plugin
   * @param {String} pluginName - name of plugin to load
   */
  Drawer.prototype.loadPlugin = function (pluginName) {
    if (!pluginName || !pluginsNamespace[pluginName]) {
      this.error('Drawer: Load plugin error - ' + pluginName + '. No such plugin.');
      return;
    }

    var alreadyInitialized = this._pluginsInstances[pluginName],
      isCorePlugin = this.options.corePlugins && this.options.corePlugins.indexOf(pluginName) !== -1;
    if (alreadyInitialized) {
      if (!isCorePlugin) {
        this.error('Drawer: Load plugin error - ' + pluginName + '. Plugin should not be specified multiple times.');
      }
      return;
    }

    try {
      var pluginConfig = {};
      if (this.options.pluginsConfig[pluginName]) {
        pluginConfig = this.options.pluginsConfig[pluginName];
      }
      var plugin = new pluginsNamespace[pluginName](this, pluginConfig);
      this._pluginsInstances[pluginName] = plugin;
    } catch (err) {
      this.error('Drawer: Load plugin error - ' + pluginName + '.');
      this.error(err);
    }
  };


  /**
   * Create plugins instances, according to options.plugins list
   */
  Drawer.prototype.loadPlugins = function () {
    if (!this._pluginsInstances) {
      this._pluginsInstances = {};
    } else {
      this.unloadPlugins();
    }

    if (this.options.corePlugins) {
      for (var j = 0; j < this.options.corePlugins.length; j++) {
        this.loadPlugin(this.options.corePlugins[j]);
      }
    }
    if (this.options.plugins) {
      for (var i = 0; i < this.options.plugins.length; i++) {
        this.loadPlugin(this.options.plugins[i]);
      }
    }
  };





  Drawer.prototype.log = function (tag, msg) {
    if (this.options.debug) {
      console.log('%c[' + tag + ']', 'color: green', msg);
    }
  };

  Drawer.prototype.error = function (msg) {
    if (this.options.debug) {
      console.error(msg);
    }
  };

  Drawer.prototype.clickEvent = function (namespace) {
    return 'click.' + namespace + ' touchend.' + namespace;
  };


  /**
   * Returns html string that should be appended to DOM and will represent
   * editable canvas.
   *
   * @returns {String}
   */
  Drawer.prototype.getHtml = function () {
    var img = $('<img>')
      .css({
        'display': 'inline-block',
        'width': this.width || this.options.defaultWidth,
        'height': this.height || this.options.defaultHeight,
        'background': this.options.backgroundCss
      })
      .addClass('editable-canvas-image')
      .addClass('editable-canvas-not-edited')
      .attr('id', 'canvas_image_' + this.id)
      .attr('data-redactor-drawer-enabled', true)
      .attr('src', '');

    if (this.options.borderCss) {
      img.css('border', this.options.borderCss);
    }

    return $(img)[0].outerHTML;
  };

  /**
   * This method should be called every time display properties of canvas
   * change to properly restore them after sync/load.
   */
  Drawer.prototype.updateAligmentCss = function () {
    this.aligmentCss = this.getAligmentCssFor(this.$imageElement);
  };

  Drawer.prototype.getAligmentCssFor = function (element) {
    var styles = window.getComputedStyle(element[0]),
      aligmentCss = {
        'display': styles.display,
        'float': styles.float,
        'margin-left': styles.marginLeft,
        'margin-right': styles.marginRight,
        'position': styles.position,
        'left': styles.left,
        'top': styles.top
      };

    if (element.attr('data-margin-left')) {
      aligmentCss['margin-left'] = element.attr('data-margin-left');
    }

    if (element.attr('data-margin-right')) {
      aligmentCss['margin-right'] = element.attr('data-margin-right');
    }

    return aligmentCss;
  };

  Drawer.prototype.setAligmentCssFor = function (element, css) {
    if (element.css('display') == 'none') {
      delete css.display;
    }

    element.css(css);

    if (css['margin-left'] && css['margin-left'] == 'auto') {
      element.css('margin-left', 'auto');
      element.attr('data-margin-left', 'auto');
    }

    if (css['margin-right'] && css['margin-right'] == 'auto') {
      element.css('margin-right', 'auto');
      element.attr('data-margin-right', 'auto');
    }
  };

  /**
   * Should be called after inserting this element to DOM
   * to setup necessary event handlers etc.
   */
  Drawer.prototype.onInsert = function () {
    var _this = this;

    $(document).off(this.clickEvent('DrawerStop' + this.id));

    // If we have no image element here - this is the first run
    var firstRun = !this.$imageElement;

    this.$imageElement = $(document.getElementById('canvas_image_' + this.id));

    if (firstRun) {
      if (this.options.align) {
        this.aligmentCss = this._generateAlignCss(this.options.align);
      } else {
        this.aligmentCss = this.getAligmentCssFor(this.$imageElement);
      }

      this.width = this.$imageElement.outerWidth();
      this.height = this.$imageElement.outerHeight();
      this.$imageElement.removeClass('edit-mode');
    }

    if (this.$imageElement.attr('src').length < 1 &&
      !this.$imageElement.hasClass('editable-canvas-not-edited')) {
      this.$imageElement.attr('src', this.getImageData());
      this.$imageElement.removeClass('editable-canvas-not-edited');
    }

    //this.setSize(this.width, this.height);
    this.setAligmentCssFor(this.$imageElement, this.aligmentCss);

    // since this function can be called multiple times in a row
    // (it's called every time something in redactor changes)
    // we should clear previously set handlers.
    if (this.options.editOnClick) {
      this.$imageElement.off();
      util.bindClick(this.$imageElement, 'Drawer', function (event) {
        _this._startEditing();
      });
    } else {
      this.$imageElement.off(_this.clickEvent('Drawer'));
      this.$imageElement.on(_this.clickEvent('Drawer'), function () {
        _this.drawCanvasControls();
      });
    }

    // set global click handler
    util.bindClick($(document), 'Drawer', this._globalClickHandler.bind(this));

    // call onOptionsChange on EVENT_OPTIONS_CHANGED
    this.on(this.EVENT_OPTIONS_CHANGED, this.onOptionsChange.bind(this));
    // call onOptionsChange on EVENT_CANVAS_READY, for initial setup
    this.on(this.EVENT_CANVAS_READY, this.onOptionsChange.bind(this));
  };


  /**
   * Intercept all click events and check their targets.
   * if target is inside canvas edit box - do nothing.
   * otherwise - trigger 'stopEditing' method.
   *
   * @param  {Event} event click
   */
  Drawer.prototype._globalClickHandler = function (event) {
    var parentDrawerBox = $(event.target).parents('#redactor-drawer-box');

    if (parentDrawerBox.length > 0) {
      if (parentDrawerBox.attr('data-canvas-id') != this.id) {
        return false;
      }
      return true;
    }

    if (event.target.id == 'canvas_image_' + this.id) {
      return false;
    }

    if (event.target.id == 'redactor-image-editter' &&
      $(event.target).attr('data-canvas-id') == this.id) {
      return false;
    }

    if ($(event.target).parents('#redactor-modal').length > 0) {
      return false;
    }

    if (this.$canvasEditContainer) {
      // ignore outside mouse-up while resizing
      var needToStop = this.options.exitOnOutsideClick &&
        !this.resizingNow &&
        !this.croppingNow &&
        !this.movingNow &&
        !this.fullscreenMode &&
        !this.drawingInProgress &&
        !this.isBrushDrawing;
      if (needToStop) {
        this._stopEditing();
      }
    }
  };

  /**
   * Activates on the 'onclick' handler
   * to draw controls ('Edit in drawer button') over image.
   *
   * Redactor automatically places 'onclick' handler on image when it is appended,
   * so it will fire first and it will place image edit html
   * over image ('Edit' at the center).
   * That handler is Redactor.image.loadEditableControls(imageElement);
   *
   * Our handler will file later with that edit html appended, so all we need is
   * to correct button's title and to change 'onclick' handler of edit-box
   * to ours.
   */
  Drawer.prototype.drawCanvasControls = function () {
    var _this = this;
    // use default image module to generate markup
    //this.redactorInstance.image.loadEditableControls(this.imageElement);

    // enable click handlers
    var $editter = $('#redactor-image-editter');
    $editter.attr('data-canvas-id', this.id);
    $editter.text('Edit in Drawer');
    // position correction
    $editter.css('margin-left', '-' + $editter.innerWidth() / 2 + 'px');
    $editter.off('click');

    $editter.on('click', function () {
      _this._startEditing();
    });
  };

  /**
   * Turns on edit mode.
   *
   * Hides image element, appends fabricjs canvas element after image,
   * adds toolbars for drawing etc.
   *
   * @private
   */
  Drawer.prototype._startEditing = function () {
    this.log('canvasEditMode', 'startEditing()');
    var _this = this;
    if (this.mode != this.MODE_INACTIVE) {
      this.log('Drawer.startEditing(): already in active mode');
      return;
    }
    this.mode = this.MODE_PREPARING;

    // since we are working inside redactor's editing area which has
    // contenteditable=true, every our click to canvas/image/canvas edit
    // controls etc will be propagated to redactor's editbox and keyboard
    // will be shown on touch devices.
    // To prevent that we need to set contenteditable=false every time canvas
    // goes into editing mode.
    if (_this.redactorInstance) {
      _this.redactorInstance.$editor.attr('contenteditable', 'false');
    }

    _this._previousFocusedElement = document.activeElement;
    $(document.activeElement).blur();

    // user can resize image so we need to update our w/h.
    this.width = this.$imageElement.outerWidth();
    this.height = this.$imageElement.outerHeight();

    this.$imageElement.addClass('edit-mode');
    this.$imageElement.removeClass('editable-canvas-not-edited');

    var $canvas = $('<canvas width="' + this.width + '"' +
      ' height="' + this.height + '" />');

    this.$canvasEditContainer = $('<span id="redactor-drawer-box" ' +
      'data-canvas-id="' + this.id + '" tabindex="0"></span>');
    this.$canvasEditContainer = $('<span></span>');
    this.$canvasEditContainer.attr('id', 'redactor-drawer-box');
    this.$canvasEditContainer.attr('data-canvas-id', this.id);
    this.$canvasEditContainer.attr({
      'id': 'redactor-drawer-box',
      'class': 'drawer-instance-container',
      'data-canvas-id': this.id,
      'tabindex': '0'
    });

    if (this.options.borderCss) {
      this.$canvasEditContainer.css('border', this.options.borderCssEditMode);
    }

    if (this.touchDevice) {
      this.$canvasEditContainer.addClass('touch');
    }
    this.$canvasEditContainer.css({
      'position': 'absolute'
    });

    this.aligmentCss = this.getAligmentCssFor(this.$imageElement);

    this.$canvasEditContainer.append($canvas);

    $('#' + this.drawerContainerID).append(this.$canvasEditContainer);
    this.adjustEditContainer();
    $(window).on('resize.drawer' + this.id, function () {
      _this.adjustEditContainer(false, true);
    });

    if (this.redactorInstance) {
      this.redactorInstance.image.hideResize();
    }

    // get serialized canvas
    var serializedCanvas = this.getCanvasData();

    // create fabricJs canvas
    this.fCanvas = new namespace.Canvas($canvas.get(0));
    this.fCanvas.selection = false; // [DRW-74] Prevent selecting multiple objects

    if (this.options && this.options.canvasProperties) {
      $.each(this.options.canvasProperties, function (k, v) {
        _this.fCanvas[k] = v;
      });
    }

    this.$canvasEditContainer
      .css('background', this.$imageElement.css('background'));

    this.loadCanvas(serializedCanvas);
  };


  /**
   * Turns off edit mode.
   *
   * Serializes all objects painted on canvas to base64 and sets it as
   * this.imageElement 'src' attribute.
   *
   * Serializes all objects painted on canvas to json and sets it as
   * this.imageElement 'data-canvas-serialized' attribute, so all canvas objects
   * could be restored for editing later.
   *
   * @private
   */
  Drawer.prototype._stopEditing = function () {
    this.log('canvasEditMode', '_stopEditing()');
    if (this.mode == this.MODE_INACTIVE) {
      this.log('canvasEditMode', '_stopEditing(): already stopped, mode is INACTIVE');
      return;
    }

    // see startEditingMethod for the reason of making redactor uneditable
    if (this.redactorInstance) {
      this.redactorInstance.$editor.attr('contenteditable', 'true');
    }

    // deactivate all tools
    this.trigger(this.EVENT_DO_DEACTIVATE_ALL_TOOLS);

    // torn off any selection on objects
    if (this.fCanvas) {
      this.fCanvas.deactivateAll();
    }

    // image should show what has been painted on canvas
    this.$imageElement.attr('src', this.getImageData());
    this.$imageElement.removeClass('edit-mode');
    this.$imageElement.removeClass('editable-canvas-not-edited');

    this.syncCanvasData();

    this.$imageElement.show();

    if (this.$canvasEditContainer) {
      this.$canvasEditContainer.remove();
      this.$canvasEditContainer = null;
    }

    $(window).off('resize.drawer' + this.id);

    this.trigger(this.EVENT_EDIT_STOP);
    this.mode = this.MODE_INACTIVE;
  };

  /**
   *
   * @param {String} newColor
   */
  Drawer.prototype.setActiveColor = function (newColor) {
    var currActiveObject = this.fCanvas.getActiveObject(),
      currActiveTool = this.activeDrawingTool,
      colorPluginInstance = this._pluginsInstances.Color,
      colorPickerControl = colorPluginInstance && colorPluginInstance.colorControl;


    this.activeColor = newColor;
    this.options.activeColor = newColor;
    if (!currActiveObject && colorPickerControl) {
      colorPickerControl.setColor(newColor);
    }
    if (currActiveTool) {
      currActiveTool.brush = null;
      currActiveTool._activateTool();
    }
    this.trigger(this.EVENT_CANVAS_MODIFIED);
  };


  /**
   * Changes active editor color.
   * When called without attributes re-sets previously saved color to canvas.
   *
   * @param {String|null} [newColor]
   * @param {Number|null} [newOpacity] - New opacity value
   */
  Drawer.prototype.setColor = function (newColor, newOpacity) {
    if (newColor) {
      this.activeColor = newColor;
    }
    if (newOpacity !== undefined) {
      this.activeOpacity = newOpacity;
    }

    if (!this.fCanvas) {
      return;
    }

    var activeObject = this.fCanvas.getActiveObject();
    this.setOpacity(this.activeOpacity, true);
    if (activeObject) {
      var isLineType = activeObject.type === 'line' || activeObject.type === 'arrow',
        havePath = activeObject.path;
      if (isLineType) {
        activeObject.set('stroke', this.activeColor);
      } else {
        if (havePath) {
          activeObject.set('stroke', this.activeColor);
        } else {
          activeObject.set('fill', this.activeColor);
        }
      }
      this.fCanvas.renderAll();
    }
    this.fCanvas.freeDrawingBrush.color = this.activeColor;
    this.fCanvas.freeDrawingBrush.fill = this.activeColor;
    this.trigger(this.EVENT_CANVAS_MODIFIED);
  };

  /**
   *
   * @param value
   * @param withoutProcessing
   */
  Drawer.prototype.setOpacity = function (value, withoutProcessing) {
    value = value !== undefined ? value : this.activeOpacity;
    this.activeOpacity = value;
    var activeObject = this.fCanvas.getActiveObject();
    if (activeObject) {
      activeObject.set('opacity', value);
    }
    this.fCanvas.freeDrawingBrush.opacity = value;
    if (!withoutProcessing) {
      this.fCanvas.renderAll();
      this.trigger(this.EVENT_CANVAS_MODIFIED);
    }
  };

  /**
   * Changes brush size in free drawing mode.
   *
   * @param {int|null} [newBrushSize]
   */
  Drawer.prototype.setBrushSize = function (newBrushSize) {
    if (this.fCanvas) {
      this.fCanvas.freeDrawingBrush.width = newBrushSize;
    }

    this.trigger(this.EVENT_BRUSH_SIZE_CHANGED);
  };

  /**
   * Returns size of currently selected tool brush.
   *
   * @returns {number}
   */
  Drawer.prototype.getBrushSize = function () {
    var size = 0;

    if (this.fCanvas) {
      size = this.fCanvas.freeDrawingBrush.width;
    }

    return size;
  };

  Drawer.prototype.setBrush = function (newBrush) {
    this.fCanvas.freeDrawingBrush = newBrush;
    this.trigger(this.EVENT_BRUSH_CHANGED);
  };

  Drawer.prototype.getBrush = function () {
    return this.fCanvas ? this.fCanvas.freeDrawingBrush : null;
  };

  /**
   * Get current sizes of Drawer
   * @returns {DrawerJs.DrawerApi.sizesOfDrawer}
   */
  Drawer.prototype.getSize = function () {
    var result = {},
      $container = this.$canvasEditContainer,
      containerSizes = $container && $container.get(0).getBoundingClientRect(),
      scrollSizes = $container && util.getScrollOffset($container);

    result.top = containerSizes ? containerSizes.top : null;
    result.left = containerSizes ? containerSizes.left : null;

    result.scrollTop = scrollSizes ? scrollSizes.top : null;
    result.scrollLeft = scrollSizes ? scrollSizes.left : null;

    result.width = this.width;
    result.height = this.height;
    return result;
  };

  /**
   * Changes canvas size.
   *
   * @param {number} width
   * @param {number} height
   */
  Drawer.prototype.setSize = function (width, height) {
    this.width = width;
    this.height = height;

    if (this.fCanvas) {
      this.fCanvas.setWidth(this.width);
      this.fCanvas.setHeight(this.height);
    }

    this.$imageElement.css('width', this.width);
    this.$imageElement.css('height', this.height);

    if (this.$canvasEditContainer) {
      this.$canvasEditContainer.css('width', this.width);
      this.$canvasEditContainer.css('height', this.height);
    }

    this.adjustEditContainer(false, true);
  };


  /**
   * Generates css for given align.
   * @param  {String} alignMode left|right|center|inline|floating
   * @return {Object} object with css properties
   */
  Drawer.prototype._generateAlignCss = function (alignMode) {
    var aligmentCss = {};

    switch (alignMode) {
      case 'floating':
        aligmentCss['position'] = 'absolute';
        aligmentCss['display'] = 'block';
        aligmentCss['float'] = 'none';
        aligmentCss['left'] = '0px';
        aligmentCss['top'] = '0px';
        break;

      case 'center':
        aligmentCss['float'] = 'none';
        aligmentCss['margin-left'] = 'auto';
        aligmentCss['margin-right'] = 'auto';
        aligmentCss['display'] = 'block';
        aligmentCss['position'] = 'static';
        break;

      case 'left':
      case 'right':
        aligmentCss['display'] = 'block';
        aligmentCss['float'] = alignMode;
        aligmentCss['position'] = 'static';
        break;

      case 'inline':
        aligmentCss['float'] = 'none';
        aligmentCss['display'] = 'inline-block';
        aligmentCss['position'] = 'static';
        break;

      default:
        aligmentCss['float'] = 'none';
        aligmentCss['display'] = 'inline-block';
        aligmentCss['position'] = 'static';
    }

    return aligmentCss;
  };

  /**
   * Changes canvas aligment.
   *
   * @param {String} align left|right|center|inline|floating
   */
  Drawer.prototype.setAlign = function (align) {
    var newAligmentCss = this._generateAlignCss(align);

    this.setAligmentCssFor(this.$imageElement, newAligmentCss);
    this.aligmentCss = this.getAligmentCssFor(this.$imageElement);

    this.adjustEditContainer();

    this.trigger(this.EVENT_CANVAS_MODIFIED);
  };

  /**
   * Returns current aligment setting for canvas.
   *
   * @returns {string} align left|right|center|inline|floating
   */
  Drawer.prototype.getAlign = function () {
    var currentAlign = 'inline';

    if (this.aligmentCss['position'] == 'absolute') {
      currentAlign = 'floating';
    }
    else if (this.aligmentCss['float'] == 'left') {
      currentAlign = 'left';
    } else if (this.aligmentCss['float'] == 'right') {
      currentAlign = 'right';
    } else if (this.aligmentCss['display'] == 'block' &&
      this.aligmentCss['margin-left'] == 'auto') {
      currentAlign = 'center';
    }

    return currentAlign;
  };

  /**
   *
   * @param {Boolean} [withAnimation]
   * @param {Boolean} [doNotUseDelay] - in some conditions need to use delay to prevent animation
   * @returns {boolean}
   */
  Drawer.prototype.adjustEditContainer = function (withAnimation, doNotUseDelay) {
    var self = this,
      drawerHaveAnimatedClass = this.$canvasEditContainer.hasClass('animated');
    if (!this.$canvasEditContainer) {
      return false;
    }

    var imageOffset = this.$imageElement.offset();
    if (!withAnimation) {
      this.$canvasEditContainer.removeClass('animated');
    }
    this.$canvasEditContainer.css({
      top: 0, //imageOffset.top
      left: 0 //imageOffset.left
    });
    if (!withAnimation) {
      if (doNotUseDelay) {
        self.$canvasEditContainer.toggleClass('animated', !!drawerHaveAnimatedClass);
      } else {
        util.setTimeout(function () {
          if (self.$canvasEditContainer && self.$canvasEditContainer.length) {
            self.$canvasEditContainer.toggleClass('animated', !!drawerHaveAnimatedClass);
          }
        }, 0);
      }
    }
  };

  /**
   * Removes itself from redactor entirely.
   */
  Drawer.prototype.destroy = function () {
    this.trigger(this.EVENT_DESTROY);
    this._stopEditing();
    this.$imageElement.remove();
    this.syncCanvasData(true);
    this.syncImageData(true);
  };


  /**
   * Activates default tool, if options.defaultActivePlugin is set;
   * Name of plugin is taken from options.defaultActivePlugin.name;
   * Depending on options.defaultActivePlugin.mode :
   * 'always'   - same plugin will be always activated
   * 'onNew'    - default plugin is activated, only if canvas is empty
   * 'lastUsed' - on first drawer run plugin options.defaultActivePlugin.name, on consecutive - last used tool
   */
  Drawer.prototype.activateDefaultPlugin = function () {
    // see, if we need to activate default tool
    if (!this.options.defaultActivePlugin) {
      return;
    }

    var pluginName = this.options.defaultActivePlugin.name;
    var pluginMode = this.options.defaultActivePlugin.mode;

    // mode 'always' and 'lastUsed' go always; onNew - only if canvas is empty
    if ((pluginMode == 'always') || (pluginMode == 'lastUsed') ||
      (pluginMode == 'onNew' && (this.fCanvas.getObjects().length === 0))) {

      this.log('defaultActivePlugin', pluginMode + '/' + pluginName);

      // if pluginMode is 'lastUsed' and we have already used tool - use it again
      if (this.lastUsedPluginName && pluginMode == 'lastUsed') {
        pluginName = this.lastUsedPluginName;
      }

      // if plugin 'pluginName' exists, activate it
      var defaultTool = this._pluginsInstances[pluginName];
      if (defaultTool) {
        this.trigger(this.EVENT_DO_ACTIVATE_TOOL, [defaultTool]);
      } else {
        this.log('WARNING', "options['defaultActivePlugin']['name'] is '" + pluginName + "', but no such plugin found.");
      }
    }

  };

  /**
   * This method will be triggered every time something on canvas change
   * to perform synchronization work, trigger events etc.
   *
   * @param {Boolean} ignoreOptions Some options like saveAfterInactiveSec
   * could modify this method behavior to postpone modify event for later
   * for better performance. This param allows to ignore any options when
   * {true} is passed.
   */
  Drawer.prototype.onCanvasModified = function (ignoreOptions) {
    var _this = this;

    if (ignoreOptions === undefined &&
      this.options.contentConfig.saveAfterInactiveSec) {

      if (this.__activityTimer !== undefined) {
        _this.log('saveAfterInactiveSec', 'Cleaning previous timeout');
        clearTimeout(this.__activityTimer);
      }

      _this.log('saveAfterInactiveSec', 'Setting up a timeout');
      this.__activityTimer = util.setTimeout(function () {
        _this.log('saveAfterInactiveSec',
          'Timeout happened, triggering onCanvasModified');
        _this.onCanvasModified(true);
      }, this.options.contentConfig.saveAfterInactiveSec * 1000);

      return false;
    }

    if (!this.redactorInstance) {
      // when working inside redactor, RedactorPlugin.js will do this for us
      // because one redactor instance could contain multiple Drawers and
      // it's RedactorPlugin's responsibility to synchronize all canvases
      // at same time
      this.beforeSync();
    }

    this.syncCanvasData();
    this.syncImageData();

    this.trigger(this.EVENT_CANVAS_MODIFIED);

    if (!this.redactorInstance) {
      this.afterSync();
    }
  };

  /**
   * Fabric.js allows objects controls configuration to be applied only
   * on instances of objects, but drawer provides global configuration
   * in 'objectControls' section.
   *
   * This method applies that config to specified object with respect to
   * touch/desktop config sections.
   *
   * @private
   */
  Drawer.prototype._updateObjectsControls = function (fObject) {
    var objProps = this.options.objectControls;

    if (this.touchDevice && this.options.objectControlsTouch) {
      objProps = this.options.objectControlsTouch;
    }

    if (objProps) {
      for (var propertyName in objProps) {
        if (objProps.hasOwnProperty(propertyName)) {
          fObject.set(propertyName, objProps[propertyName]);
        }
      }
    }
  };

  Drawer.prototype.getPluginInstance = function (pluginName) {
    if (!this._pluginsInstances[pluginName]) {
      throw new Error('Plugin not exists: ' + pluginName);
    } else {
      return this._pluginsInstances[pluginName];
    }
  };


  /**
   * Is called on EVENT_OPTIONS_CHANGED.
   * - changes background transparency
   */
  Drawer.prototype.onOptionsChange = function () {
    if (this.options.transparentBackground) {
      this.$imageElement.css('background', 'transparent');
      this.$canvasEditContainer.css('background', 'transparent');
    } else {
      this.$imageElement.css('background', 'white');
      this.$canvasEditContainer.css('background', 'white');
    }
  };


  /**
   * Returns translated text string from default vocabulary of from
   * specified 'texts' config.
   *
   * If string not found console.warn will be used and provided textString
   * will be returned.
   *
   * @param textString
   */
  Drawer.prototype.t = function (textString) {
    if (this.options.texts[textString]) {
      return this.options.texts[textString];
    } else {
      // console.warn('String not found in texts:' + textString);
      return textString;
    }
  };


  /**
   * Shows error message to user.
   *
   * @param {String} err
   */
  Drawer.prototype.showError = function (err) {
    // @todo: replace alert!
    alert(err);
  };

  /**
   * Returns x coord of drawer left-top
   *
   * @return {Number} left
   */
  Drawer.prototype.left = function () {
    return this.$canvasEditContainer.css('left').replace('px', '') | 0;
  };

  /**
   * Returns y coord of drawer left-top
   *
   * @return {Number} left
   */
  Drawer.prototype.top = function () {
    return this.$canvasEditContainer.css('top').replace('px', '') | 0;
  };

  Drawer.prototype.setTemporaryStyles = function (styles) {
    styles = styles || false;
    var stylesAreValid = !!styles,
      temporaryStyles = stylesAreValid ? $.extend(true, {}, this.fCanvas._temporaryStyles || {}, styles || {}) : false;
    for (var styleName in styles) {
      if (styles[styleName] === undefined) {
        delete temporaryStyles[styleName];
      }
    }
    if (temporaryStyles) {
      this.fCanvas._temporaryStyles = temporaryStyles;
      this.fCanvas.copiedTextStyle = {
        0: temporaryStyles
      };
      fabric.copiedTextStyle = {
        0: temporaryStyles
      };
    } else {
      this.fCanvas._temporaryStyles = temporaryStyles;
      this.fCanvas.copiedTextStyle = temporaryStyles;
      fabric.copiedTextStyle = temporaryStyles;
    }
  };

  /**
   * Get position relative to canvas from event
   * @param {Event} event
   * @param {Boolean} [considerZoom] @todo
   * @returns {Object}
   */
  Drawer.prototype.getRelativeEventPosition = function (event, considerZoom) {
    var result = {},
      absolutePosition = util.getEventPosition(event),
      scrollOffset = util.getScrollOffset(this.$canvasEditContainer),
      canvasContainer = this.$canvasEditContainer.get(0),
      canvasContainerSizes = canvasContainer.getBoundingClientRect();

    result.top = absolutePosition.top - canvasContainerSizes.top - scrollOffset.top;
    result.left = absolutePosition.left - canvasContainerSizes.left - scrollOffset.left;
    result.scaledTop = result.top;
    result.scaledLeft = result.left;


    // console.info('pos', result.left, result.scaledLeft, result.top, result.scaledTop);
    return result;
  };

  namespace.Drawer = Drawer;
}(jQuery, DrawerJs, DrawerJs.plugins, DrawerJs.util, DrawerJs.texts));

(function (Drawer, util, texts) {
  Drawer.prototype.defaultOptions = {
    captionText: 'Drawer',
    defaultWidth: '100%',
    defaultHeight: '500px',

    enableImageCrop: true,

    // TODO: should be fixed or removed
    editOnClick: true,

    exitOnOutsideClick: true,

    // default color for drawing/shapes
    activeColor: '#E80F07',
    activeOpacity: 1,

    // toolbar size in px
    toolbarSize: 35,
    toolbarSizeTouch: 45,

    // toolbars config
    // @todo: move it toolbars files?
    toolbars: {
      popupButtonAlwaysVisible: true,
      // drawing tools toolbar config
      drawingTools: {
        position: 'top'         // one of [left, right, top, bottom]
      },

      // active tool options toolbar config
      toolOptions: {
        position: 'bottom'      // one of [left, right, top, bottom]
      },

      // drawer main toolbar config
      settings: {
        position: 'right'       // one of [left, right, top, bottom]
      },
    },

    tooltipCss: {
      background: 'black',
      color: 'white'
    },

    // properties that will be applied to fabricjs canvas on creation
    canvasProperties: {
      selectionColor: 'rgba(255, 255, 255, 0.3)',
      selectionDashArray: [3, 8],
      selectionLineWidth: 1,
      selectionBorderColor: '#5f5f5f'
    },

    // properties that will be applied to every created object
    objectControls: {
      borderColor: 'rgba(102,153,255,0.75)',
      borderOpacityWhenMoving: 0.4,
      cornerColor: 'rgba(102,153,255,0.5)',
      cornerSize: 12,
      hasBorders: true
    },

    objectControlsTouch: {
      borderColor: 'rgba(102,153,255,0.75)',
      borderOpacityWhenMoving: 0.4,
      cornerColor: 'rgba(102,153,255,0.5)',
      cornerSize: 20,
      hasBorders: true
    },

    contentConfig: {
      saveAfterInactiveSec: null,
      saveInHtml: true,
      canvasDataContainer: null,
      imagesContainer: null,

      loadCanvasData: null,
      saveCanvasData: null,

      loadImageData: null,
      saveImageData: null
    },

    backgroundCss: 'white',
    borderCss: '1px dashed rgb(195, 194, 194)',
    borderCssEditMode: '1px dashed rgb(195, 194, 194)',

    defaultImageUrl: 'drawer.jpg',

    plugins: [],
    pluginsConfig: {},
    corePlugins: ['Zoom'],

    texts: texts,

    basePath: null,

    debug: false
  };

})(DrawerJs.Drawer, DrawerJs.util, DrawerJs.texts);

(function (Drawer) {
  /**
   * This event is triggered every time user clicks on canvas to edit it.
   * @type {string}
   */
  Drawer.prototype.EVENT_EDIT_START = 'editStart';
  /**
   * This event is triggered every time user stops editing canvas.
   * @type {string}
   */
  Drawer.prototype.EVENT_EDIT_STOP = 'editStop';
  /**
   * This event is triggered when canvas is deserialized
   * from image's attributes.
   * @type {string}
   */
  Drawer.prototype.EVENT_LOADED_FROM_JSON = 'loadedFromJson';
  /**
   * This event is triggered when canvas is deserialized, and is ready to work.
   * @type {string}
   */
  Drawer.prototype.EVENT_CANVAS_READY = 'ready';
  /**
   * This event is triggered after canvas resizing starts
   * from image's attributes.
   * @type {string}
   */
  Drawer.prototype.EVENT_CANVAS_START_RESIZE = 'canvas:resize:start';

  /**
   * This event is triggered after canvas resizing starts
   * from image's attributes.
   * @type {string}
   */
  Drawer.prototype.EVENT_BEFORE_RENDER = 'before:render';


  /**
   * This event is triggered after canvas resizing starts
   * from image's attributes.
   * @type {string}
   */
  Drawer.prototype.EVENT_AFTER_RENDER = 'after:render';
  /**
   * This event is triggered in process of canvas resize
   * from image's attributes.
   * @type {string}
   */
  Drawer.prototype.EVENT_CANVAS_RESIZING = 'canvas:resize:resizing';
  /**
   * This event is triggered after canvas resize stopped
   * from image's attributes.
   * @type {string}
   */
  Drawer.prototype.EVENT_CANVAS_STOP_RESIZE = 'canvas:resize:stop';
  /**
   * This event is triggered every time user changes a brush size.
   * @type {string}
   */
  Drawer.prototype.EVENT_BRUSH_SIZE_CHANGED = 'brushSizeChanged';
  /**
   * This event is triggered every time user selects a tool that changes
   * free drawing brush.
   * @type {string}
   */
  Drawer.prototype.EVENT_BRUSH_CHANGED = 'brushChanged';

  /**
   * Triggering this event will cause tool based on BaseTool to activate
   * @type {string}
   */
  Drawer.prototype.EVENT_DO_ACTIVATE_TOOL = 'activateTool';
  /**
   * Triggering this event will cause tool based on BaseTool to deactivate
   * @type {string}
   */
  Drawer.prototype.EVENT_DO_DEACTIVATE_TOOL = 'deactivateTool';
  /**
   * Triggering this event will cause to all tools based on BaseTool to deactivate.
   * This event is part of lifecycle of EVENT_DO_ACTIVATE_TOOL,
   * and is triggered every time after tool reacts on EVENT_DO_ACTIVATE_TOOL
   * @type {string}
   */
  Drawer.prototype.EVENT_DO_DEACTIVATE_ALL_TOOLS = 'deactivateAllTools';

  /**
   * Event emitted, when options have changed.
   * In most cases - if user opened used 'CanvasProperties' plugin.
   * @type {String}
   */
  Drawer.prototype.EVENT_OPTIONS_CHANGED = 'options.changed';
  /**
   * This event is triggered when user removes canvas from page.
   * @type {string}
   */
  Drawer.prototype.EVENT_DESTROY = 'destroy';


  Drawer.prototype.EVENT_TOOL_ACTIVATED = 'toolActivated';

  Drawer.prototype.EVENT_TOOL_DEACTIVATED = 'toolDeactivated';

  Drawer.prototype.EVENT_TOOLS_TOOLBAR_CREATED = 'toolsToolbarCreated';

  /**
   * This event is triggered when options toolbar is created and provides a way
   * to add buttons to it.
   * The second argument for this event is {DrawerToolbar} and can be used
   * to manipulate with t.
   *
   * @type {string}
   */

  Drawer.prototype.BEFORE_CREATE_TOOLBARS = 'beforeCreateToolbars';

  Drawer.prototype.AFTER_CREATE_TOOLBARS = 'afterCreateToolbars';

  Drawer.prototype.EVENT_OPTIONS_TOOLBAR_CREATED = 'optionsToolbarCreated';

  Drawer.prototype.EVENT_CONFIG_TOOLBAR_CREATED = 'configToolbarCreated';

  Drawer.prototype.EVENT_IMAGECROP_TOOLBAR_CREATED = 'imageCropToolbarCreated';

  Drawer.prototype.EVENT_FLOATING_TOOLBAR_CREATED = 'floatingToolbarCreated';

  Drawer.prototype.EVENT_MINIMIZED_TOOLBAR_CREATED = 'minimizedToolbarCreated';

  Drawer.prototype.EVENT_TOOLBAR_DESTROYED = 'toolbarDestroyed';

  Drawer.prototype.EVENT_TOOLBAR_CHANGE_STATE = 'toolbarChangeState';

  Drawer.prototype.EVENT_TOOLBAR_CLEAR_STATE = 'toolbarClearState';

  Drawer.prototype.EVENT_TOOLBAR_STATE_HIDDEN_OFF = 'toolbarShow';

  Drawer.prototype.EVENT_TOOLBAR_STATE_HIDDEN_ON = 'toolbarHide';

  Drawer.prototype.EVENT_TOOLBAR_STATE_OVERLAY_ON = 'toolbarOverlayShow';

  Drawer.prototype.EVENT_TOOLBAR_STATE_OVERLAY_OFF = 'toolbarOverlayHide';

  Drawer.prototype.EVENT_TOOLBAR_STATE_DISABLED_ON = 'toolbarDisableOn';

  Drawer.prototype.EVENT_TOOLBAR_STATE_DISABLED_OFF = 'toolbarDisableOff';

  Drawer.prototype.EVENT_RESTORE_DEFAULT_ZOOM = 'restoreDefaultZoom';

  Drawer.prototype.EVENT_CONTEXTMENU = 'contextmenu';

  Drawer.prototype.EVENT_KEYDOWN = 'keydown';

  Drawer.prototype.EVENT_BEFORE_SHAPE_ADD = 'beforeShapeAdd';

  Drawer.prototype.EVENT_AFTER_SHAPE_ADD = 'afterShapeAdd';

  Drawer.prototype.EVENT_ZOOM_SET = 'EVENT_ZOOM_SET';

  Drawer.prototype.EVENT_ZOOM_UNSET = 'EVENT_ZOOM_UNSET';

  Drawer.prototype.EVENT_ZOOM_UPPER_SET = 'EVENT_ZOOM_UPPER_SET';

  Drawer.prototype.EVENT_ZOOM_UPPER_UNSET = 'EVENT_ZOOM_UPPER_UNSET';

  Drawer.prototype.EVENT_ZOOM_UPPER_RESTORE = 'EVENT_ZOOM_UPPER_RESTORE';

  Drawer.prototype.EVENT_ZOOM_RESTORE = 'EVENT_ZOOM_RESTORE';


  Drawer.prototype.EVENT_ZOOM_CHANGE = 'zoomChange';

  Drawer.prototype.EVENT_CANVAS_MODIFIED = 'canvasModified';

  Drawer.prototype.EVENT_OBJECT_ADDED = 'objectAdded';

  Drawer.prototype.EVENT_OBJECT_SELECTED = 'objectSelected';

  Drawer.prototype.EVENT_OBJECT_MOVING = 'objectMoving';

  Drawer.prototype.EVENT_SELECTION_CLEARED = 'selectionCleared';

  Drawer.prototype.EVENT_TEXT_SELECTION_CHANGED = 'textSelectionChanged';

  Drawer.prototype.EVENT_TEXT_EDITING_ENTERED = 'textEditingEntered';

  Drawer.prototype.EVENT_TEXT_EDITING_EXITED = 'textEditingExited';

  Drawer.prototype.EVENT_TEXT_STYLES_CHANGED = 'textStylesChanged';

  Drawer.prototype.EVENT_TEXT_GET_STYLES = 'textGetStyles';

  Drawer.prototype.EVENT_OVERCANVAS_POPUP_SHOW = 'overcanvasPopupShow';

  Drawer.prototype.EVENT_OVERCANVAS_POPUP_HIDE = 'overcanvasPopupHide';

  Drawer.prototype.EVENT_OVERCANVAS_BUTTON_SHOW = 'overcanvasButtonShow';

  Drawer.prototype.EVENT_OVERCANVAS_BUTTON_HIDE = 'overcanvasButtonHide';

  Drawer.prototype.EVENT_IMAGE_CROP = 'initImageCrop';

  Drawer.prototype.EVENT_RESIZER_HIDE = 'resizerHide';

  Drawer.prototype.EVENT_RESIZER_SHOW = 'resizerShow';

  Drawer.prototype.EVENT_CREATE_TOOLTIP = 'createTooltip';

  Drawer.prototype.EVENT_HIDE_TOOLTIPS = 'hideTooltips';

  Drawer.prototype.EVENT_DESTROY_TOOLTIPS = 'destroyTooltips';

  /**
   * Remove event listeners by event name and(or) callback
   *
   * @param eventName
   * @param callback
   * @returns {*}
   */
  Drawer.prototype.off = function (eventName, callback) {
    return this._eventEmitter.off(eventName, callback);
  };

  /**
   * Add event listener to canvas element events.
   *
   * @param eventName
   * @param callback
   */
  Drawer.prototype.on = function (eventName, callback) {
    return this._eventEmitter.on(eventName, callback);
  };

  /**
   * Trigger any canvas event.
   *
   * @param eventName
   * @param [args]
   * @returns {*}
   */
  Drawer.prototype.trigger = function (eventName, args) {
    var eventResult,
      needToLogErors = !this.insideEvent;
    try {
      this.insideEvent = true;
      eventResult = this._eventEmitter.trigger(eventName, args);
    } catch (err) {
      if (this.options.debug) {
        var errorName = 'Catched error - ' + eventName;
        console.groupCollapsed(errorName);
        this.log('Event name', eventName);
        this.log('Arguments', args);
        this.error(err);
        console.groupEnd(errorName);
      }
    }
    if (needToLogErors) {
      this.insideEvent = false;
    }
    return eventResult;
  };
})(DrawerJs.Drawer);
(function (Drawer, util) {

  /**
   * Returns canvas data(information about all objects presented on canvas).
   *
   * The order of sources is following:
   * - fabricjs canvas instance(if available) toJSON method.
   * - options.contentConfig.loadCanvasData(if available)
   * - options.contentCOnfig.canvasDataContainer(if available)
   * - data-canvas-serialized attribute
   *   (if options.contentConfig.saveToHtml=true)
   *
   * @return {Object} serialized canvas data
   */
  Drawer.prototype.getCanvasData = function () {
    var serializedCanvas = null;

    if (this.fCanvas) {
      serializedCanvas = this.fCanvas.toJSON();
    } else if (this.options.contentConfig.loadCanvasData) {
      serializedCanvas = this.options.contentConfig.loadCanvasData(this.id);
    } else if (this.$canvasDataContainer) {
      var canvasDataText = this.$canvasDataContainer.text();
      if (canvasDataText) {
        serializedCanvas = JSON.parse(canvasDataText)[this.id];
      }
    }
    else {
      var attr = this.$imageElement.attr('data-canvas-serialized');
      if (attr) {
        serializedCanvas = JSON.parse(attr);
      }
    }

    return serializedCanvas;
  };


  /**
   * Returns data-url with image encoded to base64
   *
   * Firstly this method will try to get image data from fabric canvas instance.
   * If that is not available (for example when in edit mode)
   * it will check options for loadImageData function. If it is specified,
   * it will be invoked with this.id argument.
   *
   * If options.contentConfig.loadImageData is not set,
   * options.contentConfig.imagesContainer will be checked for data.
   *
   * @returns {String} image data encoded in base64/png.
   */
  Drawer.prototype.getImageData = function () {
    if (this.fCanvas) {
      this.imageData = this.fCanvas.toDataURL();
    } else if (this.options.contentConfig.loadImageData) {
      this.imageData = this.options.contentConfig.loadImageData(this.id);
      if (this.imageData[0] == '"' &&
        this.imageData[this.imageData.length - 1] == '"') {
        this.imageData = this.imageData.substr(1, this.imageData.length - 2);
      }
    } else if (this.$imagesContainer) {
      var imagesDataText = this.$imagesContainer.text();
      if (imagesDataText) {
        var imagesData = JSON.parse(imagesDataText);
        this.imageData = imagesData[this.id];
      }
    }

    return this.imageData;
  };




  /**
   * Loads canvas from serialized data.
   * Triggers EVENT_LOADED_FROM_JSON on complete.
   */
  Drawer.prototype.loadCanvas = function (serializedCanvas) {
    var _this = this;
    if (serializedCanvas) {
      this.fCanvas.loadFromJSON(serializedCanvas, function () {
        // now when we load everything we should adjust object's properties
        // for selection controls based on our config
        var allObjects = _this.fCanvas.getObjects();
        for (var o in allObjects) {
          _this._updateObjectsControls(allObjects[o]);
        }

        // set mode to ACTIVE and trigger event
        _this.mode = _this.MODE_ACTIVE;
        _this.trigger(_this.EVENT_LOADED_FROM_JSON);

        _this.fCanvas.renderAll();
        _this.onCanvasLoaded();
      });
    } else {
      // yes, we have not set all listeners, and better place for this
      // is in the end of startEditing(), but it does not affect anything,
      // so I let it here
      _this.mode = this.MODE_ACTIVE;
      _this.onCanvasLoaded();
    }
  };


  Drawer.prototype.onCanvasLoaded = function () {
    var _this = this;
    // subscribe to events only after deserialization to avoid triggering
    // for all objects while loading
    this.fCanvas.on('object:added', function (fEvent) {
      _this._updateObjectsControls(fEvent.target);
      _this.trigger(_this.EVENT_OBJECT_ADDED, fEvent);
      _this.onCanvasModified();
    });
    this.fCanvas.on('object:moving', function (fEvent) {
      _this.trigger(_this.EVENT_OBJECT_MOVING, fEvent);
    });
    this.fCanvas.on('object:modified', function () {
      _this.onCanvasModified();
    });
    this.fCanvas.on('before:render', function (fEvent) {
      _this.trigger(_this.EVENT_BEFORE_RENDER, fEvent);
    });
    this.fCanvas.on('after:render', function (fEvent) {
      _this.trigger(_this.EVENT_AFTER_RENDER, fEvent);
    });
    this.fCanvas.on('canvas:zoom:lower:set', function (fEvent) {
      _this.trigger(_this.EVENT_ZOOM_SET, fEvent);
    });
    this.fCanvas.on('canvas:zoom:lower:restore', function (fEvent) {
      _this.trigger(_this.EVENT_ZOOM_RESTORE, fEvent);
    });
    this.fCanvas.on('canvas:zoom:upper:set', function (fEvent) {
      _this.trigger(_this.EVENT_ZOOM_UPPER_SET, fEvent);
    });
    this.fCanvas.on('canvas:zoom:upper:restore', function (fEvent) {
      _this.trigger(_this.EVENT_ZOOM_UPPER_RESTORE, fEvent);
    });
    this.fCanvas.on('pencil:move:before', function (fEvent) {
      _this.trigger(_this.EVENT_ZOOM_UPPER_SET, fEvent);
    });
    this.fCanvas.on('pencil:move:after', function (fEvent) {
      _this.trigger(_this.EVENT_ZOOM_UPPER_RESTORE, fEvent);
    });


    this.fCanvas.on('object:removed', function () {
      _this.onCanvasModified();
    });
    this.fCanvas.on('object:selected', function (fEvent) {
      _this.trigger(_this.EVENT_OBJECT_SELECTED, fEvent);
    });
    this.fCanvas.on('selection:cleared', function (fEvent) {
      _this.trigger(_this.EVENT_SELECTION_CLEARED, fEvent);
    });
    this.fCanvas.on('text:selection:changed', function (fEvent) {
      _this.trigger(_this.EVENT_TEXT_SELECTION_CHANGED, fEvent);
    });
    this.fCanvas.on('text:editing:entered', function (fEvent) {
      _this.trigger(_this.EVENT_TEXT_EDITING_ENTERED, fEvent);
    });
    this.fCanvas.on('text:editing:exited', function (fEvent) {
      _this.trigger(_this.EVENT_TEXT_EDITING_EXITED, fEvent);
    });

    // restore brush and color settings
    this.setColor();
    // this.setBrushSize();

    // create all toolbars. Toolbars must be created after fCanvas is initialized.
    this.toolbars.createAllToolbars();

    // give upper-canvas some id to properly handle clicks inside it
    $(this.fCanvas.upperCanvasEl).attr('data-id', 1);

    this.setSize(this.width, this.height);

    this.$canvasEditContainer.on('contextmenu', function (event) {
      // disable context menu in drawer for any other plugins, but trigger an
      // event for all drawer-plugins to handle
      _this.trigger(_this.EVENT_CONTEXTMENU, [event]);
      return false;
    });

    util.bindLongPress(this.$canvasEditContainer, 'canvasEdit',
      function (event) {
        _this.trigger(_this.EVENT_CONTEXTMENU, [event]);
      });

    this.trigger(this.EVENT_EDIT_START);

    this.$canvasEditContainer.on('keydown', '.canvas-container', function (event) {
      _this.trigger(_this.EVENT_KEYDOWN, event);

      var isDelKey = event.which == 8,
        isBackspaceKey = event.which == 46,
        removeKeyTriggered = isDelKey || isBackspaceKey,
        needToDeleteActiveObj = removeKeyTriggered;

      if (needToDeleteActiveObj) {
        _this.fCanvas.renderOnAddRemove = false;
        var activeObject = _this.fCanvas.getActiveObject();
        if (activeObject) {
          activeObject.remove();

          event.preventDefault();
          event.stopPropagation();
        }

        var activeGroup = _this.fCanvas.getActiveGroup();
        if (activeGroup) {
          activeGroup.getObjects().map(function (canvasObject) {
            canvasObject.remove();
          });

          _this.fCanvas.discardActiveGroup();

          event.preventDefault();
          event.stopPropagation();
        }

        _this.fCanvas.renderAll();
        _this.fCanvas.renderOnAddRemove = true;

        return false;
      }
    });

    this.trigger(this.EVENT_CANVAS_READY);
  };

  Drawer.prototype.getSerializedCanvas = function () {
    var serializedCanvas = this.fCanvas.toJSON();
    var serializedCanvasStr = JSON.stringify(serializedCanvas, null, 2);
    return serializedCanvasStr;
  };


  Drawer.prototype.beforeSync = function () {
    // we do not want redactor to see image's data in its tag since it could be
    // huge.
    if (this.options.contentConfig && !this.options.contentConfig.saveInHtml) {
      this.$imageElement.attr('src', '');
    }

    if (this.fCanvas) {
      this.beforeSyncActiveObject = this.fCanvas.getActiveObject();
      this.beforeSyncActiveGroup = this.fCanvas.getActiveGroup();
      this.fCanvas.deactivateAll();
    }
  };

  Drawer.prototype.afterSync = function () {
    if (this.beforeSyncActiveObject) {
      this.fCanvas.setActiveObject(this.beforeSyncActiveObject);
      delete this.beforeSyncActiveObject;
    }

    if (this.beforeSyncActiveGroup) {
      this.fCanvas.setActiveGroup(this.beforeSyncActiveGroup);
      delete this.beforeSyncActiveGroup;
    }

    if (this.options.contentConfig && !this.options.contentConfig.saveInHtml) {
      this.$imageElement.attr('src', this.getImageData());
    }
  };

  /**
   * Synchronizes canvas data with storages specified in options.contentConfig.
   *
   * @param deleteItself
   */
  Drawer.prototype.syncCanvasData = function (deleteItself) {
    var _this = this;

    if (!_this.fCanvas) {
      return;
    }

    var serializedCanvasStr = this.getSerializedCanvas();

    if (_this.$canvasDataContainer) {
      var existingDataText = _this.$canvasDataContainer.text();
      var existingData = {};
      if (existingDataText.length > 0) {
        existingData = JSON.parse(existingDataText);
      }

      if (!deleteItself) {
        if (_this.fCanvas) {
          existingData[_this.id] = serializedCanvasStr;
        }
      } else {
        delete existingData[_this.id];
      }

      _this.$canvasDataContainer.text(JSON.stringify(existingData, null, 2));

      _this.log('sync', 'sync with data container done.');
    }

    if (this.options.contentConfig.saveCanvasData) {
      this.options.contentConfig.saveCanvasData(this.id, serializedCanvasStr);
    }

    if (this.options.contentConfig.saveInHtml) {
      this.$imageElement.attr('data-canvas-serialized', serializedCanvasStr);
    }
  };


  /**
   * Synchronizes image data(base64/png) string with storages defined in
   * options.contentConfig.
   *
   * @param deleteItself
   */
  Drawer.prototype.syncImageData = function (deleteItself) {
    var _this = this;

    var imageData = _this.getImageData();

    if (_this.$imagesContainer) {

      var existingDataText = _this.$imagesContainer.text();
      var existingData = {};
      if (existingDataText.length > 0) {
        existingData = JSON.parse(existingDataText);
      }

      if (!deleteItself) {
        existingData[_this.id] = imageData;
      } else {
        delete existingData[_this.id];
      }

      _this.$imagesContainer.text(JSON.stringify(existingData, null, 2));

      _this.log('sync', 'sync with images container done.');
    }

    if (_this.options.contentConfig.saveImageData) {
      _this.options.contentConfig.saveImageData(this.id, imageData);
    }
  };


})(DrawerJs.Drawer, DrawerJs.util);
(function (global) {
  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;


  /**
   *
   * @param {Object[]} eraserPathsData
   * @constructor
   */
  fabric.ErasableMixin = function (objData) {
    // clear eraserPaths. Do this, because inside fabric.Object.initialize()
    // options properties were merged into 'this', and this.eraserPaths is copy of objData.eraserPaths
    this.eraserPaths = [];
    if (objData) {
      // create eraserPaths from eraserPathsData
      this.eraserPaths = this._createEraserPaths(objData.eraserPaths);
    }
  };


  /**
   * Initialization of object instance.
   * Resets this.eraserPaths, then instantiates array of eraserPaths in it,
   * data taken from objData.eraserPaths
   *
   * @param {Object} objData
   */
  fabric.ErasableMixin.prototype.initialize = function (objData) {
    // add eraserPaths property to object instance
    this.eraserPaths = [];

    // TODO: do we need this??
    if (objData) {
      this.eraserPaths = fabric.ErasableMixin.prototype._createEraserPaths.call(this, objData.eraserPaths);
      // this.eraserPaths = this._createEraserPaths(objData.eraserPaths);
    }
  };


  /**
   * Fills this.eraserPaths with paths, created from objData.eraserPaths
   *
   * @param {Object[]} eraserPathsData
   * @private
   */
  fabric.ErasableMixin.prototype._createEraserPaths = function (eraserPathsData) {
    var eraserPaths = [];
    // fill this.eraserPaths with EraserPath objects, created from objData.eraserPaths
    if (eraserPathsData) {
      eraserPathsData.map(function (p) {
        fabric.EraserPath.fromObject(p, function (createdPath) {
          eraserPaths.push(createdPath);
        });
      });
    }
    return eraserPaths;
  };


  /**
   *
   * @param ctx
   */
  fabric.ErasableMixin.prototype.render = function ErasableMixinRender(ctx, mainObjectRenderer) {
    // get temp canvas
    var tempCanvas = DrawerJs.util.getTemporaryCanvas(this.canvas);
    var tempContext = tempCanvas.getContext('2d');

    // clear rect with size as working canvas
    var width = this.canvas.width;
    var height = this.canvas.height;
    tempContext.clearRect(0, 0, width, height);

    // render object in tempContext, (Object._render() will be called)
    mainObjectRenderer(tempContext);

    // now render erasers on top of object
    for (var i = 0; i < this.eraserPaths.length; i++) {
      this.eraserPaths[i].globalCompositeOperation = 'destination-out';
      this.eraserPaths[i].render(tempContext);
    }

    // render tempCanvas on working one
    ctx.drawImage(tempCanvas, 0, 0);
  };


  /**
   * Add eraser path to object.
   * Sets neccessary path properties;
   * Sets path angle same as object's,
   * to keep path points in same place - rotates them back individually
   *
   * @param {fabric.Path} path
   * @param {Function} callback
   */
  fabric.ErasableMixin.prototype.addEraserPath = function (srcPath, callback) {
    var self = this;
    // clone path, modify clone and add it
    srcPath.clone(function (clonedPath) {
      // eraser path must be non-interactive
      clonedPath.set('stroke', 'blue');
      clonedPath.selectable = false;
      clonedPath.evented = false;

      var thisCenter = self.getCenterPoint();
      var pathCenter = clonedPath.getCenterPoint();

      // eraser path offset to object
      clonedPath.polygonOffsetX = pathCenter.x - thisCenter.x;
      clonedPath.polygonOffsetY = pathCenter.y - thisCenter.y;

      // set path angle same, as angle of object
      var a = self.angle;
      clonedPath.set({ angle: a });

      // to keep path points in same place - rotate them by -a
      for (var i = 0; i < clonedPath.path.length; i++) {
        fabric.ErasableMixin.prototype._rotatePathPoints(clonedPath.path[i], thisCenter, -a);
      }

      // call to make path be in correct position with object
      fabric.ErasableMixin.prototype._updatePathPosition.call(self, clonedPath);

      // now add modified clonedPath to list of eraser paths
      self.eraserPaths.push(clonedPath);

      // call callback
      if (callback) {
        callback(this);
      }
    });
  };


  /**
   * Override default _set().
   * Updates eraser paths' position, angle and scale,
   * to be consistent with changes in object.
   *
   * @param key
   * @param value
   * @private
   */
  fabric.ErasableMixin.prototype._updateEPathsOnSet = function (key, value) {
    if (!this.eraserPaths)
      return;

    var thisCenter = this.getCenterPoint();

    for (var p = 0; p < this.eraserPaths.length; p++) {
      var ePath = this.eraserPaths[p];

      if (!(ePath instanceof fabric.EraserPath)) {
        continue;
      }

      var ePathScaleX = ePath.scaleX, ePathScaleY = ePath.scaleY;
      var coeff;

      // if object's scaleX has changed - update each ePath individual scaleX
      if (key == 'scaleX') {
        coeff = value / this.scaleX;
        ePathScaleX = ePathScaleX * coeff;
        ePath.set('scaleX', ePathScaleX);
      }

      // if object's scaleY has changed - update each ePath individual scaleY
      if (key == 'scaleY') {
        coeff = value / this.scaleY;
        ePathScaleY = ePathScaleY * coeff;
        ePath.set('scaleY', ePathScaleY);
      }

      // eraser's angle is always same as object's
      if (key == 'angle') {
        ePath.set('angle', this.angle);
      }

      fabric.ErasableMixin.prototype._updatePathPosition.call(this, ePath);
    }
  };


  fabric.ErasableMixin.prototype._updatePathPosition = function (ePath) {
    var thisCenter = this.getCenterPoint();
    var ePathScaleX = ePath.scaleX, ePathScaleY = ePath.scaleY;

    // global ePath origin coord = object center coord +  scaled ePath offset
    var globalOffsetPointX = thisCenter.x + (ePath.polygonOffsetX * ePathScaleX);
    var globalOffsetPointY = thisCenter.y + (ePath.polygonOffsetY * ePathScaleY);

    // calc ePath origin offset, respecting rotation
    var rotatedPoint = fabric.util.rotatePoint(
      new fabric.Point(globalOffsetPointX, globalOffsetPointY),
      thisCenter,
      fabric.util.degreesToRadians(ePath.angle)
    );

    ePath.set('left', rotatedPoint.x);
    ePath.set('top', rotatedPoint.y);
  };

  /**
    * Rotate all points in path around 'rotationOrigin' by 'angle'
    * Iterates through 'data', and makes changes in-place
    *
    * @param  array data  array with path commands and coords, and coords WILL BE CHANGED
    * @param  fabric.Point rotationOrigin
    * @param  number angle angle in degrees
    *
    * @return array
    */
  fabric.ErasableMixin.prototype._rotatePathPoints = function (data, rotationOrigin, angle) {
    var point, rotatedPoint;
    var angleRad = fabric.util.degreesToRadians(angle);
    var l = data.length;
    for (var i = 0; i < l; i++) {
      // skip non-numeric
      if (typeof data[i] != 'number')
        continue;

      // if data[i] is number, then it and next number are coords of point in path:
      // data[i] -> x, data[i + 1] -> y
      point = new fabric.Point(data[i], data[i + 1]);

      // rotate point around rotationOrigin
      rotatedPoint = fabric.util.rotatePoint(point, rotationOrigin, angleRad);

      // write rotatedPoint coords back in data[]
      data[i] = rotatedPoint.x;
      data[i + 1] = rotatedPoint.y;

      // jump to next pair
      i++;
    }

    return data;
  };


  /**
   * Returns object representation of an instance
   * @return {Object} Object representation of an instance
   */
  fabric.ErasableMixin.prototype.toObject = function () {
    var o = {
      eraserPaths: this.eraserPaths.map(function (p) {
        return p.toObject();
      })
    };
    return o;
  };


  /**
   * Returns fabric.ErasableObject instance from an object representation
   *
   * @static
   * @memberOf fabric.Polygon
   * @param {Object} objectData Object to create an instance from
   * @return {fabric.ErasableObject} Instance of fabric.ErasableObject
   */
  fabric.ErasableMixin.prototype.fromObject = function (objectData) {
    this.eraserPaths = fabric.ErasableMixin.prototype._createEraserPaths(objectData.eraserPaths);
    return this;
  };


  /**
   * @param {fabric.Object} obj
   */
  fabric.makeObjectErasable = function (obj) {
    var mixin = new fabric.ErasableMixin();

    // will be used in Eraser to determine, which objects are erasable
    obj.prototype.isErasable = true;

    var objInitialize = obj.prototype.initialize;
    obj.prototype.initialize = function () {
      // first call object initialize() with arguments
      objInitialize.apply(this, arguments);
      // then call mixin initialize()
      mixin.initialize.apply(this, arguments);
    };

    obj.prototype.addEraserPath = function (path) {
      mixin.addEraserPath.call(this, path);
    };

    var oldRender = obj.prototype.render;
    obj.prototype.render = function (ctx) {
      mixin.render.call(this, ctx, oldRender.bind(this));   // then render eraser paths
    };

    var oldSet = obj.prototype._set;
    obj.prototype._set = function (key, value) {
      mixin._updateEPathsOnSet.call(this, key, value);   // first update eraserPaths
      oldSet.call(this, key, value);  // then call original _set() of object
    };


    // fromObject - special case. It is part of obj, not its prototype
    var oldFromObject = obj.fromObject;
    obj.fromObject = function (objData, callback) {
      if (this.async) {
        // first call fromObject() of object
        oldFromObject.call(this, objData,
          function (createdObject) {
            // then call ErasableMixin.fromObject to instantiate eraserPath
            var objWithErasers = mixin.fromObject.call(createdObject, objData);
            if (callback) {
              callback(objWithErasers);
            }
          });
      } else {
        // first call fromObject() of object
        var createdObject = oldFromObject.call(this, objData);
        // then call ErasableMixin.fromObject to instantiate eraserPath
        var objWithErasers = mixin.fromObject.call(createdObject, objData);

        return objWithErasers;
      }
    };


    var oldToObject = obj.prototype.toObject;
    obj.prototype.toObject = function () {
      // first call oldToObject () of object
      var objData = oldToObject.call(this);
      // then extend result with
      extend(objData, mixin.toObject.call(this));

      return objData;
    };
  };


})(typeof exports !== 'undefined' ? exports : this);
(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  fabric.ErasableObject = fabric.util.createClass(fabric.Object, {
    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'erasableObject',

    /**
     * Constructor
     * @param {Object} objData object
     * @return {fabric.ErasableObject}
     */
    initialize: function (objData) {
      objData = objData || {};

      // call super[fabric.Object].initialize()
      this.callSuper('initialize', objData);
    }
  });


  /**
   * Creates fabric object from data.
   *
   * @param objData
   * @param {function} callback
   * @return {fabric.ErasableObject} Instance of fabric.ErasableObject
   */
  fabric.ErasableObject.fromObject = function (objData) {
    return new fabric.ErasableObject(objData);
  };

  // make our object erasable via ErasableMixin.
  fabric.makeObjectErasable(fabric.ErasableObject);

})(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;

  fabric.SegmentablePolygon = fabric.util.createClass(fabric.ErasableObject, {
    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'segmentablePolygon',

    /**
     * Points array
     * @type Array[]
     * @default
     */
    points: null,

    /**
     * Minimum X from points values, necessary to offset points
     * @type Number
     * @default
     */
    minX: 0,

    /**
     * Minimum Y from points values, necessary to offset points
     * @type Number
     * @default
     */
    minY: 0,

    /**
     * List of options to show when object is selected
     * @type {String[]}
     */
    objectOptionsList: ['color', 'border', 'opacity'],

    /**
     * Constructor
     * @param {Array} points Array of points
     * @param {Object} [options] Options object
     * @return {fabric.Polygon} thisArg
     */
    initialize: function (points, options) {
      var _this = this;

      this.points = points;
      if (points === undefined) {
        console.error('Points should be array.');
      }
      options = options || {};

      // call super[ErasableObject].initialize()
      this.callSuper('initialize', options);

      this._calcDimensions();
      if (!('top' in options)) {
        this.top = this.minY;
      }
      if (!('left' in options)) {
        this.left = this.minX;
      }
    },

    /**
     * @private
     */
    _calcDimensions: function () {
      var minX = null;
      var minY = null;
      var maxX = null;
      var maxY = null;

      for (var i = 0; i < this.points.length; i++) {
        var _minX = min(this.points[i], 'x');
        if (minX === null || _minX < minX) {
          minX = _minX;
        }

        var _minY = min(this.points[i], 'y');
        if (minY === null || _minY < minY) {
          minY = _minY;
        }

        var _maxX = max(this.points[i], 'x');
        if (maxX === null || _maxX > maxX) {
          maxX = _maxX;
        }

        var _maxY = max(this.points[i], 'y');
        if (maxY === null || _maxY > maxY) {
          maxY = _maxY;
        }
      }

      this.width = (maxX - minX) || 1;
      this.height = (maxY - minY) || 1;

      this.minX = minX;
      this.minY = minY;
    },

    _fixPoints: function () {
      this._calcDimensions();

      if (this.minX != (this.width / 2) * -1 || this.minY != (this.height / 2) * -1) {
        this.points.forEach(function (pointSegment) {
          pointSegment.forEach(function (p) {
            p.x -= (this.minX + this.width / 2);
            p.y -= (this.minY + this.height / 2);
          }, this);
        }, this);
      }
    },


    /**
     * @private
     */
    _applyPointOffset: function () {
      // change points to offset polygon into a bounding box
      // executed one time
      this.points.forEach(function (pointSegment) {
        pointSegment.forEach(function (p) {
          p.x -= (this.minX + this.width / 2);
          p.y -= (this.minY + this.height / 2);
        }, this);
      }, 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(),
        type: this.type
      });
    },

    render: function (ctx, noTransform) {
      // fix some stroke issue, save old stroke
      if (this.points[0].length == 2) {
        this.oldStroke = this.stroke;
        this.oldStrokeDashArray = this.strokeDashArray;
        this.oldStrokeWidth = this.strokeWidth;

        this.stroke = this.fill;
        this.strokeWidth = 3;
        this.strokeDashArray = [];
      }

      // call super[ErasableObject].render
      this.callSuper('render', ctx, noTransform);

      // fix some stroke issue again, restore old stroke
      if (this.points[0].length == 2) {
        this.stroke = this.oldStroke;
        this.strokeDashArray = this.oldStrokeDashArray;
        this.strokeWidth = this.oldStrokeWidth;
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _render: function (ctx) {
      this.commonRender(ctx);
      this._renderFill(ctx);
      if (this.stroke || this.strokeDashArray) {
        ctx.closePath();
        this._renderStroke(ctx);
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    commonRender: function (ctx) {
      var point;
      ctx.beginPath();

      if (this._applyPointOffset) {
        if (!(this.group && this.group.type === 'path-group')) {
          this._applyPointOffset();
        }
        this._applyPointOffset = null;
      }

      var firstSegment = this.points[0];

      // when we have only one point we should draw a small cirlce
      // so user could see it
      if (firstSegment.length == 1) {
        var radius = 2;
        ctx.arc(firstSegment[0].x, firstSegment[0].y,
          radius, 0, 2 * Math.PI, false
        );
      }

      ctx.moveTo(firstSegment[0].x, firstSegment[0].y);
      for (var i = 0, len = firstSegment.length; i < len; i++) {
        point = firstSegment[i];
        ctx.lineTo(point.x, point.y);
      }

      ctx.closePath();

      if (this.points.length > 1) {
        for (var s = 1; s < this.points.length; s++) {
          var segmentPoints = this.points[s];
          ctx.moveTo(segmentPoints[0].x, segmentPoints[0].y);

          for (var sp = 0; sp < segmentPoints.length; sp++) {
            ctx.lineTo(segmentPoints[sp].x, segmentPoints[sp].y);
          }

        }
      }
    },


    /**
     * Returns complexity of an instance
     * @return {Number} complexity of this instance
     */
    complexity: function () {
      var sum = 0;
      for (var i = 0; i < this.points.length; i++) {
        sum += this.points[i].length;
      }
      return sum;
    }

  });

  /**
   * 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.SegmentablePolygon.fromObject = function (object) {
    return new fabric.SegmentablePolygon(object.points, object, true);
  };

})(typeof exports !== 'undefined' ? exports : this);
(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * @class
   * @extends fabric.IText
   */
  fabric.PText = fabric.util.createClass(fabric.IText, {
    type: 'PText',

    _defaultOptions: {
      editIconMode: true,
    },

    /**
     * List of options to show when object is selected
     * @type {Array}
     */
    objectOptionsList: [
      'color',
      'border',
      'opacity',

      'TextLineHeight',
      'TextAlign'
    ],

    /**
     * Initializes object.
     * @param  {String} text
     * @param  {Object} options
     * @param  {boolean} options.editIconMode edit icon size, in pixels
     * @param  {number|String} options.editIconSize edit icon size, in pixels or strings:  small, medium, large
     */
    initialize: function (text, options) {
      this.callSuper('initialize', text, options);

      this.options = $.extend(true, this._defaultOptions || {}, options || {});

      // create icon
      var iconPath = DrawerJs.util.getDrawerFolderUrl() + 'src/pencil-square-o.32.png';

      // initialize control
      if (this.options.editIconMode) {
        this.startEditControl = new ObjectFloatingControl(this, iconPath, this._iconClickHandler.bind(this), this.options);
      }
    },
    /**
     * Copied from fabric js - add support of useCopiedStyles
     * Handles onInput event
     * @param {Event} e Event object
     */
    onInput: function (e) {
      if (!this.isEditing || this.inCompositionMode) {
        return;
      }
      var offset = this.selectionStart || 0,
        offsetEnd = this.selectionEnd || 0,
        textLength = this.text.length,
        newTextLength = this.hiddenTextarea.value.length,
        diff, charsToInsert, start;
      if (newTextLength > textLength) {
        //we added some character
        start = this._selectionDirection === 'left' ? offsetEnd : offset;
        diff = newTextLength - textLength;
        charsToInsert = this.hiddenTextarea.value.slice(start, start + diff);
      }
      else {
        //we selected a portion of text and then input something else.
        //Internet explorer does not trigger this else
        diff = newTextLength - textLength + offsetEnd - offset;
        charsToInsert = this.hiddenTextarea.value.slice(offset, offset + diff);
      }
      var emptySelection = this.selectionStart === this.selectionEnd,
        useCopiedStyles = emptySelection && this.canvas.copiedTextStyle;
      this.insertChars(charsToInsert, useCopiedStyles);
      e.stopPropagation();
    },

    renderCursorOrSelection: function (ctx) {
      this.canvas.fire('canvas:zoom:upper:set');
      this.callSuper('renderCursorOrSelection', ctx);
      this.canvas.fire('canvas:zoom:upper:restore');

    },


    /**
     * Overriding IText mouseup handler.
     * This version do not trigger editing mode on second click.
     * This functionality is inside _iconClickHandler
     */
    initMouseupHandler: function () {
      this.on('mouseup', function (evt) {
        if (this.options.editIconMode) {
          this.onMouseUpHandler(evt);
        } else {
          this.onMouseUpSuperHandler(evt);
        }
      });
    },


    onMouseUpHandler: function (evt) {
      this.__isMousedown = false;
      if (!this.editable || (this._isObjectMoved && this._isObjectMoved(evt.e))) {
        return;
      }

      this.selected = true;
    },

    /**
     *
     * @see fabric.js Text.onMouseUpSuperHandler
     * @param  {fabric.Event} evt
     */
    onMouseUpSuperHandler: function (evt) {
      this.__isMousedown = false;
      if (!this.editable || (this._isObjectMoved && this._isObjectMoved(evt.e))) {
        return;
      }

      if (this.__lastSelected && !this.__corner) {
        this.enterEditing(evt.e);
        if (this.selectionStart === this.selectionEnd) {
          this.initDelayedCursor(true);
        }
        else {
          this.renderCursorOrSelection();
        }
      }

      this.selected = true;
    },


    /**
     * Checks, if click on canvas is inside icon rect.
     * If yes - launches editing mode
     *
     * @param  {fabric.Event} evt
     */
    _iconClickHandler: function (evt) {
      //  select current object again, it is required for proper UI work
      this.canvas.setActiveObject(this);

      // enter editing and do stuff. Code is copied from IText.initMouseupHandler
      this.enterEditing(evt.e);
      if (this.selectionStart === this.selectionEnd) {
        this.initDelayedCursor(true);
      }
      else {
        this.renderCursorOrSelection();
      }
    },
    /**
     * Collect all styles which affects text
     *
     * @param  {fabric.Object} [obj] - text object
     * @returns {object} - styles object
     */
    getObjStyles: function (obj) {
      obj = obj || this;
      var styles = {
        fontSize: obj.fontSize,
        fill: obj.fill,
        textBackgroundColor: obj.textBackgroundColor,
        textDecoration: obj.textDecoration,
        fontFamily: obj.fontFamily,
        fontWeight: obj.fontWeight,
        fontStyle: obj.fontStyle,
        lineHeight: obj.lineHeight,
        stroke: obj.stroke,
        strokeWidth: obj.strokeWidth
      };
      return styles;
    },


    /**
     * Draws text and edit icon
     * @param  {Context2D} ctx
     */
    render: function (ctx) {
      this.callSuper('render', ctx);
    },


    toObject: function (propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude));
    }

  });


  /**
   * Creates fabric object from data.
   * Is sync, so simply returns new object.
   *
   * @param objData
   * @param {function} callback
   */
  fabric.PText.fromObject = function (objData) {
    return new fabric.PText(objData.text, objData);
  };

})(typeof exports !== 'undefined' ? exports : this);

(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * @class
   * @extends fabric.Image
   */
  fabric.ErasableText = fabric.util.createClass(fabric.PText, {
    type: 'ErasableText',
    isErasable: true,

    /**
     */
    initialize: function (text, options) {
      this.callSuper('initialize', text, options);
    },
  });


  /**
   * Creates fabric object from data.
   * Is async, so always use callback param.
   *
   * @param objData
   * @param {function} callback
   */
  fabric.ErasableText.fromObject = function (objData/*, callback*/) {
    return new fabric.ErasableText(objData.text, objData);
  };

  // important! set 'ErasableText.async'
  // It is already set for the prototype, but if do not set here - it WILL CRASH on image load from object;
  // idiotic stuff...
  // fabric.ErasableText.async = true;

  // make our object erasable via ErasableMixin.
  fabric.makeObjectErasable(fabric.ErasableText);

})(typeof exports !== 'undefined' ? exports : this);
(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;


  fabric.Arrow = fabric.util.createClass(fabric.Line, {

    type: 'arrow',

    objectCaching: false,

    /**
     * List of options to show when object is selected
     * @type {String[]}
     */
    objectOptionsList: ['border', 'opacity', 'lineWidth', 'strokeWidth'],


    initialize: function (points, options) {
      this.callSuper('initialize', points, options);
      if (options) {
        this.set('oneSided', options.oneSided);
      }
    },


    // _getNonTransformedDimensions :function(argument) {
    //     var strokeWidth = this.strokeWidth,
    //     w = this.width + strokeWidth + 30,
    //     h = this.height + strokeWidth + 30;

    //     return { x: w, y: h };
    // },

    _render: function (ctx, noTransform) {
      if (
        (this.width < 20 && this.height < 20) ||
        (this.scaleX < 0.3 && this.scaleY < 0.3)
      ) {
        this.set('padding', 20);
      } else {
        this.set('padding', 10);
      }

      this.canvas.calcOffset();

      this.callSuper('_render', ctx, noTransform);
      var points = this.callSuper('calcLinePoints');

      var angle = this._calcArrowAngle(points.x1, points.y1, points.x2, points.y2);

      if (!this.oneSided) {
        this._drawArrow(ctx, { x: points.x1, y: points.y1 }, angle + 90);
      }
      this._drawArrow(ctx, { x: points.x2, y: points.y2 }, angle - 90);
    },

    _drawArrow: function (ctx, point, angle) {
      var arrowSize = 0.5;

      if (this.get('strokeWidth') > 1) {
        arrowSize = arrowSize * (this.get('strokeWidth') / 2);
      }

      var arrowPointOffsetX = 10;
      var arrowPointOffsetY = 20;

      var frontCenterPoint = {
        x: point.x,
        y: point.y + (arrowPointOffsetX * arrowSize)
      };
      var leftPoint = {
        x: point.x - (arrowPointOffsetX * arrowSize),
        y: point.y - (arrowPointOffsetY * arrowSize)
      };
      var backCenterPoint = {
        x: point.x,
        y: point.y - (arrowPointOffsetX * arrowSize)
      };
      var rightPoint = {
        x: point.x + (arrowPointOffsetX * arrowSize),
        y: point.y - (arrowPointOffsetY * arrowSize)
      };

      ctx.save();

      ctx.translate(point.x, point.y);
      ctx.rotate(angle * Math.PI / 180);
      ctx.translate(point.x * -1, point.y * -1);

      ctx.beginPath();
      ctx.moveTo(frontCenterPoint.x, frontCenterPoint.y);
      ctx.lineTo(leftPoint.x, leftPoint.y);
      ctx.lineTo(backCenterPoint.x, backCenterPoint.y);
      ctx.lineTo(rightPoint.x, rightPoint.y);
      ctx.fillStyle = this.stroke;
      ctx.fill();

      ctx.restore();
    },

    _calcArrowAngle: function (x1, y1, x2, y2) {
      var angle = 0,
        x, y;

      x = (x2 - x1);
      y = (y2 - y1);

      if (x === 0) {
        angle = (y === 0) ? 0 :
          (y > 0) ? Math.PI / 2 : Math.PI * 3 / 2;
      }
      else if (y === 0) {
        angle = (x > 0) ? 0 : Math.PI;
      }
      else {
        angle = (x < 0) ? Math.atan(y / x) + Math.PI :
          (y < 0) ? Math.atan(y / x) + (2 * Math.PI) : Math.atan(y / x);
      }

      return (angle * 180 / Math.PI);
    },

    /**
     * Returns object representation of an instance
     */
    toObject: function (propertiesToInclude) {
      if (!propertiesToInclude) {
        propertiesToInclude = [];
      }

      propertiesToInclude.push('oneSided', 'padding');

      return fabric.util.object.extend(
        this.callSuper('toObject', propertiesToInclude),
        this.calcLinePoints()
      );
    }
  });


  /**
   * Returns fabric.Arrow instance from an object representation
   */
  fabric.Arrow.fromObject = function (object) {
    var points = [object.x1, object.y1, object.x2, object.y2];
    return new fabric.Arrow(points, object);
  };

})(typeof exports !== 'undefined' ? exports : this);
if (!this.DrawerJs) {
  this.DrawerJs = {};
}

this.DrawerJs.clipping = {};

(function (namespace) {
  "use strict";

  namespace.runClippingOperation = function (params) {
    var firstShape = params.firstShape;
    var secondShape = params.secondShape;

    var polyPoints = prepareForClipper(
      firstShape.width, firstShape.currentWidth,
      firstShape.height, firstShape.currentHeight,
      firstShape.center, firstShape.angleInRadians, firstShape.points);

    // TODO: another shape angle and scaling needed
    var anotherShapePointsGlobal =
      localToGlobal(secondShape.center, secondShape.points);

    var solution = processShape(params.cmd, polyPoints,
      uppercaseCoords(anotherShapePointsGlobal));

    if (secondShape.centersQueue) {
      for (var i = 0; i < secondShape.centersQueue.length; i++) {

        anotherShapePointsGlobal = localToGlobal(
          secondShape.centersQueue[i], secondShape.points
        );

        solution = processShape(
          params.cmd, solution, uppercaseCoords(anotherShapePointsGlobal)
        );
      }
    }

    solution = ClipperLib.JS.Lighten(solution, 0.1);

    var result = null;

    if (solution.length > 0) {
      result = restoreAfterClipper(
        firstShape.currentWidth, firstShape.width,
        firstShape.currentHeight, firstShape.height,
        firstShape.center, firstShape.angleInRadians * -1, solution);
    }

    return result;
  };


  var processShape = function (cmd, firstShapePoints, secondShapePoints) {
    var solution = new ClipperLib.Paths();
    var c = new ClipperLib.Clipper();

    c.AddPaths(firstShapePoints,
      ClipperLib.PolyType.ptSubject, true
    );

    c.AddPaths(secondShapePoints,
      ClipperLib.PolyType.ptClip, true
    );
    c.Execute(cmd, solution);

    return solution;
  };

  var localToGlobal = function (center, pointsSegments) {
    var result = [];
    for (var s = 0; s < pointsSegments.length; s++) {
      var segmentResult = [];
      for (var p = 0; p < pointsSegments[s].length; p++) {
        segmentResult.push({
          x: center.x + pointsSegments[s][p].x,
          y: center.y + pointsSegments[s][p].y
        });
      }
      result.push(segmentResult);
    }
    return result;
  };

  var globalToLocal = function (center, pointsSegments) {
    var result = [];
    for (var s = 0; s < pointsSegments.length; s++) {
      var segmentResult = [];
      for (var p = 0; p < pointsSegments[s].length; p++) {
        segmentResult.push({
          x: pointsSegments[s][p].x - center.x,
          y: pointsSegments[s][p].y - center.y
        });
      }
      result.push(segmentResult);
    }
    return result;
  };

  var rotatePoints = function (pointsSegments, center, angle) {
    var result = [];
    for (var s = 0; s < pointsSegments.length; s++) {
      var segmentResult = [];
      for (var p = 0; p < pointsSegments[s].length; p++) {
        var point = new fabric.Point(pointsSegments[s][p].x,
          pointsSegments[s][p].y
        );
        var rotatedPoint = fabric.util.rotatePoint(point, center, angle);
        segmentResult.push({
          x: rotatedPoint.x,
          y: rotatedPoint.y
        });
      }
      result.push(segmentResult);
    }
    return result;
  };

  var uppercaseCoords = function (pointsSegments) {
    var result = [];
    for (var s = 0; s < pointsSegments.length; s++) {
      var segmentResult = [];
      for (var p = 0; p < pointsSegments[s].length; p++) {
        segmentResult.push({
          X: pointsSegments[s][p].x,
          Y: pointsSegments[s][p].y
        });
      }
      result.push(segmentResult);
    }
    return result;
  };

  var lowercaseCoords = function (pointsSegments) {
    var result = [];
    for (var s = 0; s < pointsSegments.length; s++) {
      var segmentResult = [];
      for (var p = 0; p < pointsSegments[s].length; p++) {
        segmentResult.push({
          x: pointsSegments[s][p].X,
          y: pointsSegments[s][p].Y
        });
      }
      result.push(segmentResult);
    }
    return result;
  };

  var scaleCoords = function (originalWidth, scaledWidth,
    originalHeight, scaledHeight, pointsSegments) {
    var result = [];
    for (var s = 0; s < pointsSegments.length; s++) {
      var segmentResult = [];
      for (var p = 0; p < pointsSegments[s].length; p++) {
        var x = pointsSegments[s][p].x;
        var y = pointsSegments[s][p].y;

        var originalWidthPercent = x * 100 / originalWidth;
        var newX = originalWidthPercent * scaledWidth / 100;

        var originalHeightPercent = y * 100 / originalHeight;
        var newY = originalHeightPercent * scaledHeight / 100;

        segmentResult.push({
          x: newX,
          y: newY
        });
      }
      result.push(segmentResult);
    }
    return result;
  };

  var prepareForClipper = function (originalWidth, scaledWidth,
    originalHeight, scaledHeight,
    centerPoint, rotationAngleInRadians,
    pointsSegments) {
    var result = [];
    var sin = Math.sin(rotationAngleInRadians),
      cos = Math.cos(rotationAngleInRadians);

    for (var s = 0; s < pointsSegments.length; s++) {
      var segmentResult = [];
      for (var p = 0; p < pointsSegments[s].length; p++) {
        var x = pointsSegments[s][p].x;
        var y = pointsSegments[s][p].y;

        // --- scale ---
        var originalWidthPercent = x * 100 / originalWidth;
        var newX = originalWidthPercent * scaledWidth / 100;

        var originalHeightPercent = y * 100 / originalHeight;
        var newY = originalHeightPercent * scaledHeight / 100;
        // --- /scale ---

        // ---- local to global ----
        newX = centerPoint.x + newX;
        newY = centerPoint.y + newY;
        // ---- /local to global ----


        // ---- rotation angle ----
        // we could not use fabric point here because it's needed to be constructed
        // with memory allocation but that is bad idea for large data processing
        newX -= centerPoint.x;
        newY -= centerPoint.y;

        var rx = newX * cos - newY * sin,
          ry = newX * sin + newY * cos;

        newX = rx += centerPoint.x;
        newY = ry += centerPoint.y;
        // ---- /rotation angle ----

        segmentResult.push({
          X: newX,
          Y: newY
        });
      }
      result.push(segmentResult);
    }
    return result;
  };

  var restoreAfterClipper = function (originalWidth, scaledWidth,
    originalHeight, scaledHeight,
    centerPoint, rotationAngleInRadians,
    pointsSegments) {
    var result = [];
    var sin = Math.sin(rotationAngleInRadians),
      cos = Math.cos(rotationAngleInRadians);

    for (var s = 0; s < pointsSegments.length; s++) {
      var segmentResult = [];
      for (var p = 0; p < pointsSegments[s].length; p++) {
        var x = pointsSegments[s][p].X;
        var y = pointsSegments[s][p].Y;

        // ---- rotation angle ----
        // we could not use fabric point here because it's needed to be constructed
        // with memory allocation but that is bad idea for large data processing
        x -= centerPoint.x;
        y -= centerPoint.y;

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

        x = rx += centerPoint.x;
        y = ry += centerPoint.y;
        // ---- /rotation angle ----

        // ---- local to global ----
        x = x - centerPoint.x;
        y = y - centerPoint.y;
        // ---- /local to global ----

        // --- scale ---
        var originalWidthPercent = x * 100 / originalWidth;
        x = originalWidthPercent * scaledWidth / 100;

        var originalHeightPercent = y * 100 / originalHeight;
        y = originalHeightPercent * scaledHeight / 100;
        // --- /scale ---

        segmentResult.push({
          x: x,
          y: y
        });
      }
      result.push(segmentResult);
    }
    return result;
  };

})(DrawerJs.clipping);
(function (global) {

  'use strict';

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

  fabric.ErasableArrow = fabric.util.createClass(fabric.Arrow, {
    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'ErasableArrow',
    originX: 'center',
    originY: 'center',


    /**
     * Constructor
     * @param {Array} points Array of points
     * @param {Object} [options] Options object
     * @return {fabric.ErasableArrow}
     */
    initialize: function (points, options) {
      options = options || {};
      this.callSuper('initialize', points, options);
    },


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

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _render: function (ctx) {
      this.callSuper('_render', ctx);
    }

  });

  /**
   * 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.ErasableArrow.fromObject = function (object) {
    // form 'points' array, for first parameter in fabric.ErasableArrow c-tor
    var points = [object.x1, object.y1, object.x2, object.y2];
    return new fabric.ErasableArrow(points, object);
  };


  // make our object erasable via ErasableMixin.
  fabric.makeObjectErasable(fabric.ErasableArrow);
})(typeof exports !== 'undefined' ? exports : this);

(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  /**
   * @class
   * @extends fabric.Image
   */
  fabric.ErasableImage = fabric.util.createClass(fabric.Image, {
    type: 'ErasableImage',
    async: true,

    /**
     * List of options to show when object is selected
     * @type {String[]}
     */
    objectOptionsList: ['opacity', 'border'],

    /**
     * Initializes ErasableImage with fabric.Image
     *
     * @param {fabric.Image} fabricImage
     * @param options
     */
    initialize: function (fabricImage, options) {
      var _this = this;
      options = options || {};

      // set width and height
      this.width = options.width || 10;
      this.height = options.height || 10;

      this.callSuper('initialize', fabricImage, options);
    },

    _render: function (ctx) {
      this.callSuper('_render', ctx);
    },

    _set: function (key, value) {
      this.callSuper('_set', key, value);
    },

    toObject: function (propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude), {
        width: this.width,
        height: this.height
      });
    }

  });


  /**
   * Creates fabric object from data.
   * Is async, so always use callback param.
   *
   * @param objData
   * @param {function} callback
   */
  fabric.ErasableImage.fromObject = function (objData, callback) {
    fabric.util.loadImage(objData.src, function (createdImage) {
      var erasableImage = new fabric.ErasableImage(createdImage, objData);
      // call callback with instance of our erasableImage
      if (callback)
        callback(erasableImage);
    });
  };

  // important! set 'ErasableImage.async'
  // It is already set for the prototype, but if do not set here - it WILL CRASH on image load from object;
  // idiotic stuff...
  fabric.ErasableImage.async = true;

  // make our object erasable via ErasableMixin.
  fabric.makeObjectErasable(fabric.ErasableImage);

})(typeof exports !== 'undefined' ? exports : this);
(function (global) {

  'use strict';

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

  fabric.ErasableLine = fabric.util.createClass(fabric.Line, {
    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'ErasableLine',
    originX: 'center',
    originY: 'center',

    /**
     * List of options to show when object is selected
     * @type {String[]}
     */
    objectOptionsList: ['border', 'opacity', 'lineWidth', 'strokeWidth'],

    /**
     * Constructor
     * @param {Array} points Array of points
     * @param {Object} [options] Options object
     * @return {fabric.ErasableLine}
     */
    initialize: function (points, options) {
      options = options || {};
      this.callSuper('initialize', points, options);
    },


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

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _render: function (ctx) {
      this.callSuper('_render', ctx);
    }

  });

  /**
   * 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.ErasableLine.fromObject = function (object) {
    // form 'points' array, for first parameter in fabric.ErasableLine c-tor
    var points = [object.x1, object.y1, object.x2, object.y2];
    return new fabric.ErasableLine(points, object);
  };


  // make our object erasable via ErasableMixin.
  fabric.makeObjectErasable(fabric.ErasableLine);
})(typeof exports !== 'undefined' ? exports : this);

(function (global) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;

  fabric.ErasablePath = fabric.util.createClass(fabric.Path, {

    type: 'ErasablePath',

    objectCaching: false,
    evented: false,
    selectable: false,


    initialize: function (pathData, options) {
      options = options || {};
      this.callSuper('initialize', pathData, options);
    },


    _render: function (ctx) {
      this.callSuper('_render', ctx);
    },


    toObject: function (propertiesToInclude) {
      return this.callSuper('toObject', propertiesToInclude);
    }

  });

  /**
   * Creates object from  serialized data
   * @param  {Object} object
   * @return {ErasablePath}
   */
  fabric.ErasablePath.fromObject = function (data) {
    // @todo: maybe use code from fabric.Path.fromObject
    return new fabric.ErasablePath(data.path, data);
  };


  fabric.ErasablePath.async = false;

  // make our object erasable via ErasableMixin.
  fabric.makeObjectErasable(fabric.ErasablePath);

})(typeof exports !== 'undefined' ? exports : this);

(function (global) {
  'use strict';

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

  fabric.ErasablePencilBrush = fabric.util.createClass(fabric.PencilBrush, {

    type: 'ErasablePencilBrush',

    /**
     * List of tool options to show when object is selected
     * @type {String[]}
     */
    objectOptionsList: ['color', 'opacity', 'brush'],


    /**
     * Overriding fabric.PencilBrush.onMouseMove
     * Method code is exactly copy of fabric.PencilBrush.onMouseMove,
     * only change is trigger events
     *
     * @param {Object} pointer - object with "x" and "y" values
     * @see {fabric.PencilBrush.onMouseMove}
     */
    onMouseMove: function (pointer) {
      this._captureDrawingPath(pointer);
      // redraw curve
      // clear top canvas

      this.canvas.clearContext(this.canvas.contextTop);
      // this.canvas.fire('pencil:move:before');
      this._render();
      // this.canvas.fire('pencil:move:after');
      this.canvas.renderAll();
    },

    /**
     * Overriding fabric.PencilBrush._render
     * Method code is exactly copy of fabric.PencilBrush._render,
     * only change is trigger events
     *
     * @see {fabric.PencilBrush._render}
     * @private
     */
    _render: function () {
      this.canvas.fire('pencil:move:before');
      this.callSuper('_render');
      this.canvas.fire('pencil:move:after');
    },

    initialize: function (canvas, options) {
      options = options || {};
      // parent c-tor has no options argument, so adding them here
      this.options = options;
      // call parent c-tor
      this.callSuper('initialize', canvas);
    },


    /**
     * Overriding fabric.PencilBrush.createPath
     * Method code is exactly copy of fabric.PencilBrush.createPath,
     * only change is creating fabric.ErasablePath instead of fabric.Path
     *
     * Creates fabric.ErasablePath object to add on canvas
     * @param {String} pathData Path data
     * @return {fabric.Path} Path to add on canvas
     * @see {fabric.PencilBrush.createPath}
     */
    createPath: function (pathData) {
      var path = new fabric.ErasablePath(pathData, {
        fill: null,
        stroke: this.color,
        opacity: this.opacity,
        strokeWidth: this.width,
        strokeLineCap: this.strokeLineCap,
        strokeLineJoin: this.strokeLineJoin,
        strokeDashArray: this.strokeDashArray,
        originX: 'center',
        originY: 'center'
      });

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

      return path;
    }

  });

})(typeof exports !== 'undefined' ? exports : this);

(function (global, Util) {

  'use strict';

  var fabric = global.fabric || (global.fabric = {}),
    extend = fabric.util.object.extend;


  var ObjectFloatingControl = function (obj, iconPath, callback, options) {
    // set object
    if (!obj || !(obj instanceof fabric.Object)) {
      throw new Error('[ObjectFloatingControl()]  invalid param : obj');
    }
    this.obj = obj;

    // set iconPath
    if (!iconPath || !Util.isString(iconPath)) {
      throw new Error('[ObjectFloatingControl()]  invalid param : iconPath');
    }
    this.iconPath = iconPath;

    // set iconClickHandler
    if (!callback || !(callback instanceof Function)) {
      throw new Error('[ObjectFloatingControl()]  invalid param : callback');
    }
    this.callback = callback;

    // set options
    this.options = $.extend(true, {}, this._defaultOptions || {}, options || {});

    // by default control is enabled
    this.enabled = this.options.enabled;

    // load icon
    fabric.Image.fromURL(iconPath, this._onIconReady.bind(this));

    // set handlers on object added to / removed from canvas
    this.obj.on('added', this._onAdded.bind(this));
    this.obj.on('removed', this._onRemoved.bind(this));
  };


  ObjectFloatingControl.prototype.SMALL_ICON = 'small';
  ObjectFloatingControl.prototype.MEDIUM_ICON = 'medium';
  ObjectFloatingControl.prototype.LARGE_ICON = 'large';

  ObjectFloatingControl.prototype.LEFT_TOP = 'left-top';
  ObjectFloatingControl.prototype.LEFT_BOTTOM = 'left-bottom';
  ObjectFloatingControl.prototype.RIGHT_TOP = 'right-top';
  ObjectFloatingControl.prototype.RIGHT_BOTTOM = 'right-bottom';

  ObjectFloatingControl.prototype._defaultOptions = {
    defaulIconSize: 32,
    symbolicIconSizes: {
      'small': 16,
      'medium': 32,
      'large': 48
    },
    enabled: true
  };


  ObjectFloatingControl.prototype.remove = function () {
    this._onRemoved();
  };


  ObjectFloatingControl.prototype.enable = function () {
    this.enabled = true;
  };


  ObjectFloatingControl.prototype.disable = function () {
    this.enabled = false;
  };

  /**
   * After object was added to canvas - add our click handler on canvas
   * @param  {fabric.Event} evt
   */
  ObjectFloatingControl.prototype._onAdded = function (evt) {
    // object now has canvas property set
    this.canvas = this.obj.canvas;

    // set click handlers
    this._iconClickHandlerBinded = this._iconClickHandler.bind(this);
    this.canvas.on('mouse:up', this._iconClickHandlerBinded);
    this.canvas.on('touchend', this._iconClickHandlerBinded);

    // set after:render handler
    this._bindedAfterRender = this._onAfterRender.bind(this);
    this.canvas.on('after:render', this._bindedAfterRender);
  };


  /**
   * After object was removed from canvas - remove our click handler on canvas
   * @param  {fabric.Event} evt
   */
  ObjectFloatingControl.prototype._onRemoved = function (evt) {
    this.canvas.off('mouse:up', this._iconClickHandlerBinded);
    this.canvas.off('touchend', this._iconClickHandlerBinded);
    this.canvas.off('after:render', this._bindedAfterRender);
  };


  /**
   * When image is loaded - save it as this.icon,
   * and calc it dimensions;
   *
   * @param  {fabric.Image} image
   */
  ObjectFloatingControl.prototype._onIconReady = function (image) {
    var originalSize = this.options.iconSize || this.options.defaulIconSize,
      sizeFromOptions = this.options.editIconSize,
      neededSizeAsSymbolic = typeof sizeFromOptions === 'string' && this.options.symbolicIconSizes[sizeFromOptions],
      neededSize = neededSizeAsSymbolic || sizeFromOptions || this.options.defaulIconSize,
      iconScale = neededSize / originalSize;

    this.icon = image;
    this.iconSize = originalSize;
    this.iconScale = iconScale;

    // calc the best looking offset
    this.icon.offsetFromLeft = this.options.defaulIconSize * 0.66;
    this.icon.offsetFromTop = -this.iconSize * 0.66;

    this.icon.set({
      scaleX: iconScale,
      scaleY: iconScale,
      originX: 'center',
      originY: 'center',
      opacity: 1
    });
  };


  /**
   * If click on canvas is inside icon rect - launches iconClickHandler.
   *
   * @param  {fabric.Event} evt
   */
  ObjectFloatingControl.prototype._iconClickHandler = function (evt) {
    // checking for this.canvas to be sure, kinda hacky
    // checking for this.icon, as event can be fired, when icon is not ready yet
    if (!this.enabled || !this.canvas || !this.icon) {
      return;
    }

    var x, y;

    var evtCoords = this.canvas.getPointer(evt.e);

    if (evtCoords) {
      x = evtCoords.x;
      y = evtCoords.y;
    } else {
      // if no offsetX provided (case of touch events)
      if (evt.e.offsetX === undefined) {
        var drawerLeft = drawer.left();
        var drawerTop = drawer.top();

        // in case of touch event - we have no offestX, offsetY
        x = evt.e.pageX - drawerLeft;
        y = evt.e.pageY - drawerTop;
        console.log('x : ', evt.e.pageX, '-', drawerLeft, '=', x);
        console.log('y : ', evt.e.pageY, '-', drawerTop, '=', y);
      } else {
        x = evt.e.offsetX;
        y = evt.e.offsetY;
      }
    }

    if (this._coordInsideIcon(x, y)) {
      this.callback(evt, this);
    }
  };


  /**
   * Returns true, if coord is inside icon rect
   *
   * @param  {number} x
   * @param  {number} y
   * @return {Boolean}
   */
  ObjectFloatingControl.prototype._coordInsideIcon = function (x, y) {
    var rect = (this.icon.width * this.icon.scaleX) / 2,
      coords = this._calcIconCoords(),
      insideX = (x > coords.x - rect) && (x < coords.x + rect),
      insideY = (y > coords.y - rect) && (y < coords.y + rect),
      inside = insideX && insideY;

    return inside;
  };


  ObjectFloatingControl.prototype._onAfterRender = function (evt) {
    this.canvas.fire('canvas:zoom:lower:set');
    this._drawIcon();
    this.canvas.fire('canvas:zoom:lower:restore');
  };

  /**
   * Draws edit icon, if object is selected.
   *
   * @param  {context2D} ctx
   */
  ObjectFloatingControl.prototype._drawIcon = function () {
    if (this.enabled && this.icon && this.obj.active) {
      var coords = this._calcIconCoords();

      this.icon.left = coords.x;
      this.icon.top = coords.y;
      this.icon.render(this.canvas.getContext());
    }
  };


  /**
   * Calc icon coords based on object coords.
   * @return {Object} coords {x, y}
   */
  ObjectFloatingControl.prototype._calcIconCoords = function () {
    var realTopLeft = this.obj.getPointByOrigin('left', 'top');

    var xOffsetLocal = this.icon.offsetFromLeft * this.obj.scaleX;
    var yOffsetLocal = this.icon.offsetFromTop;

    var a = fabric.util.degreesToRadians(this.obj.angle);
    var xOffset = xOffsetLocal * Math.cos(a) - yOffsetLocal * Math.sin(a);
    var yOffset = xOffsetLocal * Math.sin(a) + yOffsetLocal * Math.cos(a);

    return { x: realTopLeft.x + xOffset, y: realTopLeft.y + yOffset };
  };

  global.ObjectFloatingControl = ObjectFloatingControl;

})(typeof exports !== 'undefined' ? exports : this, DrawerJs.util);

(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;

  fabric.SegmentableLine = fabric.util.createClass(fabric.Object, {
    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'SegmentableLine',

    /**
     * List of options to show when object is selected
     * @type {String[]}
     */
    objectOptionsList: ['border', 'opacity', 'lineWidth', 'strokeWidth'],

    /**
     * Points array
     * @type Array[]
     * @default
     */
    points: null,

    /**
     * Minimum X from points values, necessary to offset points
     * @type Number
     * @default
     */
    minX: 0,

    /**
     * Minimum Y from points values, necessary to offset points
     * @type Number
     * @default
     */
    minY: 0,

    /**
     * Constructor
     * @param {Array} points Array of points
     * @param {Object} [options] Options object
     * @return {fabric.Polygon} thisArg
     */
    initialize: function (points, options) {
      options = options || {};
      this.points = points;
      this.callSuper('initialize', options);
      this._calcDimensions();
      if (!('top' in options)) {
        this.top = this.minY;
      }
      if (!('left' in options)) {
        this.left = this.minX;
      }
    },
    /**
     * @private
     */
    _calcDimensions: function () {

      var points = this.points;

      var minX = null;
      var minY = null;
      var maxX = null;
      var maxY = null;

      for (var i = 0; i < this.points.length; i++) {
        var _minX = min(this.points[i], 'x');
        if (minX === null || _minX < minX) {
          minX = _minX;
        }

        var _minY = min(this.points[i], 'y');
        if (minY === null || _minY < minY) {
          minY = _minY;
        }

        var _maxX = max(this.points[i], 'x');
        if (maxX === null || _maxX > maxX) {
          maxX = _maxX;
        }

        var _maxY = max(this.points[i], 'y');
        if (maxY === null || _maxY > maxY) {
          maxY = _maxY;
        }
      }

      this.width = (maxX - minX) || 1;
      this.height = (maxY - minY) || 1;

      this.minX = minX;
      this.minY = minY;
    },

    /**
     * @private
     */
    _applyPointOffset: function () {
      // change points to offset polygon into a bounding box
      // executed one time
      this.points.forEach(function (pointSegment) {
        pointSegment.forEach(function (p) {
          p.x -= (this.minX + this.width / 2);
          p.y -= (this.minY + this.height / 2);
        }, this);
      }, 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(),
        type: this.type
      });
    },

    /* _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(
        '<', this.type, ' ',
        'points="', points.join(''),
        '" style="', this.getSvgStyles(),
        '" transform="', this.getSvgTransform(),
        ' ', this.getSvgTransformMatrix(),
        '"/>\n'
      );

      return reviver ? reviver(markup.join('')) : markup.join('');
    },
    /* _TO_SVG_END_ */

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _render: function (ctx) {
      this.commonRender(ctx);
      this._renderFill(ctx);
      if (this.stroke || this.strokeDashArray) {
        //ctx.closePath();
        this._renderStroke(ctx);
      }
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    commonRender: function (ctx) {
      var point;
      ctx.beginPath();

      if (this._applyPointOffset) {
        if (!(this.group && this.group.type === 'path-group')) {
          this._applyPointOffset();
        }
        this._applyPointOffset = null;
      }

      var firstSegment = this.points[0];

      ctx.moveTo(firstSegment[0].x, firstSegment[0].y);
      for (var i = 0, len = firstSegment.length; i < len; i++) {
        point = firstSegment[i];
        ctx.lineTo(point.x, point.y);
      }

      //ctx.closePath();

      if (this.points.length > 1) {
        for (var s = 1; s < this.points.length; s++) {
          var segmentPoints = this.points[s];
          ctx.moveTo(segmentPoints[0].x, segmentPoints[0].y);

          for (var sp = 0; sp < segmentPoints.length; sp++) {
            ctx.lineTo(segmentPoints[sp].x, segmentPoints[sp].y);
          }

        }
      }
    },

    /**
     * Returns complexity of an instance
     * @return {Number} complexity of this instance
     */
    complexity: function () {
      return this.points.length;
    }

  });

  /**
   * 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.SegmentableLine.fromObject = function (object) {
    return new fabric.SegmentableLine(object.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;

  fabric.PCircle = fabric.util.createClass(fabric.SegmentablePolygon, {

    radius: 0,
    type: 'PCircle',
    initialize: function (options) {
      var _this = this;
      options = options || {};
      this.radius = options.radius || 0;

      var points = this.makeCircle(this.radius);

      this.width = this.radius * 2;
      this.height = this.radius * 2;

      this.callSuper('initialize', points, options);
    },
    makeCircle: function (radius) {
      var points = [];

      var centerX = 0;
      var centerY = 0;

      for (var degree = 0; degree < 360; degree++) {
        var radians = degree * Math.PI / 180;
        var x = centerX + radius * Math.cos(radians);
        var y = centerY + radius * Math.sin(radians);
        points.push({ x: x, y: y });
      }

      return [points];
    },
    _render: function (ctx) {
      this.callSuper('_render', ctx);
    },
    _set: function (key, value) {
      if (key === 'radius') {
        this.points = this.makeCircle(value);
        this.width = value * 2;
        this.height = value * 2;
        this.callSuper('_set', 'points', this.points);
      }

      this.callSuper('_set', key, value);
    },
    isPointInside: function (x, y) {
      var center = this.getCenterPoint();
      return Math.sqrt(
        (x - center.x) * (x - center.x) + (y - center.y) * (y - center.y)
      ) < this.radius;
    },
    getPerimeterPoints: function (deltaX, deltaY, useEntireCircle) {
      var result;
      var sectorAngleOffset = 0,
        sectorAngle = 360;

      var deltaIsValid = typeof deltaX == 'number' && typeof deltaY == 'number',
        getPointsFromSector = deltaIsValid && !useEntireCircle;
      if (getPointsFromSector) {
        // Check if eraser tool position is changed
        if (deltaX || deltaY) {
          sectorAngle = deltaX !== 0 && deltaY !== 0 ? 90 : 180;
          if (deltaY < 0) {
            sectorAngleOffset = 180;
            if (deltaX > 0) {
              sectorAngleOffset = 270;
            }
          } else {
            if (deltaX < 0) {
              sectorAngleOffset = 90;
            }
          }
        }
      }

      result = this.getSectorPoints(sectorAngleOffset, sectorAngle);
      return result;
    },
    getSectorPoints: function (sectorAngleOffset, sectorAngle) {
      var result = [],
        sizeOfStepInDeg = 15,
        randomDiff = Math.round(Math.random() * sizeOfStepInDeg / 2),
        endPoint = sectorAngleOffset + sectorAngle + sizeOfStepInDeg - randomDiff,
        currPointAngle = sectorAngleOffset - randomDiff;

      for (currPointAngle; currPointAngle <= endPoint; currPointAngle += sizeOfStepInDeg) {
        var currPointCoords = {},
          currPointAngleRad = fabric.util.degreesToRadians(currPointAngle % 360),
          pointOffsetX = (this.radius * Math.cos(currPointAngleRad)),
          pointOffsetY = (this.radius * Math.sin(currPointAngleRad));

        currPointCoords.x = this.left + Math.round(pointOffsetX);
        currPointCoords.y = this.top + Math.round(pointOffsetY);
        result.push(currPointCoords);
      }
      return result;
    },
    toObject: function (propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude), {
        radius: this.radius
      });
    }
  });

  fabric.PCircle.fromObject = function (object) {
    return new fabric.PCircle(object, true);
  };

  fabric.PCircle.async = false;

})(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;

  fabric.PRect = fabric.util.createClass(fabric.SegmentablePolygon, {
    type: 'PRect',
    initialize: function (options) {
      var _this = this;
      options = options || {};

      this.width = options.width || 10;
      this.height = options.height || 10;

      var points = this.makeRect(this.width, this.height);

      this.callSuper('initialize', points, options);
    },
    makeRect: function (width, height) {
      var halfWidth = width / 2;
      var halfHeight = height / 2;

      var points = [
        { x: halfWidth * -1, y: halfHeight * -1 },
        { x: halfWidth, y: halfHeight * -1 },
        { x: halfWidth, y: halfHeight },
        { x: halfWidth * -1, y: halfHeight }
      ];

      return [points];
    },
    _render: function (ctx) {
      this.callSuper('_render', ctx);
    },
    _set: function (key, value) {
      var dimensionsChanged = false;
      if (key === 'width') {
        this.width = value;
        dimensionsChanged = true;
      }
      if (key === 'height') {
        this.height = value;
        dimensionsChanged = true;
      }
      if (dimensionsChanged) {
        this.points = this.makeRect(this.width, this.height);
        this.callSuper('_set', 'points', this.points);
      }

      this.callSuper('_set', key, value);
    },
    toObject: function (propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude), {
        width: this.width,
        height: this.height
      });
    }
  });


  fabric.PRect.fromObject = function (object) {
    return new fabric.PRect(object, true);
  };

  fabric.PRect.async = false;

})(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;

  fabric.PTriangle = fabric.util.createClass(fabric.SegmentablePolygon, {
    type: 'PTriangle',
    initialize: function (options) {
      var _this = this;
      options = options || {};

      this.width = options.width || 10;
      this.height = options.height || 10;

      var points = this.makeTriangle(this.width, this.height);

      this.callSuper('initialize', points, options);
    },
    makeTriangle: function (width, height) {
      var halfWidth = width / 2;
      var halfHeight = height / 2;

      var points = [
        { x: halfWidth * -1, y: halfHeight },
        { x: 0, y: halfHeight * -1 },
        { x: halfWidth, y: halfHeight }
      ];

      return [points];
    },
    _render: function (ctx) {
      this.callSuper('_render', ctx);
    },
    _set: function (key, value) {
      var dimensionsChanged = false;
      if (key === 'width') {
        this.width = value;
        dimensionsChanged = true;
      }
      if (key === 'height') {
        this.height = value;
        dimensionsChanged = true;
      }
      if (dimensionsChanged) {
        this.points = this.makeTriangle(this.width, this.height);
        this.callSuper('_set', 'points', this.points);
      }

      this.callSuper('_set', key, value);
    },
    toObject: function (propertiesToInclude) {
      return extend(this.callSuper('toObject', propertiesToInclude), {
        width: this.width,
        height: this.height
      });
    }
  });


  fabric.PTriangle.fromObject = function (object) {
    return new fabric.PTriangle(object, true);
  };

  fabric.PTriangle.async = false;

})(typeof exports !== 'undefined' ? exports : this);
var emptyFunc = function () { };

/**
 * Default configuration object of toolbar
 * @typedef {DrawerToolbar.defaultSetOfOptions} defaultToolbarOptions
 * @memberOf DrawerToolbar
 * @property {DrawerToolbar.defaultSetOfOptions} fullscreenMode - Options for fullscreen mode. Able to use same
 * options as for normal mode. Undefined options will be inherited from normal mode
 */

/**
 * Configuration object of button.
 * @typedef {Object} DrawerToolbar.buttonConfig
 * @property {String} [additionalClass] add specified class to button's <li> element.
 * @property {String} [iconClass] add specified class to button's <i> element.
 * @property {String} [tooltipText] Tooltip text that will be shown on mouse over.
 * @property {Number} [buttonOrder=10] Order priority of button. Button with min order value will be first.
 * @property {Boolean} [isSubMenu=false] Button is submenu of other button
 * @property {Function} [clickHandler] function that will be invoked when user clicks on this button.
 * @property {Object} [group]  Group object with group class name and tooltip text
 * @property {String} [group.name]  Group unique id
 * @property {String} [group.tooltip]  A tooltip text that will be shown on mouse over
 */

/**
 * Toolbar with tools like brush/rectangle/text etc.
 * @param {DrawerJs.Drawer} drawerInstance - Drawer instance
 * @param {DrawerToolbar.defaultSetOfOptions} [options] Configuration object
 * @constructor DrawerToolbar
 */
var DrawerToolbar = function (drawerInstance, options) {
  if (!drawerInstance) {
    throw new Error("DrawerToolbar c-tor : drawerInstance is not set!");
  }
  this.drawerInstance = drawerInstance;
  this.buttonsGroups = {};

  this._setupOptions(options);
  this._setupElement();
  this._attachEventHandlers();
  this._attachDrawerEventHandlers();
  this._initCompactType();
};

DrawerToolbar.prototype.MULTILINE = 'multiline';
DrawerToolbar.prototype.SCROLLABLE = 'scrollable';
DrawerToolbar.prototype.POPUP = 'popup';

DrawerToolbar.prototype.POSITION_TYPE_OUTSIDE = 'outside';
DrawerToolbar.prototype.POSITION_TYPE_INSIDE = 'inside';

/**
 * Toolbar position - one of [left, top, right, bottom, custom]
 * @type {string}
 */
DrawerToolbar.prototype.position = 'top';
// button groups
DrawerToolbar.prototype.buttonsGroups = {};
DrawerToolbar.prototype.buttons = [];

DrawerToolbar.prototype._defaultOptions = {
  compactType: DrawerToolbar.prototype.MULTILINE,
  positionType: DrawerToolbar.prototype.POSITION_TYPE_OUTSIDE,
  buttonWidth: 32,
  buttonHeight: 35
};

/**
 * Default values of button config
 * @type {DrawerToolbar.buttonConfig}
 * @private
 */
DrawerToolbar.prototype._defaultButtonConfig = {
  additionalClass: '',
  iconClass: '',
  tooltipText: '',
  buttonOrder: 10,
  isSubMenu: false,
  group: {
    name: '',
    tooltip: ''
  },
  clickHandler: emptyFunc
};

/**
 * Setup options
 * @param {Object} [options] - Configuration object
 * @private
 */
DrawerToolbar.prototype._setupOptions = function (options) {
  this.options = $.extend(true, {}, this._defaultOptions || {}, options || {});
};

/**
 * Create/setup toolbar element
 * @private
 */
DrawerToolbar.prototype._setupElement = function () {
  var toolbarHtml = this._generateTemplate(),
    $toolbar = $(toolbarHtml);
  this.$toolbar = $toolbar;
  this.$toolbarContentHolder = this.$toolbar.find('.toolbar-content-wrapper');
};

/**
 * Get html of toolbar element
 * @returns {String}
 * @private
 */
DrawerToolbar.prototype._generateTemplate = function () {
  var html,
    wrapperClasses = 'editable-canvas-toolbar ' +
      (this.options.toolbarClass || '') +
      (this.options.hidden ? ' hidden ' : '');

  html = '' +
    '<ul class="' + wrapperClasses + '" ' +
    'contenteditable="false"' +
    // 'tabindex="-1"' +
    '>' +
    '<ul class="toolbar-content-wrapper"></ul>' +
    '</ul>';
  return html;
};

/**
 * Setup events
 * @private
 */
DrawerToolbar.prototype._attachEventHandlers = function () {

};

/**
 * Setup drawer events
 * @private
 */
DrawerToolbar.prototype._attachDrawerEventHandlers = function () {
  var self = this;
  // always hide group dropdown when any tool activates
  this.drawerInstance.on(this.drawerInstance.EVENT_DO_ACTIVATE_TOOL, function () {
    // self.$toolbar.find('ul.group-items-container').addClass('hidden');
    // @todo: hide all open sub-menus
    self.hideActiveSubmenu();
  });
};

/**
 * Process "compactType" option
 * @private
 */
DrawerToolbar.prototype._initCompactType = function () {
  switch (true) {
    case (this.options.compactType === DrawerToolbar.prototype.SCROLLABLE):
      this._initCompactType_scrollable();
      break;
    case (this.options.compactType === DrawerToolbar.prototype.MULTILINE):
      this._initCompactType_multiline();
      break;
    case (this.options.compactType === DrawerToolbar.prototype.POPUP):
      this._initCompactType_popup();
      break;
    default:
      this._initCompactType_multiline();
      break;
  }
};

/**
 * Init 'scrollable' compact type
 * @private
 */
DrawerToolbar.prototype._initCompactType_scrollable = function () {
  this.isScrollable = true;
  this.scrollModeActive = false;
  this.currentScrollOffset = 0;

  this.$toolbar.addClass('toolbar-scrollable');
  this._addScrollButtons();

  // look, if show scroll UI on drawer resize and edit start

  this.drawerInstance.on(this.drawerInstance.EVENT_CANVAS_START_RESIZE, this._onCanvasResizeStart.bind(this));
  this.drawerInstance.on(this.drawerInstance.EVENT_CANVAS_RESIZING, this._onCanvasResizing.bind(this));
  this.drawerInstance.on(this.drawerInstance.EVENT_CANVAS_STOP_RESIZE, this._onCanvasResizeFinish.bind(this));
  this.drawerInstance.on(this.drawerInstance.EVENT_EDIT_START, this.checkScroll.bind(this));

  // handle scrolling by swipe
  this.$toolbar.on('mousedown.toolbar touchstart.toolbar', this.onTouchStart.bind(this));
};

/**
 * Init 'multiline' compact type
 * @private
 */
DrawerToolbar.prototype._initCompactType_multiline = function () {
  this.$toolbar.addClass('toolbar-multiline');
};

/**
 * Init 'popup' compact type
 * @private
 */
DrawerToolbar.prototype._initCompactType_popup = function () {
  this._initCompactType_multiline();
};


/**
 * Removes toolbar element.
 * @fires DrawerJs.Drawer.EVENT_TOOLBAR_DESTROYED
 */
DrawerToolbar.prototype.remove = function () {
  this.$toolbar.remove();
  this.buttonsGroups = {};
  this.drawerInstance.trigger(this.drawerInstance.EVENT_TOOLBAR_DESTROYED, [this]);
};


/**
 * Adds control to toolbar.
 * @param {jQuery|HTMLElement} control
 * @param buttonOrder
 */
DrawerToolbar.prototype.addControl = function (control, buttonOrder) {
  buttonOrder = buttonOrder !== undefined ? buttonOrder : this._defaultButtonConfig.buttonOrder;
  var orderString = this._getOrderString(buttonOrder),
    currStyleAttr = control.attr('style');
  control.attr('style', (currStyleAttr || '') + ';' + orderString);
  this.$toolbarContentHolder.append(control);
};


/**
 * Add a button to this toolbar.
 * @param {DrawerToolbar.buttonConfig} options Button configuration object
 */
DrawerToolbar.prototype.addButton = function (options) {
  options = this._validateButtonConfig(options);
  var $button = this.createButton(options);

  this.buttons.push($button);

  // add button and create tooltip
  this.$toolbarContentHolder.append($button);

  // toolbar grew bigger, so call check for scroll
  this.checkScroll();

  return $button;
};

/**
 * Add a button to this toolbar. Button will be appended to group.
 * Group is a one button which will show dropdown with its buttons on click.
 *
 * @param {DrawerToolbar.buttonConfig} options Button configuration object
 */
DrawerToolbar.prototype.addButtonToGroup = function (options) {
  options = this._validateButtonConfig(options);
  var $groupContainer = this.buttonsGroups[options.group.name],
    groupElementIsExist = $groupContainer && $groupContainer.length,
    needToCreateGroup = !groupElementIsExist;

  // create group. if no group exists
  if (needToCreateGroup) {
    this._createAndAppendGroup(options.group, options.iconClass, options.buttonOrder);
    $groupContainer = this.buttonsGroups[options.group.name];
  }

  // create button
  options.isSubMenu = true;
  var $button = this.createButton(options);

  // and append to toolbar
  $groupContainer.$submenuWrapper.buttons.push($button);
  $groupContainer.find('.group-items-container').append($button);

  return $button;
};

/**
 * Check values of button config object
 * @param {DrawerToolbar.buttonConfig} options - button configuration
 * @returns {DrawerToolbar.buttonConfig}
 */
DrawerToolbar.prototype._validateButtonConfig = function (options) {
  options = options || {};
  var defaultConf = $.extend(true, {}, this._defaultButtonConfig),
    result = $.extend(true, {}, defaultConf, options);

  result.buttonOrder = typeof result.buttonOrder === 'number' ? result.buttonOrder : defaultConf.buttonOrder;
  result.isSubMenu = result.isSubMenu !== undefined ? result.isSubMenu : defaultConf.isSubMenu;
  result.clickHandler = typeof result.clickHandler === 'function' ? result.clickHandler : defaultConf.clickHandler;
  return result;
};

DrawerToolbar.prototype._getOrderString = function (orderValue) {
  var orderString = '' +
    '-webkit-order:' + orderValue + ';' +
    '-ms-flex-order:' + orderValue + ';' +
    'order:' + orderValue + ';';
  return orderString;
};

/**
 * Creates jQuery object with button element markup.
 *
 * @param {DrawerToolbar.buttonConfig} options - button configuration
 * @returns {jQuery}
 */
DrawerToolbar.prototype.createButton = function (options) {
  var orderString = this._getOrderString(options.buttonOrder),
    styleString = 'style="' + orderString + '"';
  // button html
  var $button,
    submenuClass = options.isSubMenu ? ' submenu-child ' : ' ',
    classString = 'toolbar-button ' + options.additionalClass + submenuClass,
    buttonHtml = '' +
      '<li ' +
      'class="' + classString + '"' +
      'data-tooltip-class="' + options.additionalClass + '"' +
      styleString +
      '>' +
      '<a href="#" ' +
      'class="toolbar-button-icon ' + submenuClass + '"' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      'data-editable-canvas-cssrules="line-height,font-size:($v / 2.5)" tabindex="-1"' +
      '>' +
      '<i class="fa ' + options.iconClass + ' ' + submenuClass + '"></i>' +
      '</a>' +
      '</li>';
  $button = $(buttonHtml);

  var tooltipOptions = {
    additionalClass: options.additionalClass,
    text: options.tooltipText,
    position: 'bottom'
  };
  $button.tooltip = this.drawerInstance.trigger(this.drawerInstance.EVENT_CREATE_TOOLTIP, [$button.find('a'), tooltipOptions]);

  // prevent default behavior on link click
  var $link = $button.find('a');
  $link.on('click', function (e) {
    e.preventDefault();
    e.stopPropagation();
  });

  // set click handler
  DrawerJs.util.bindClick($link, 'editable-canvas-toolbar-button', options.clickHandler);
  return $button;
};


/**
 * Remove button and attached handlers
 * @param {jQuery} $button - Button element
 */
DrawerToolbar.prototype.removeButton = function ($button) {
  if (!$button) {
    console.warn("DrawerToolbar.removeButton() : no $button is provided!");
    return;
  }
  // unbind click handler
  DrawerJs.util.unbindClick($button.find('a'), 'editable-canvas-toolbar-button');
  // remove element
  $button.remove();

};

/**
 * Creates group button with empty list of tools, and appends it to toolbar
 *
 * @param {Object} group - group data object
 * @param {string} group.name - name of group
 * @param {string} iconClass  - css icon class of group button
 * @param {Number} buttonOrder  - button order value
 * @private
 */
DrawerToolbar.prototype._createAndAppendGroup = function (group, iconClass, buttonOrder) {
  // group container html
  var orderString = this._getOrderString(buttonOrder),
    styleString = 'style="' + orderString + '"';
  var $groupContainer = $(
    '<li class="toolbar-button btn-group group-' + group.name + '"' +
    'data-editable-canvas-sizeable="toolbar-button" ' +
    'data-editable-canvas-cssrules="width,height"' +
    styleString +
    '>' +
    '<a href="#" ' +
    'class="toolbar-button-icon"' +
    'data-editable-canvas-sizeable="toolbar-button" ' +
    'data-editable-canvas-cssrules="line-height,font-size:($v / 2.5)" ' +
    'tabindex="-1"' +
    '>' +
    '<i class="fa ' + iconClass + '"></i>' +
    '</a>' +
    '<div class="submenu-wrapper">' +
    '<ul class="group-items-container submenu-child toolbar-dropdown-block">' +
    '</ul>' +
    '</div>' +
    '</li>');

  $groupContainer.$submenuWrapper = $groupContainer.find('.submenu-wrapper');
  $groupContainer.$submenuWrapper.buttons = [];

  // add group container to toolbar
  this.$toolbarContentHolder.append($groupContainer);
  this.buttonsGroups[group.name] = $groupContainer;

  // prevent default action on link click - moving to anchor
  $groupContainer.find('a').on('click', function (e) {
    e.preventDefault();
  });
  // react on click
  $groupContainer.on('click', this.onGroupButtonClick.bind(this, $groupContainer));

  // create tooltip to group

  var tooltipOptions = {
    additionalClass: group.name,
    text: group.tooltip,
    position: 'bottom'
  };
  this.drawerInstance.trigger(this.drawerInstance.EVENT_CREATE_TOOLTIP, [$groupContainer.children('a'), tooltipOptions]);
};

/**
 * Set active button
 * @param {String} buttonClassName
 */
DrawerToolbar.prototype.setActiveButton = function (buttonClassName) {
  var $button = this.$toolbar.find('.toolbar-button.' + buttonClassName),
    $buttonIcon = $button.find('.toolbar-button-icon');
  $buttonIcon.addClass('active');
};

/**
 * Remove active state for all buttons
 */
DrawerToolbar.prototype.clearActiveButton = function () {
  var $activeButton = this.$toolbar.find('.toolbar-button .toolbar-button-icon.active');
  $activeButton.removeClass('active');
};


/**
 * React on group button click
 * @param {jQuery} $groupButtton - Group container
 * @param {Event} evt
 * @private
 */
DrawerToolbar.prototype.onGroupButtonClick = function ($groupButtton, evt) {
  // copy submenu, including handlers
  this.activeSubmenu = $groupButtton.$submenuWrapper.clone(true);
  // var $submenu = $submenuSrc.clone(true);

  // position our submenu below button
  var $btn = $(evt.currentTarget);

  // button position relative to toolbar placeholder =
  // button position relative to toolbarContentWrapper - toolbarOffset +
  // toolbar padding
  if (this.isHorizontal()) {
    var paddingLeft = parseInt(this.$toolbar.css('padding-left'));
    var left = $btn.position().left - this.currentScrollOffset + paddingLeft;
    this.activeSubmenu.css('left', left + 'px');
  } else {
    var paddingTop = parseInt(this.$toolbar.css('padding-top'));
    var top = $btn.position().top - this.currentScrollOffset + paddingTop;
    this.activeSubmenu.css('top', top + 'px');
  }

  // append to toolbar before positioning, so this.activeSubmenu has width and height for further calculations
  this.$toolbar.parent().append(this.activeSubmenu);

  // offset of submenu relative to toolbar placeholder
  var submenuOffset = 0;
  switch (this.position) {
    case 'custom':
    case 'top':
      // position submenu below of the toolbar
      submenuOffset = this.$toolbar[0].offsetTop + this.$toolbar.height();
      this.activeSubmenu.css('top', submenuOffset + 'px');
      break;
    case 'left':
      // position submenu to the right of toolbar
      submenuOffset = this.$toolbar[0].offsetLeft + this.$toolbar.width();
      this.activeSubmenu.css('left', submenuOffset + 'px');
      break;
    case 'right':
      // position submenu to the left of toolbar
      submenuOffset = this.$toolbar[0].offsetLeft - this.$toolbar.width();
      this.activeSubmenu.css('left', submenuOffset + 'px');
      break;
    case 'bottom':
      // position submenu above of toolbar
      submenuOffset = this.$toolbar[0].offsetTop - this.activeSubmenu.height();
      this.activeSubmenu.css('top', submenuOffset + 'px');
      break;
  }

  $('body').on('mousedown.submenu', this.onSubmenuMouseDown.bind(this));

};

/**
 * React on submenu click
 * @param {Event} evt
 * @private
 */
DrawerToolbar.prototype.onSubmenuMouseDown = function (evt) {
  if (!this.activeSubmenu)
    return;
  if (evt.target == this.activeSubmenu || $(evt.target).hasClass('submenu-child'))
    return;

  this.hideActiveSubmenu();
};

/**
 * Hide active submenu
 */
DrawerToolbar.prototype.hideActiveSubmenu = function () {
  if (this.activeSubmenu) {
    // hide all tooltips
    this.drawerInstance.trigger(this.drawerInstance.EVENT_HIDE_TOOLTIPS);

    this.activeSubmenu.remove();
    this.activeSubmenu = null;
    $('body').off('mousedown.submenu');

  }
};

// DrawerToolbar.prototype.onGroupButtonClick = function(evt, button) {

// };

/**
 * Makes tool button with css class buttonClass as active.
 * This is achieved by making group button copy of the tool button.
 *
 * @param groupName
 * @param buttonClass
 * @private
 */
DrawerToolbar.prototype._setGroupButtonActive = function (groupName, buttonClass) {
  // find group and button
  var $groupContainer = this.buttonsGroups[groupName];
  var $button = this.$toolbar.find('.' + buttonClass);

  // set $groupContainer css class same as $button class
  $groupContainer.attr('class', $button.attr('class'));
  $groupContainer.addClass('btn-group');

  var groupButton = $groupContainer.children('a');
  // make $button.a active
  groupButton.addClass('active');
  // copy $button.a html to $groupContainer.a
  groupButton.html($button.children('a').html());
};


/**
 * Setup/add scroll buttons
 * @private
 */
DrawerToolbar.prototype._addScrollButtons = function () {
  // aux function
  var _getBtnHtml = function (btnClass) {
    return '<li class="toolbar-button ' + btnClass + '">' +
      '<a href="#" ' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      'data-editable-canvas-cssrules="line-height,font-size:($v / 2.5)" tabindex="-1"' +
      '>' +
      '<i class="fa"></i>' +
      '</a>' +
      '</li>';
  };

  // buttons html
  this.$scrollToBeginBtn = $(_getBtnHtml('scroll-to-begin-btn'));
  this.$scrollToEndBtn = $(_getBtnHtml('scroll-to-end-btn'));

  // prevent default behavior on link click
  var $linkToBegin = this.$scrollToBeginBtn.find('a');
  var $linkToEnd = this.$scrollToEndBtn.find('a');
  $linkToBegin.on('click', function (e) {
    e.preventDefault();
    // e.stopPropagation();
  });
  $linkToEnd.on('click', function (e) {
    e.preventDefault();
    // e.stopPropagation();
  });

  // set click handlers
  DrawerJs.util.bindClick($linkToBegin, 'scroll-to-begin-btn', this.onScrollToBegin.bind(this));
  DrawerJs.util.bindClick($linkToEnd, 'scroll-to-end-btn', this.onScrollToEnd.bind(this));

  // scroll buttons are added direct;y to toolbar,
  // unlike common buttons, which are added to $toolbarContentHolder
  this.$toolbar.append(this.$scrollToBeginBtn);
  this.$toolbar.append(this.$scrollToEndBtn);
};


/**
 * React on touch start
 * @param {Event} evt
 * @private
 */
DrawerToolbar.prototype.onTouchStart = function (evt) {
  if (!this.scrollModeActive)
    return;

  this.mouseDown = true;

  // handling touch events
  var e = (evt.type == 'touchstart') ? evt.originalEvent.touches[0] : evt;
  // get coord we are interested in
  var curCoord = this.isHorizontal() ? e.pageX : e.pageY;

  // touch start coord plus existing offset
  this.touchStartCoord = curCoord + this.currentScrollOffset;

  console.log('TOUCH START', this.touchStartCoord, evt);

  $('body').on('mouseup.toolbar touchend.toolbar', this.onTouchEnd.bind(this));

  this.$toolbar.on('mousemove.toolbar touchmove.toolbar', this.onTouchMove.bind(this));
};

/**
 * React on touch end
 * @param {Event} evt
 * @private
 */
DrawerToolbar.prototype.onTouchEnd = function (evt) {
  console.log('TOUCH END', evt);
  this.mouseDown = false;
  $('body').off('mousemove.toolbar touchmove.toolbar');
  $('body').off('mouseup.toolbar touchend.toolbar');
};

/**
 * React on touch move
 * @param {Event} evt
 * @private
 */
DrawerToolbar.prototype.onTouchMove = function (evt) {
  if (this.mouseDown) {
    var eventTarget = evt.target,
      $eventTarget = $(eventTarget),
      isInput = $eventTarget.is('input'),
      isColorIndicator = $eventTarget.is('.color-indicator'),
      processEvent = !isInput && !isColorIndicator;

    if (processEvent) {
      // prevent default action - copying of toolbar content
      evt.preventDefault();

      // handling touch events
      var e = (evt.type === 'touchmove') ? evt.originalEvent.touches[0] : evt;

      // get coord we are interested in
      var curCoord = this.isHorizontal() ? e.pageX : e.pageY;

      var delta = this.touchStartCoord - curCoord;
      console.log('TOUCH MOVE', curCoord, delta);

      this.scrollTo(delta);
    }
  }
};

/**
 * React on canvas resize start
 * @private
 */
DrawerToolbar.prototype._onCanvasResizeStart = function () {
  this.checkScroll();
};

/**
 * React on canvas resizing
 * @private
 */
DrawerToolbar.prototype._onCanvasResizing = function () {
  this.checkScroll();
};

/**
 * React on canvas resize finish
 * @private
 */
DrawerToolbar.prototype._onCanvasResizeFinish = function () {
  this.checkScroll();
};

/**
 *  Compares sizes of toolbar and its placeholder,
 *  if toolbar is bigger - shows scroll
 */
DrawerToolbar.prototype.checkScroll = function () {
  var toolbarContentHolder = this.$toolbarContentHolder.get(0),
    contentSize = this.isHorizontal() ? toolbarContentHolder.scrollWidth : toolbarContentHolder.scrollHeight;

  var toolbarSize = this.isHorizontal() ? this.$toolbar.width()
    : this.$toolbar.height();

  if (contentSize > toolbarSize) {
    this.scrollModeActive = true;
    this.$toolbar.addClass('show-scroll');
  } else {
    this.scrollModeActive = false;
    this.$toolbar.removeClass('show-scroll');
    this.scrollTo(0);
  }
};


DrawerToolbar.prototype._getCurrOffsetProps = function () {
  var result = {},
    $element,
    isHorizontal = this.isHorizontal(),
    currentScrollOffset = this.currentScrollOffset,
    $contentWrapper = this.$toolbar.find('.toolbar-content-wrapper'),
    contentWrapperSizes = $contentWrapper.get(0).getBoundingClientRect(),
    $elementsToCheck = $contentWrapper.children(),
    minOffset,
    relativeOffset;

  $elementsToCheck.each(function (i, currElement) {
    var $currElement = $(currElement),
      currElSizes = currElement.getBoundingClientRect(),
      currElOffsetAbsolute = isHorizontal ? currElSizes.left - contentWrapperSizes.left : currElSizes.top - contentWrapperSizes.top,
      currElOffset = Math.abs(currElOffsetAbsolute - currentScrollOffset),
      currElIsVisible = $currElement.is(':visible'),
      currElIsCloser = minOffset > currElOffset,
      currElIsMatched = currElIsVisible && (minOffset === undefined || currElIsCloser);

    if (currElIsMatched) {
      minOffset = currElOffset;

      result.relativeOffset = currElOffsetAbsolute - currentScrollOffset;
      result.$element = $currElement;
      result.sizes = currElSizes;
    }
  });
  return result;
};

DrawerToolbar.prototype._getOffsetStep_Custom = function (toBegin) {
  var result = 0,
    isHorizontal = this.isHorizontal(),
    toolbarSize = isHorizontal ? this.$toolbar.width() : this.$toolbar.height(),
    buttonSize = isHorizontal ? this.options.buttonWidth : this.options.buttonHeight,
    maxOffsetVal = toolbarSize - buttonSize * 2;

  var offsetProps = this._getCurrOffsetProps(),
    $currentStartOffsetEl = offsetProps.$element,
    needMoreOffset = true,
    $el,
    $nextEl,
    prevSizes,
    currSizes,
    delta;

  result = offsetProps.relativeOffset;



  var startElSizes = $currentStartOffsetEl.get(0).getBoundingClientRect();
  $el = $currentStartOffsetEl;

  while (needMoreOffset) {
    $nextEl = toBegin ? $el.prev() : $el.next();
    if ($nextEl.length && (Math.abs(result) < maxOffsetVal)) {
      var isVisible = $nextEl.is(':visible');
      if (isVisible) {
        currSizes = $nextEl.get(0).getBoundingClientRect();
        delta = isHorizontal ? currSizes.left - startElSizes.left : currSizes.top - startElSizes.top;

        if (delta > 0) {
          delta += 3;
        } else {
          delta -= 3;
        }
        result += delta;
        needMoreOffset = false;
      }
    } else {
      needMoreOffset = false;
    }
    $el = $nextEl;
  }
  // result += buttonSize;
  return result;
};

DrawerToolbar.prototype._getOffsetStep_Buttons = function () {
  var offset,
    toolbarLength = this.isHorizontal() ? this.$toolbar.width() : this.$toolbar.height(),
    btnLength = this.isHorizontal() ? this.options.buttonWidth : this.options.buttonHeight,
    buttonsPerToolbar = Math.ceil(toolbarLength / btnLength),
    buttonsToScroll = Math.max(buttonsPerToolbar - 1, 1);
  offset = buttonsToScroll * btnLength;
  return offset;
};

/**
 * Do scroll to begin
 */
DrawerToolbar.prototype.onScrollToBegin = function () {
  var offset,
    offsetDelta;
  if (this.customScrollMode) {
    offset = this._getOffsetStep_Custom(true);
  } else {
    offsetDelta = this._getOffsetStep_Buttons();
    offset = -offsetDelta;
  }
  this.scrollToolbarBy(offset);
};

/**
 * Do scroll to end
 */
DrawerToolbar.prototype.onScrollToEnd = function () {
  var offset,
    offsetDelta;
  if (this.customScrollMode) {
    offset = this._getOffsetStep_Custom();
  } else {
    offsetDelta = this._getOffsetStep_Buttons();
    offset = offsetDelta;
  }
  this.scrollToolbarBy(offset);
};


/**
 * Scrolls toolbar  by offset in directions - to the end(offset>0)/ to the beginning (offset<0).
 * If offset is to big, it is set to max possible.
 * @param offset
 */
DrawerToolbar.prototype.scrollToolbarBy = function (offset) {
  var newOffset = this.currentScrollOffset + parseInt(offset),
    toolbarEl = this.$toolbar.get(0),
    isHorizontal = this.isHorizontal(),
    toolbarSize = isHorizontal ? this.$toolbar.width() : this.$toolbar.height(),
    toolbarContentSize = isHorizontal ? toolbarEl.scrollWidth : toolbarEl.scrollHeight,
    btnSize = isHorizontal ? this.options.buttonWidth : this.options.buttonHeight,
    maxOffset = toolbarContentSize - btnSize;

  newOffset = Math.min(newOffset, maxOffset); // see, if offset not exceeds maximum
  newOffset = Math.max(newOffset, 0); // no negative offset check
  // scroll it
  this.scrollTo(newOffset);
};

/**
 * Toggle toolbar visibility
 * @param {Boolean} [saveCurrentState]
 * @param {Boolean} [useSaved]
 */
DrawerToolbar.prototype.toggleToolbarVisibility = function (saveCurrentState, useSaved) {
  var currentState = !this.$toolbar.hasClass('hidden'),
    showToolbar = useSaved ? this.visibilityState : currentState;

  if (showToolbar) {
    this.showToolbar(saveCurrentState);
  } else {
    this.hideToolbar(saveCurrentState);
  }
};

/**
 * Hide toolbar
 * @param {Boolean} [saveCurrentState]
 */
DrawerToolbar.prototype.hideToolbar = function (saveCurrentState) {
  if (saveCurrentState) {
    this.visibilityState = !this.$toolbar.hasClass('hidden');
  }
  this.invisible = true;
  this.$toolbar.addClass('hidden');
};

/**
 * Show toolbar
 * @param {Boolean} [saveCurrentState]
 */
DrawerToolbar.prototype.showToolbar = function (saveCurrentState) {
  if (saveCurrentState) {
    this.visibilityState = !this.$toolbar.hasClass('hidden');
  }
  this.invisible = false;
  this.$toolbar.removeClass('hidden');
};


/**
 * Scroll toolbar to the offset.
 * @param  {Number} newOffset
 */
DrawerToolbar.prototype.scrollTo = function (newOffset) {
  this.currentScrollOffset = parseInt(newOffset);
  if (this.isHorizontal()) {
    this.$toolbarContentHolder.css('left', '-' + this.currentScrollOffset + 'px');
  } else {
    this.$toolbarContentHolder.css('top', '-' + this.currentScrollOffset + 'px');
  }
};


/**
 * Returns one of [horizontal, vertical]
 * @return {String} toolbar orientation
 */
DrawerToolbar.prototype.getToolbarOrientation = function () {
  return this.$toolbar.hasClass('toolbar-vertical') ? 'vertical' : 'horizontal';
};


/**
 * @return {Boolean}
 */
DrawerToolbar.prototype.isHorizontal = function () {
  return (this.getToolbarOrientation() === 'horizontal');
};


/**
 * height of toolbar
 * @return {Number}
 */
DrawerToolbar.prototype.height = function () {
  return this.$toolbar.height();
};


/**
 * Width of toolbar
 * @return {Number}
 */
DrawerToolbar.prototype.width = function () {
  return this.$toolbar.width();
};



var DrawerToolbarManager = function (drawer) {
  this.drawerInstance = drawer;
  if (!drawer) {
    throw new Error("DrawerToolbarManager : drawer must be provided!");
  }
  this.toolbars = {};
  this.toolbarPlaceholders = {};
  this.TooltipManager = new DrawerJs.utilPlugins.TooltipManager(this.drawerInstance);
};


/**
 * Appends toolbar to specified location.
 *
 * @param {DrawerToolbar} toolbar - toolbar to append
 * @param {DrawerToolbar.defaultSetOfOptions} [options] - options of toolbar
 */
DrawerToolbarManager.prototype.addToolbar = function (toolbar, options) {
  options = options || {};
  var customPosition = (options.position == ToolbarPlaceholder.prototype.CUSTOM_POSITION),
    posKey = customPosition ? options.customAnchorSelector : options.position,
    isCustomPositionType = posKey === ToolbarPlaceholder.prototype.POPUP_POSITION || posKey === ToolbarPlaceholder.prototype.OVER_CANVAS_POSITION,
    posTypeKey = isCustomPositionType ? ToolbarPlaceholder.prototype.POSITION_TYPE_CUSTOM : options.positionType || ToolbarPlaceholder.prototype.POSITION_TYPE_OUTSIDE,
    placeHolderByType = this.toolbarPlaceholders[posTypeKey] && this.toolbarPlaceholders[posTypeKey][posKey],
    placeholderByKey = this.toolbarPlaceholders[posKey],
    placeholder = placeHolderByType || placeholderByKey;
  if (placeholder) {
    placeholder.addToolbar(toolbar);
  } else {
    if (!customPosition) {
      var messageText = "DrawerToolbarManager.addToolbar() : no placeholder exists with name '",
        variablesText = posTypeKey + " " + posKey + " " + options.customAnchorSelector + "'";
      this.drawerInstance.error(messageText + variablesText);
    }
  }
};


/**
 * Setup/create all toolbars
 */
DrawerToolbarManager.prototype.createAllToolbars = function () {
  var _this = this;

  this.drawerInstance.trigger(this.drawerInstance.BEFORE_CREATE_TOOLBARS, [this]);

  // create wrapper for toolbar
  this._createHelperElements();

  // create placeholders for toolbars

  this._createToolbarsPlaceholders();

  var toolbarsOptions = this.drawerInstance.options.toolbars,
    isFullscreen = this.drawerInstance.$canvasEditContainer.hasClass('fullscreen');

  var toolOptions_conf,
    drawingTools_conf,
    settingsToolbar_conf,
    overCanvasToolbar_conf = toolbarsOptions.overCanvas;

  if (!isFullscreen) {
    toolOptions_conf = toolbarsOptions.toolOptions;
    drawingTools_conf = toolbarsOptions.drawingTools;
    settingsToolbar_conf = toolbarsOptions.settings;
  } else {
    var overridedValues = {
      positionType: ToolbarPlaceholder.prototype.POSITION_TYPE_INSIDE
    };
    toolOptions_conf = $.extend(true,
      {},
      toolbarsOptions.toolOptions,
      toolbarsOptions.toolOptions.fullscreenMode || {},
      overridedValues);
    drawingTools_conf = $.extend(true,
      {},
      toolbarsOptions.drawingTools,
      toolbarsOptions.drawingTools.fullscreenMode || {},
      overridedValues);
    settingsToolbar_conf = $.extend(true,
      {},
      toolbarsOptions.settings,
      toolbarsOptions.settings.fullscreenMode || {},
      overridedValues);
  }

  // order of toolbox creation MATTERS
  // OptionsToolbar must be created first, because in createToolsToolbar() we can activate default tool,
  // which will trigger updates in BrushSize and BrushColor tools.
  this.toolOptionsToolbar = new ToolOptionsToolbar(this.drawerInstance, toolOptions_conf);
  var toolOptionsToolbarOptions = {
    position: toolOptions_conf.position,
    positionType: toolOptions_conf.positionType,
    customAnchorSelector: toolOptions_conf.customAnchorSelector
  };
  if (toolOptions_conf.compactType === DrawerToolbar.prototype.POPUP) {
    toolOptionsToolbarOptions.position = ToolbarPlaceholder.prototype.POPUP_POSITION;
    toolOptionsToolbarOptions.positionType = ToolbarPlaceholder.prototype.POSITION_TYPE_CUSTOM;
  }
  this.addToolbar(this.toolOptionsToolbar, toolOptionsToolbarOptions);


  // drawer tools
  this.drawingToolsToolbar = new DrawingToolsToolbar(this.drawerInstance, drawingTools_conf);

  var drawingToolsToolbarOptions = {
    position: drawingTools_conf.position,
    positionType: drawingTools_conf.positionType,
    customAnchorSelector: drawingTools_conf.customAnchorSelector
  };
  this.addToolbar(this.drawingToolsToolbar, drawingToolsToolbarOptions);

  // toolbar with close, move, options buttons
  this.settingsToolbar = new SettingsToolbar(this.drawerInstance, settingsToolbar_conf);
  var settingsToolbarOptions = {
    position: settingsToolbar_conf.position,
    positionType: settingsToolbar_conf.positionType,
    customAnchorSelector: settingsToolbar_conf.customAnchorSelector
  };
  this.addToolbar(this.settingsToolbar, settingsToolbarOptions);

  // Over canvas toolbar
  this.overCanvasToolbar = new OverCanvasToolbar(this.drawerInstance, overCanvasToolbar_conf);
  var overCanvasToolbarOptions = {
    position: ToolbarPlaceholder.prototype.OVER_CANVAS_POSITION,
    positionType: ToolbarPlaceholder.prototype.POSITION_TYPE_CUSTOM
  };
  this.addToolbar(this.overCanvasToolbar, overCanvasToolbarOptions);

  // Over canvas toolbar
  this.cropImageToolbar = new CropImageToolbar(this.drawerInstance, overCanvasToolbar_conf);
  var cropImageToolbarOptions = {
    position: ToolbarPlaceholder.prototype.TOP_POSITION,
    positionType: ToolbarPlaceholder.prototype.POSITION_TYPE_INSIDE
  };

  this.addToolbar(this.cropImageToolbar, cropImageToolbarOptions);

  // Minimized toolbar
  this.minimizedToolbar = new MinimizedToolbar(this.drawerInstance);
  var minimizedToolbarOptions = {
    position: ToolbarPlaceholder.prototype.TOP_POSITION,
    positionType: ToolbarPlaceholder.prototype.POSITION_TYPE_INSIDE
  };
  this.addToolbar(this.minimizedToolbar, minimizedToolbarOptions);

  // @todo: move to other place
  this.setToolbarButtonsSize();

  // remove all toolbars on exit
  this.drawerInstance.on(this.drawerInstance.EVENT_EDIT_STOP, function () {
    _this.destroyAllToolbars();
  });

  this.drawerInstance.trigger(this.drawerInstance.AFTER_CREATE_TOOLBARS, [this]);
};


/**
 * Creates placeholders for toolbars.
 * All placeholders are inside this.toolbarPlaceholders.
 * Default keys are : left, right, top, bottom
 * Custom anchor toolbars placeholders are stored as this.toolbarPlaceholders[customAnchorSelector]
 * If no anchor element found by selector - 'top' placeholder is used instead
 *
 * @private
 */
DrawerToolbarManager.prototype._createToolbarsPlaceholders = function () {
  // create placeholders elements
  this.toolbarPlaceholders['outside'] = {};
  this.toolbarPlaceholders['inside'] = {};
  this.toolbarPlaceholders['custom'] = {};

  this.toolbarPlaceholders['outside']['top'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.TOP_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_OUTSIDE);
  this.toolbarPlaceholders['outside']['left'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.LEFT_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_OUTSIDE);
  this.toolbarPlaceholders['outside']['right'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.RIGHT_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_OUTSIDE);
  this.toolbarPlaceholders['outside']['bottom'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.BOTTOM_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_OUTSIDE);

  this.toolbarPlaceholders['inside']['top'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.TOP_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_INSIDE);
  this.toolbarPlaceholders['inside']['left'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.LEFT_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_INSIDE);
  this.toolbarPlaceholders['inside']['right'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.RIGHT_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_INSIDE);
  this.toolbarPlaceholders['inside']['bottom'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.BOTTOM_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_INSIDE);

  this.toolbarPlaceholders['custom']['canvas'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.OVER_CANVAS_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_CUSTOM);
  this.toolbarPlaceholders['custom']['popup'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.POPUP_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_CUSTOM);
  this.toolbarPlaceholders['custom']['minimized'] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.TOP_POSITION, ToolbarPlaceholder.prototype.POSITION_TYPE_INSIDE);

  // @todo: remake this!
  var toolbarNames = ['drawingTools', 'toolOptions', 'settings'];

  // look for all toolbars options and see, if there are custom positions, to create placeholder
  for (var i = 0; i < toolbarNames.length; i++) {
    var toolbarConf = this.drawerInstance.options.toolbars[toolbarNames[i]];
    if (toolbarConf && (toolbarConf.position == ToolbarPlaceholder.prototype.CUSTOM_POSITION) && (toolbarConf.customAnchorSelector)) {
      var anchorSelector = toolbarConf.customAnchorSelector;
      this.toolbarPlaceholders[anchorSelector] = new ToolbarPlaceholder(this.drawerInstance, ToolbarPlaceholder.prototype.CUSTOM_POSITION, null, anchorSelector);
    }
  }
};

/**
 * Remove helper elements such as toolbars wrapper, etc.
 * @private
 */
DrawerToolbarManager.prototype._removeHelperElements = function () {
  if (this.drawerInstance.$toolbarsWrapper && this.drawerInstance.$toolbarsWrapper.length) {
    this.drawerInstance.$toolbarsWrapper.remove();
  }
  var $toolbarsWrapper = this.drawerInstance.$canvasEditContainer.find('.toolbars-wrapper');
  if ($toolbarsWrapper && $toolbarsWrapper.length) {
    $toolbarsWrapper.remove();
  }
  this.drawerInstance.$toolbarsWrapper = undefined;
};

/**
 * Create helper elements such as toolbars wrapper, etc.
 * @private
 */
DrawerToolbarManager.prototype._createHelperElements = function () {
  this._removeHelperElements();
  var toolbarsWrapperHtml = '<div class="toolbars-wrapper"></div>';

  var $toolbarsWrapper = $(toolbarsWrapperHtml),
    container = this.drawerInstance.$canvasEditContainer;

  container.append($toolbarsWrapper);

  this.drawerInstance.$toolbarsWrapper = $toolbarsWrapper;
};

/**
 * Removes all toolbars.
 */
DrawerToolbarManager.prototype.destroyAllToolbars = function () {
  this.settingsToolbar.remove();
  this.drawingToolsToolbar.remove();
  this.toolOptionsToolbar.remove();
};


/**
 * Removes and then re-creates all toolbars.
 */
DrawerToolbarManager.prototype.resetAllToolbars = function () {
  this.destroyAllToolbars();
  this.createAllToolbars();
};


/**
 * This method allows dynamical size adjustment of elements.
 * Elements which needs to be resized should have two attributes:
 *
 * data-editable-canvas-sizeable="someNamespace",
 * where someNamespace is unique id for the group of elements tht will be
 * resized together.
 *
 * data-editable-canvas-cssrules=width,height,font-size:($v / 2.5)
 * which provides a list of css rules on which a new size will be applied.
 * If resulting size needs to be modififed in some way, the one could
 * specify a function like in font-size.
 *
 * @param {String} namespace
 * @param {String} newSize
 * @private
 */
DrawerToolbarManager.prototype._adjustElementsSize = function (namespace, newSize) {
  var elementsToResize =
    $('[data-editable-canvas-sizeable=' + namespace + ']');

  for (var i = 0; i < elementsToResize.length; i++) {
    var elem = elementsToResize[i];

    var attributesToChange = $(elem).attr('data-editable-canvas-cssrules');
    // if no attributes to change - skip
    if (!attributesToChange)
      continue;

    var attributesToChangeArr = attributesToChange.split(',');

    for (var a = 0; a < attributesToChangeArr.length; a++) {
      var attrName = attributesToChangeArr[a];
      var attrVal = newSize;

      if (attrName[0] == '-') {
        attrName = attrName.substr(1);
        attrVal = '-' + newSize;
      }

      var matches = attrName.match(/:\((.+)\)/);
      if (matches) {
        attrName = attrName.replace(matches[0], '');
        var expression = matches[1];
        expression = expression.replace('$v', attrVal);
        var result = new Function("return " + expression)();
        attrVal = result;
      }

      $(elem).css(attrName, attrVal + 'px');
    }
  }
};



/**
 * Sets the size of buttons on all toolbars.
 *
 * @param [size] width and height in px
 */
DrawerToolbarManager.prototype.setToolbarButtonsSize = function (size) {
  if (size) {
    if (this.touchDevice) {
      this.drawerInstance.options.toolbarSizeTouch = size;
    } else {
      this.drawerInstance.options.toolbarSize = size;
    }
  } else {
    if (this.touchDevice) {
      size = this.drawerInstance.options.toolbarSizeTouch;
    } else {
      size = this.drawerInstance.options.toolbarSize;
    }
  }

  this._adjustElementsSize('toolbar-button', size);
};

var ToolbarPlaceholder = function (drawerInstance, position, positionType, customAnchorSelector) {
  this.drawerInstance = drawerInstance;
  if (!drawerInstance) {
    throw new Error("ToolbarPlaceholder: drawerInstance is not provided!");
  }

  this._setupOptions(position, positionType, customAnchorSelector);


  // Create element
  this._setupElement();
  this._attachEventHandlers();
  this._attachDrawerEventHandlers();
};

// positions of placeholder
ToolbarPlaceholder.prototype.TOP_POSITION = 'top';
ToolbarPlaceholder.prototype.BOTTOM_POSITION = 'bottom';
ToolbarPlaceholder.prototype.LEFT_POSITION = 'left';
ToolbarPlaceholder.prototype.RIGHT_POSITION = 'right';
ToolbarPlaceholder.prototype.OVER_CANVAS_POSITION = 'canvas';
ToolbarPlaceholder.prototype.POPUP_POSITION = 'popup';

ToolbarPlaceholder.prototype.POSITION_TYPE_OUTSIDE = 'outside';
ToolbarPlaceholder.prototype.POSITION_TYPE_INSIDE = 'inside';
ToolbarPlaceholder.prototype.POSITION_TYPE_CUSTOM = 'custom';

ToolbarPlaceholder.prototype.CUSTOM_POSITION = 'custom';

// orientations of placeholder
ToolbarPlaceholder.prototype.HORIZONTAL_ORIENTATION = 'horizontal';
ToolbarPlaceholder.prototype.VERTICAL_ORIENTATION = 'vertical';

// States of placeholder
ToolbarPlaceholder.prototype.STATE_HIDDEN = 'hidden';
ToolbarPlaceholder.prototype.STATE_OVERLAY = 'overlay';
ToolbarPlaceholder.prototype.STATE_DISABLED = 'disabled';


// placeholder html
ToolbarPlaceholder.prototype._html_default = '' +
  '<ul class="noselect toolbar-placeholder" contenteditable=false>' +
  '<li class="toolbar-placeholder-overlay"></li>' +
  '</ul>';

ToolbarPlaceholder.prototype._html_popup = '' +
  '<div class="noselect toolbar-placeholder hidden" contenteditable=false>' +
  '<div class="close-btn">' +
  '<span class="fa fa-close"></span>' +
  '</div>' +
  '<div class="overlay"></div>' +
  '</div>';

/**@
 * Save and process options
 * @param {String} position one of predefined positions - look at ToolbarPlaceholder.prototype.*_POSITION
 * @param {String} [positionType] one of predefined types - look at ToolbarPlaceholder.prototype.POSITION_TYPE_*
 * @param {String} [customAnchorSelector]
 * @private
 */
ToolbarPlaceholder.prototype._setupOptions = function (position, positionType, customAnchorSelector) {
  if (position === this.POPUP_POSITION) {
    var $popupWrapper = this.drawerInstance.$popupWrapper,
      $popupContent = $popupWrapper && $popupWrapper.find('.popup-content');
    if ($popupContent && $popupContent.length) {
      position = this.CUSTOM_POSITION;
      this.$customAnchor = $popupContent;
    }
  }

  this.position = position;
  this.positionType = positionType;
  this.customAnchorSelector = customAnchorSelector;
};


/**
 * Setup event handlers
 * @private
 */
ToolbarPlaceholder.prototype._attachEventHandlers = function () {

};

/**
 * Setup event handlers for Drawer
 * @private
 */
ToolbarPlaceholder.prototype._attachDrawerEventHandlers = function () {
  // remove on edit stop
  var self = this;
  this.drawerInstance.on(this.drawerInstance.EVENT_EDIT_STOP, function () {
    self.$element.remove();
  });

  var checkElements = function ($elements) {
    var excludeElements = $elements && $elements.length ? $elements : $(),
      isParent,
      isChild,
      excludeElementsMatched;

    excludeElements.each(function (i, element) {
      var $currEl = $(element);
      if (!excludeElementsMatched) {
        isParent = self.$element.find($currEl).length;
        isChild = self.$element.closest($currEl).length;
        excludeElementsMatched = isParent || isChild;
      }
    });

    return excludeElementsMatched;
  };

  this.drawerInstance.on(this.drawerInstance.EVENT_TOOLBAR_CHANGE_STATE, function (fEvent, data) {
    var dataForChange = $.extend(true, {}, data || {});
    self.changeState(dataForChange);
  });

  this.drawerInstance.on(this.drawerInstance.EVENT_TOOLBAR_CLEAR_STATE, function (fEvent, data) {
    var excludeElements = data && data.excludeElements,
      isParent = self.$element.find(excludeElements).length,
      isChild = self.$element.closest(excludeElements).length,
      customPosition = self.position === self.POSITION_TYPE_CUSTOM,
      excludeElementsMatched = isParent || isChild;
    if (!excludeElementsMatched && !customPosition) {
      self.turnOffOverlay();
      self.turnOffDisabled();
      self._showPlaceholder();
    }
  });

  this.drawerInstance.on(this.drawerInstance.EVENT_TOOLBAR_STATE_HIDDEN_OFF, function (fEvent, data) {
    var dataForChange = $.extend(true, {}, data || {});
    dataForChange.state = 'hidden';
    dataForChange.turnOn = false;
    self.changeState(dataForChange);
  });

  this.drawerInstance.on(this.drawerInstance.EVENT_TOOLBAR_STATE_HIDDEN_ON, function (fEvent, data) {
    var dataForChange = $.extend(true, {}, data || {});
    dataForChange.state = 'hidden';
    dataForChange.turnOn = true;
    self.changeState(dataForChange);
  });

  this.drawerInstance.on(this.drawerInstance.EVENT_TOOLBAR_STATE_OVERLAY_ON, function (fEvent, data) {
    var dataForChange = $.extend(true, {}, data || {});
    dataForChange.state = 'overlay';
    dataForChange.turnOn = true;
    self.changeState(dataForChange);
  });

  this.drawerInstance.on(this.drawerInstance.EVENT_TOOLBAR_STATE_OVERLAY_OFF, function (fEvent, data) {
    var dataForChange = $.extend(true, {}, data || {});
    dataForChange.state = 'overlay';
    dataForChange.turnOn = false;
    self.changeState(dataForChange);
  });

  this.drawerInstance.on(this.drawerInstance.EVENT_TOOLBAR_STATE_DISABLED_ON, function (fEvent, data) {
    var dataForChange = $.extend(true, {}, data || {});
    dataForChange.state = 'disabled';
    dataForChange.turnOn = true;
    self.changeState(dataForChange);
  });

  this.drawerInstance.on(this.drawerInstance.EVENT_TOOLBAR_STATE_DISABLED_OFF, function (fEvent, data) {
    var dataForChange = $.extend(true, {}, data || {});
    dataForChange.state = 'disabled';
    dataForChange.turnOn = false;
    self.changeState(dataForChange);
  });
};

/**
 * Sets placeholder positioning.
 *
 * @param {String} [position] - one of predefined positions - look at ToolbarPlaceholder.prototype.*_POSITION
 * @param {String} [positionType] one of predefined types - look at ToolbarPlaceholder.prototype.POSITION_TYPE_*
 * @param {String} [customAnchorSelector]
 * @private
 */
ToolbarPlaceholder.prototype._setPosition = function (position, positionType, customAnchorSelector) {
  position = position || this.position;
  positionType = positionType || this.positionType;
  customAnchorSelector = customAnchorSelector || this.customAnchorSelector;

  this.position = position;
  if (position == this.CUSTOM_POSITION) {
    this.customAnchorSelector = customAnchorSelector;


    var anchorFromElement = $(window.document).find(this.$customAnchor),
      anchorFromSelector = $(window.document).find(customAnchorSelector),
      $anchor = anchorFromElement.length ? anchorFromElement : (anchorFromSelector.length ? anchorFromSelector : false);
    if ($anchor) {
      // @todo - why this limitation?
      // at the moment - only horizontal position for custom anchors
      this.orientation = this.HORIZONTAL_ORIENTATION;

      // wrap anchor in jQuery
      this.$customAnchor = $anchor;
      this.$customAnchor.append(this.$element);
    } else {
      this.drawerInstance.error("ToolbarPlaceholder : no anchor element found for custom toolbar by selector '" + customAnchorSelector + "'");

      // custom placeholder will be existing top placeholder
      this.position = this.TOP_POSITION;
    }
  } else {

    switch (position) {
      case this.TOP_POSITION:
        this.$element.addClass('toolbar-placeholder-top');
        this.orientation = this.HORIZONTAL_ORIENTATION;
        break;
      case this.BOTTOM_POSITION:
        this.$element.addClass('toolbar-placeholder-bottom');
        this.orientation = this.HORIZONTAL_ORIENTATION;
        break;
      case this.LEFT_POSITION:
        this.$element.addClass('toolbar-placeholder-left');
        this.orientation = this.VERTICAL_ORIENTATION;
        break;
      case this.RIGHT_POSITION:
        this.$element.addClass('toolbar-placeholder-right');
        this.orientation = this.VERTICAL_ORIENTATION;
        break;
      case this.OVER_CANVAS_POSITION:
        this.$element.addClass('toolbar-placeholder-overcanvas');
        this.orientation = this.HORIZONTAL_ORIENTATION;
        break;
      default:
        this.position = this.TOP_POSITION;
        this.$element.addClass('toolbar-placeholder-top');
        this.orientation = this.HORIZONTAL_ORIENTATION;
    }

    if (positionType && positionType === this.POSITION_TYPE_INSIDE) {
      this.$element.addClass('toolbar-placeholder-inside');
    }
    this.$element.attr('data-position', position);

    // add placeholder to container
    var container = this.drawerInstance.$toolbarsWrapper;
    container.append(this.$element);
  }
};

/**@
 * Init placeholder element - create and setup
 * @private
 */
ToolbarPlaceholder.prototype._setupElement = function () {
  var elementHtml = this._getHtmlOfElement();
  this.$element = $(elementHtml);
  this.$overlay = this.$element.find('.toolbar-placeholder-overlay');
  this._setPosition();
};

/**
 * Get html of template
 * @private
 */
ToolbarPlaceholder.prototype._getHtmlOfElement = function () {
  var result;
  result = this._html_default;
  if (this.position === this.POPUP_POSITION) {
    result = this._html_popup;
  }
  return result;
};


/**
 * Toggle state of placeholder
 * @param {Object} data - Configuration object
 * @param {String} data.state - Type of state
 * @param {Boolean} [data.turnOn] - Turn on/off selected state
 * @param {jQuery} [data.excludeElements] - Do not change state of placeholder if it is parent/child at least of one of the next elements
 */
ToolbarPlaceholder.prototype.changeState = function (data) {
  var self = this,
    checkElements;

  checkElements = function ($elements) {
    var excludeElements = $elements && $elements.length ? $elements : $(),
      isParent,
      isChild,
      excludeElementsMatched;

    excludeElements.each(function (i, element) {
      var $currEl = $(element);
      if (!excludeElementsMatched) {
        isParent = self.$element.find($currEl).length;
        isChild = self.$element.closest($currEl).length;
        excludeElementsMatched = isParent || isChild;
      }
    });

    return excludeElementsMatched;
  };

  var excludeElements = data && data.excludeElements.length ? data.excludeElements : $(),
    excludeElementsMatched = checkElements(excludeElements),
    customPosition = self.position === self.POSITION_TYPE_CUSTOM,
    ignore = excludeElementsMatched || customPosition;
  if (!ignore) {
    switch (data.state) {
      case this.STATE_OVERLAY:
        this.toggleOverlay(data.turnOn);
        break;
      case this.STATE_HIDDEN:
        if (data.turnOn) {
          this._hidePlaceholder();
        } else {
          this._showPlaceholder();
        }
        break;
      case this.STATE_DISABLED:
        if (data.turnOn) {
          this.turnOnDisabled();
        } else {
          this.turnOffDisabled();
        }
        break;
    }
  }
};


/**
 * Hide placeholder element
 * @private
 */
ToolbarPlaceholder.prototype._hidePlaceholder = function () {
  this.$element.addClass('hidden');
};

/**
 * Show placeholder element
 * @private
 */
ToolbarPlaceholder.prototype._showPlaceholder = function () {
  this.$element.removeClass('hidden');
};

/**
 * Toggle state of overlay
 * @param {Boolean} [turnOn] - force to turn on overlay state
 */
ToolbarPlaceholder.prototype.toggleOverlay = function (turnOn) {
  if (turnOn) {
    this.turnOnOverlay();
  } else {
    this.turnOffOverlay();
  }
};

/**
 * Turn on "disabled" state
 */
ToolbarPlaceholder.prototype.turnOnDisabled = function () {
  this.$element.addClass('placeholder-disabled');
};

/**
 * Turn off "disabled" state
 */
ToolbarPlaceholder.prototype.turnOffDisabled = function () {
  this.$element.removeClass('placeholder-disabled');
};

/**
 * Turn on "overlay" state
 */
ToolbarPlaceholder.prototype.turnOnOverlay = function () {
  this.$element.addClass('placeholder-overlayed');
};

/**
 * Turn off "overlay" state
 */
ToolbarPlaceholder.prototype.turnOffOverlay = function () {
  this.$element.removeClass('placeholder-overlayed');
};

/**
 * Adds toolbar to placeholder.
 * Shifts placeholder css position to retain its positioning
 *
 * @param {DrawerToolbar} toolbar to add
 */
ToolbarPlaceholder.prototype.addToolbar = function (toolbar) {
  var $toolbar = toolbar.$toolbar;
  this.$element.append($toolbar);

  // set toolbar position equal to placeholder position
  toolbar.position = this.position;

  // set toolbar orientation
  $toolbar.removeClass('toolbar-horizontal').removeClass('toolbar-vertical');
  if (this.orientation == this.HORIZONTAL_ORIENTATION) {
    // $toolbar.setOrientation(this.HORIZONTAL_ORIENTATION);
    $toolbar.addClass('toolbar-horizontal');
  } else {
    // $toolbar.setOrientation(this.VERTICAL_ORIENTATION);
    $toolbar.addClass('toolbar-vertical');
  }

};
var CropImageToolbar = function (drawerInstance, options) {
  options = options || {};
  options.toolbarClass = 'tool-cropimage hidden';
  // call super c-tor
  DrawerToolbar.call(this, drawerInstance, options);

  // create default buttons for toolbar
  this._createDefaultButtons();

  // Trigger event
  drawerInstance.trigger(drawerInstance.EVENT_IMAGECROP_TOOLBAR_CREATED, [this]);
};

CropImageToolbar.prototype = Object.create(DrawerToolbar.prototype);
CropImageToolbar.prototype.constructor = DrawerToolbar;


/**
 * Creates defaults buttons.
 */
CropImageToolbar.prototype._createDefaultButtons = function () {
};
var DrawingToolsToolbar = function (drawerInstance, options) {
  options.toolbarClass = 'drawing-tools-toolbar';
  this.eventNameSpace = '.toolbar-drawingTools';

  // call super constructor
  DrawerToolbar.call(this, drawerInstance, options);
  this._setDrawerHandlers();
  drawerInstance.trigger(drawerInstance.EVENT_TOOLS_TOOLBAR_CREATED, [this]);
};

DrawingToolsToolbar.prototype = Object.create(DrawerToolbar.prototype);
DrawingToolsToolbar.prototype.constructor = DrawerToolbar;

/**
 * Attach drawer events handlers
 * @private
 */
DrawingToolsToolbar.prototype._setDrawerHandlers = function () {
  var drawerInstance = this.drawerInstance,
    ns = this.eventNameSpace;
  // @todo - move this to some other place!
  // on activating tool - remember it in lastUsedPluginName
  drawerInstance.off(drawerInstance.EVENT_DO_ACTIVATE_TOOL + ns);
  drawerInstance.on(drawerInstance.EVENT_DO_ACTIVATE_TOOL + ns, function (e, tool) {
    drawerInstance.lastUsedPluginName = tool.name;
  });

  // @todo - move this to some other place!
  // if tool was manually switched off - reset lastUsedPluginName
  drawerInstance.off(drawerInstance.EVENT_DO_DEACTIVATE_TOOL + ns);
  drawerInstance.on(drawerInstance.EVENT_DO_DEACTIVATE_TOOL + ns, function (e, tool) {
    if (drawerInstance.lastUsedPluginName == tool.name) {
      drawerInstance.lastUsedPluginName = null;
    }
  });

  drawerInstance.off(drawerInstance.AFTER_CREATE_TOOLBARS + ns);
  drawerInstance.on(drawerInstance.AFTER_CREATE_TOOLBARS + ns, function () {
    drawerInstance.activateDefaultPlugin();
  });
};
var MinimizedToolbar = function (drawerInstance, options) {
  options = options || {};
  // css class for toolbar
  options.toolbarClass = 'tool-minimized-toolbar';
  // call DrawerToolbar c-tor
  DrawerToolbar.call(this, drawerInstance, options);
  // cry loud of birth
  drawerInstance.trigger(drawerInstance.EVENT_MINIMIZED_TOOLBAR_CREATED, [this]);
};

MinimizedToolbar.prototype = Object.create(DrawerToolbar.prototype);
MinimizedToolbar.prototype.constructor = DrawerToolbar;
var OverCanvasToolbar = function (drawerInstance, options) {
  options = options || {};
  // css class for toolbar
  options.toolbarClass = 'tool-overcanvas-toolbar';
  // call DrawerToolbar c-tor
  DrawerToolbar.call(this, drawerInstance, options);
  // cry loud of birth
  drawerInstance.trigger(drawerInstance.EVENT_FLOATING_TOOLBAR_CREATED, [this]);
};

OverCanvasToolbar.prototype = Object.create(DrawerToolbar.prototype);
OverCanvasToolbar.prototype.constructor = DrawerToolbar;
var SettingsToolbar = function (drawerInstance, options) {
  options = options || {};
  options.toolbarClass = 'tool-settings-toolbar';
  // call super c-tor
  DrawerToolbar.call(this, drawerInstance, options);

  // create default buttons for tghi toolbart
  this._createDefaultButtons();

  // Trigger event
  drawerInstance.trigger(drawerInstance.EVENT_CONFIG_TOOLBAR_CREATED, [this]);
};

SettingsToolbar.prototype = Object.create(DrawerToolbar.prototype);
SettingsToolbar.prototype.constructor = DrawerToolbar;


/**
 * Creates defaults buttons : close.
 * @private
 */
SettingsToolbar.prototype._createDefaultButtons = function () {
};


var ToolOptionsToolbar = function (drawerInstance, options) {
  // css class for toolbar
  options.toolbarClass = 'tool-options-toolbar';
  // call DrawerToolbar c-tor
  DrawerToolbar.call(this, drawerInstance, options);
  // cry loud of birth
  drawerInstance.trigger(drawerInstance.EVENT_OPTIONS_TOOLBAR_CREATED, [this]);
};

ToolOptionsToolbar.prototype = Object.create(DrawerToolbar.prototype);
ToolOptionsToolbar.prototype.constructor = DrawerToolbar;

ToolOptionsToolbar.prototype.customScrollMode = true;
; (function (window, $, util, utilPlugins) {
  'use strict';
  var emptyFunc = function () { },
    eventNameSpace = '.ToolbarComboBox';

  /**
   * @typeDef {Object} returnObj
   * @memberOf ToolbarComboBox
   * @property {ToolbarComboBox} instance - instance of combo box
   * @property {ToolbarComboBox#updateSelectedValues} updateSelectedValues - Update selected values func
   * @property {ToolbarComboBox#hideDropdown} hideDropdown - Hide dropdown func
   * @property {ToolbarComboBox#showDropdown} showDropdown - Show dropdown func
   **/

  /**
   * @param {HTMLElement} element - trigger element
   * @param {ToolbarComboBox.defaultOptions} [options] - configuration object
   * @returns {ToolbarComboBox.returnObj}
   * @memberOf DrawerJs.utilPlugins
   * @constructs ToolbarComboBox
   */
  var ToolbarComboBox = function (element, options) {
    this.$element = $(element);

    this._setupOptions(options);
    this._setupCombobox();
    this._attachEventHandlers();
    this._attachDrawerEventHandlers();

    return {
      instance: this,
      updateSelectedValues: this.updateSelectedValues.bind(this),
      hideDropdown: this.hideDropdown.bind(this),
      showDropdown: this.hideDropdown.bind(this)
    };
  };

  /**
   * @memberOf ToolbarComboBox
   * @typeDef {Object} defaultOptions
   * @property {String} wrapper="toolbar-combobox-wrapper" - wrapper class
   * @property {Boolean} editable=false - wrapper class
   * @property {Boolean} addNewOptions=false - wrapper class
   * @property {Boolean} buttonMode=false - wrapper class
   * @property {Array} items - wrapper class
   *
   **/

  /**
   *
   * @type {ToolbarComboBox.defaultOptions}
   * @private
   */
  ToolbarComboBox.prototype._defaultOptions = {
    wrapper: 'toolbar-combobox-wrapper',
    editable: false,
    addNewOptions: false,
    buttonMode: false,
    items: []
  };

  /**
   * Setup options
   * @param {ToolbarComboBox.defaultOptions | Object} [options] - Configuration object
   * @returns {ToolbarComboBox.defaultOptions}
   * @private
   */
  ToolbarComboBox.prototype._setupOptions = function (options) {
    var optionsFromElement = this._collectOptionsFromElement();
    this.options = $.extend(true, {}, this._defaultOptions || {}, optionsFromElement || {}, options || {});
    this._initialOptions = $.extend(true, {}, options);
    this._initialOptionsFromElement = $.extend(true, {}, optionsFromElement);
    return this.options;
  };

  /**
   * Setup combobox element
   * @private
   */
  ToolbarComboBox.prototype._setupCombobox = function () {
    var comboboxHtml = this._generateTemplate(),
      $comboboxElement = $(comboboxHtml);

    $comboboxElement.insertAfter(this.$element);
    this.$element.addClass('hidden');

    this.$combobox = $comboboxElement;
    this.$options = this.$combobox.find('ul');
    this.$dropdown = this.$combobox.find(".dropdown-box");
    this.$input = this.$combobox.find("input");
    this.$closestToolbarItem = this.$element.closest('.toolbar-item-wrapper');
  };

  /**
   * Generate html of combobox
   * @returns {string}
   * @private
   */
  ToolbarComboBox.prototype._generateTemplate = function () {
    var _self = this,
      inputHtml = '<div class="inputbox"><input type="text"/></div>',
      optionsHtml = '',
      html;


    this.options.items.forEach(function (item) {
      var isObj = typeof item === 'object',
        valueToFill = isObj ? item.value : item,
        textToFill = isObj ? item.text || valueToFill : valueToFill,
        styleToFill = isObj ? item.style || '' : '',
        currItemHtml;
      currItemHtml = _self._generateNewOptionItem(textToFill, valueToFill, false, true, styleToFill);
      optionsHtml += currItemHtml;
    });

    this.wrapperClasses = this.options.wrapper +
      (this.options.buttonMode ? ' button-mode ' : '') +
      (this.options.editable ? ' edit-mode ' : '');

    html = '' +
      '<div class="' + this.wrapperClasses + ' collapsed" tabindex="-1">' +
      '<div class="selected">' +
      '<span></span>' +
      '<a class="ui-button"><i class="fa fa-angle-down"></i></a>' +
      '</div>' +
      '<div class="dropdown-box">' +
      (this.options.editable ? inputHtml : '') +
      '<ul class="option-list">' +
      optionsHtml +
      '</ul>' +
      '</div>' +
      '</div>';
    return html;
  };

  /**
   *
   * @param {String} [txt] - Text value of option
   * @param {String} val - Value of option
   * @param {Boolean} [isSelected] - This option is selected
   * @param {Boolean} returnHtml - Need return html string
   * @param {String} [style] - Inline styles of option
   * @returns {jQuery|String}
   * @private
   */
  ToolbarComboBox.prototype._generateNewOptionItem = function (txt, val, isSelected, returnHtml, style) {
    txt = txt || '';
    style = style || '';
    var optionItemHtml = '' +
      '<li ' +
      'style="' + style + '"' +
      'data-val="' + val + '"' +
      'class="option-item">' +
      '<div class="option-item-text">' + txt + '</div>' +
      '</li>';
    var $optionItem = $(optionItemHtml);
    if (isSelected) {
      this.$combobox.find(".selected").attr("data-val", val).find('span').text(txt);
      $optionItem.addClass('selected');
    }
    return returnHtml ? $optionItem.get(0).outerHTML : $optionItem;
  };

  /**
   * Setup size of dropdown
   * @private
   */
  ToolbarComboBox.prototype._setupDropDownSize = function () {
    var comboboxWidth = this.$combobox.width();
    if (comboboxWidth) {

      this.sizesUpdated = true;
    }
  };

  /**
   * Setup/attach drawer handlers
   * @private
   */
  ToolbarComboBox.prototype._attachDrawerEventHandlers = function () {
    var _self = this,
      $closestToolbarItem = this.$element.closest('.toolbar-item-wrapper');

    if ($closestToolbarItem && $closestToolbarItem.length) {

      util.bindClick($('body'), '_optionTool_toggleDropdown', function (event) {
        var $target = $(event.target),
          $clickToolbarItem = $target.closest('.toolbar-item-wrapper'),
          isCanvas = $target.is('canvas'),
          sameToolControl = $closestToolbarItem.get(0) === $clickToolbarItem.get(0),
          needToHide = !sameToolControl && !isCanvas;
        if (needToHide) {
          $closestToolbarItem.find('.toolbar-dropdown-block').addClass('collapsed');
          _self.hideDropdown();
        }
      });

      util.bindClick($closestToolbarItem.find('.toolbar-item-icon'), '_optionTool_toggleDDropdown', function (event) {
        var needRemoveClass = $closestToolbarItem.find('.toolbar-dropdown-block').hasClass('collapsed'),
          toolbarHaveScrollable;
        if (needRemoveClass) {
          var toolbar = _self.options && _self.options.drawer && _self.options.drawer.toolbars && _self.options.drawer.toolbars.toolOptionsToolbar,
            toolbarOptions = toolbar && toolbar.options,
            notInsidePopup = toolbarOptions && toolbarOptions.position !== 'popup',
            outside = toolbarOptions && toolbarOptions.positionType === 'outside',
            insideScrollable = toolbarOptions && toolbarOptions.compactType === 'scrollable';
          toolbarHaveScrollable = notInsidePopup && outside && insideScrollable;
          if (toolbarHaveScrollable) {
            var clone = _self.$combobox.clone(true);
            // _self.$cloneControl = $('<div class="colorpicker-control"></div>');
            // _self.$cloneDropdown = this.$colorButton.$colorDropdown.clone(true);
            // _self.$cloneControl.append(this.$cloneDropdown);
            $closestToolbarItem.closest('.toolbar-placeholder').append(clone);
            clone.addClass('combobox-cloned');
            _self.$clonedCombobox = clone;

            var drawerSizes = util.getScrollOffset(_self.options.drawer.$canvasEditContainer),
              parentSizes = util.getScrollOffset(_self.$element.parent()),
              canvasRect = _self.options.drawer.$canvasEditContainer.get(0).getBoundingClientRect();

            var left = _self.$element.parent().get(0).getBoundingClientRect().left,
              clonedComboboxLeft = (left - canvasRect.left - (parentSizes.left - drawerSizes.left)) - _self.$clonedCombobox.width() / 2;
            _self.$clonedCombobox.css({
              'left': clonedComboboxLeft
            });
          }
        }

        if (!toolbarHaveScrollable) {
          $closestToolbarItem.find('.toolbar-dropdown-block').toggleClass('collapsed', !needRemoveClass);
        }

      });
    }
  };

  /**
   * Setup/attach event handlers
   * @private
   */
  ToolbarComboBox.prototype._attachEventHandlers = function () {
    var _self = this;
    if (this.options.editable) {
      var $input = this.$input,
        bEdit;

      $input.off('focus' + eventNameSpace).on('focus' + eventNameSpace, function (e) {
        _self.$combobox.addClass('focus');
        bEdit = true;
      });

      $input.off('blur' + eventNameSpace).on('blur' + eventNameSpace, function (e) {
        bEdit = false;
        _self.$combobox.trigger('blur');
      });

      $input.off('keypress' + eventNameSpace).on('keypress' + eventNameSpace, function (e) {
        if (e.keyCode == "13") {
          var val = $input.val();

          $input.val('');


          var $originalSelect = $("<option>").val(val).text(val).attr('data-is-input', 1);
          _self.$element.append($originalSelect);

          if (_self.options.addNewOptions) {
            var $opt = _self._generateNewOptionItem(val, val, true);
            _self.$options.append($opt);
          }

          _self.changeActiveOption(val);
        }
      });
    }

    this.$element.off('valueChanged' + eventNameSpace).on('valueChanged' + eventNameSpace, function (e, valueObj) {
      var valueFromElement = _self.$element.val(),
        valueFromObject = valueObj ? valueObj.value : undefined,
        value = valueObj ? valueFromObject : valueFromElement,
        classes;

      if (valueObj) {
        classes = valueObj.classString;
      }

      _self.updateSelectedValues(value, classes);
    });

    this.$element.off('change' + eventNameSpace).on('change' + eventNameSpace, function (e) {
      if (!_self.addNewOptions) {
        var $element = _self.$element,
          prevValue = $element.attr('prev-value'),
          prevValueIsNotInitial = _self.options.valuesArr.indexOf(prevValue) === -1;
        if (prevValueIsNotInitial) {
          $element.find('option[data-val="' + prevValue + '"]').remove();
        }
      }
    });


    this.$combobox.off('mouseleave' + eventNameSpace).on('mouseleave' + eventNameSpace, function (e) {
      var $closestToolbarItem = _self.$element.closest('.toolbar-item-wrapper');
      $closestToolbarItem.find('.toolbar-dropdown-block').addClass('collapsed');
      _self.hideDropdown();
    });

    this.$combobox.off('click' + eventNameSpace + '_optionSelect').on('click' + eventNameSpace + '_optionSelect', '.option-item', this._onOptionSelect.bind(this));

    this.$combobox.off('click' + eventNameSpace + '_toggleDropdown').on('click' + eventNameSpace + '_toggleDropdown', '.selected', function () {
      _self.toggleDropdown();
    });

    this.$combobox.off('blur' + eventNameSpace).on('blur' + eventNameSpace, function (e) {
      e.preventDefault();
      setTimeout(function () {
        if (bEdit) return;
        _self.hideDropdown();
      }, 100);
      _self.$combobox.removeClass('focus');
    });
  };

  /**
   * Collect array of available options from trigger element
   * @returns {Object}
   * @private
   */
  ToolbarComboBox.prototype._collectOptionsFromElement = function () {
    var result = {};

    var isEditable = this.$element.attr('data-editable') && this.$element.attr('data-editable') === 'true';

    result.items = [];
    result.valuesArr = [];
    this.$element.find('option').each(function (i, item) {
      var $item = $(item),
        value = $item.val(),
        text = $item.text(),
        style = $item.attr('style') || '',
        currItemObj = {
          value: value,
          text: text,
          style: style
        };
      result.valuesArr.push(value);
      result.items.push(currItemObj);
    });

    result.editable = isEditable;

    return result;
  };

  /**
   * React on select of option
   * @param {Event} event
   * @private
   */
  ToolbarComboBox.prototype._onOptionSelect = function (event) {
    var $selectedOption = $(event.currentTarget),
      alreadySelected = $selectedOption.hasClass('selected'),
      selectedValue = !alreadySelected ? $selectedOption.attr('data-val') : '',
      selectedText = $selectedOption.text();
    this.hideDropdown();
    this.changeActiveOption(selectedValue, selectedText);
    event.stopPropagation();
    event.preventDefault();
  };

  /**
   * Change current active option
   * @param {String} [value] - Value of new active option
   * @param {*} [text] - Text of new active option
   */
  ToolbarComboBox.prototype.changeActiveOption = function (value, text) {
    var prevValue = this.$element.val();

    this.$element.attr('prev-value', prevValue);
    this.$element.val(value);

    this.updateSelectedValues(value);

    this.$element.trigger('toolbarOptionChange');
  };

  /**
   * Update selected values
   * @param {*} [value] - Value of selected option
   * @param {String} [classes] - Additional classes
   * @param {String} [optionName] - Name of option
   */
  ToolbarComboBox.prototype.updateSelectedValues = function (value, classes, optionName) {
    value = value || '';
    var $allOptions = this.$combobox.find('.option-item'),
      $neededOption = $allOptions.filter('[data-val="' + value + '"]'),
      neededText = $neededOption && $neededOption.length ? $neededOption.text() : value,
      collapsedClass = this.$combobox.hasClass('collapsed') ? ' collapsed' : '',
      wrapperClasses = this.wrapperClasses + ' ' + (classes || '') + collapsedClass;


    this.$combobox.attr('class', wrapperClasses);
    this.$combobox.find(".selected span").text(neededText);

    $allOptions.removeClass('selected');
    if ($neededOption) {
      $neededOption.addClass('selected');
      this.$input.val('');
    } else {
      this.$input.val(value);
    }
  };


  /**
   * Show combobox dropdown
   */
  ToolbarComboBox.prototype.showDropdown = function () {
    this.dropdownIsVisible = true;
    if (!this.sizesUpdated) {
      this._setupDropDownSize();
    }
    this.$combobox.find('.ui-button .fa').addClass('fa-angle-down').removeClass('fa-angle-up');
    this.$combobox.removeClass('collapsed');
  };

  /**
   * Hide combobox dropdown
   */
  ToolbarComboBox.prototype.hideDropdown = function () {
    this.dropdownIsVisible = false;
    this.$combobox.addClass('collapsed');
    this.$combobox.find('.ui-button .fa').removeClass('fa-angle-down').addClass('fa-angle-up');

    if (this.$clonedCombobox) {
      this.$clonedCombobox.remove();
    }
  };

  /**
   * Toggle visibility of combobox dropdown
   * @param {Boolean} [show] - new state of dropdown
   */
  ToolbarComboBox.prototype.toggleDropdown = function (show) {
    var needToShow = show !== undefined ? show : !this.dropdownIsVisible;
    this.dropdownIsVisible = needToShow;

    if (needToShow) {
      this.showDropdown();
    } else {
      this.hideDropdown();
    }
  };

  /**
   * @Function
   * @param options
   * @returns {ToolbarComboBox[]}
   * @memberOf external:"jQuery.fn"
   */
  $.fn.ToolbarComboBox = function (options) {
    var instances = [];
    $(this).each(function (i, element) {
      var newInstance = new ToolbarComboBox(element, options);
      $(element).data('comboBox', newInstance);
      instances.push(newInstance);
    });
    return instances;
  };

  /**
   * @Function
   * @memberOf external:"jQuery.fn"
   * @returns {ToolbarComboBox|ToolbarComboBox[]}
   */
  $.fn.getComboBox = function () {
    var instances = [];
    $(this).each(function (i, element) {
      var currInstance = $(element).data('comboBox');
      if (currInstance) {
        instances.push(currInstance);
      }
    });
    return instances.length > 1 ? instances : instances[0];
  };

  utilPlugins.ToolbarComboBox = ToolbarComboBox;
})(window, jQuery, DrawerJs.util, DrawerJs.utilPlugins);
; (function (window, $, util, utilPlugins) {
  'use strict';

  /**
   * @typeDef {Object} returnObj
   * @memberOf DrawerJs.utilPlugins.Tooltip
   * @property {DrawerJs.utilPlugins.Tooltip} instance - Instance of tooltip plugin
   * @property {jQuery} $trigger - Trigger element
   * @property {DrawerJs.utilPlugins.Tooltip#destroy} destroy - Destroy instance
   * @property {DrawerJs.utilPlugins.Tooltip#adjustPosition} adjustPosition - Refresh/change position
   * @property {DrawerJs.utilPlugins.Tooltip#hideTooltip} hideTooltip - Hide tooltip func
   * @property {DrawerJs.utilPlugins.Tooltip#showTooltip} showTooltip - Show tooltip func
   **/

  /**
   * Plugin that provide ability to create tooltip for any element. Position/styles of tooltip are configurable.
   * Can be used as jQuery plugin - $.fn.DrawerTooltip
   * @param {HTMLElement} element - trigger element
   * @param {DrawerJs.utilPlugins.Tooltip.defaultOptions} [options] - configuration object
   * @returns {DrawerJs.utilPlugins.Tooltip.returnObj}
   * @memberOf DrawerJs.utilPlugins
   * @constructs Tooltip
   */
  var Tooltip = function (element, options) {
    this.$element = $(element);

    this._setupOptions(options);
    this._processOptions();
    this._setupTooltip();
    this._attachEventHandlers();

    return {
      instance: this,
      $trigger: this.$element,
      adjustPosition: this.adjustPosition.bind(this),
      destroy: this.destroy.bind(this),
      hideTooltip: this.hideTooltip.bind(this),
      showTooltip: this.showTooltip.bind(this)
    };
  };

  /**
   * @memberOf DrawerJs.utilPlugins.Tooltip
   * @typeDef {Object} defaultOptions
   * @property {String} defaultClass="editable-canvas-tooltip" - Tooltip class
   * @property {String} defaultPositionX="center" - Default horizontal position. Can be left/right/center
   * @property {String} defaultPositionY="bottom" - Default vertical position. Can be top/left/center
   * @property {String} additionalClass - Tooltip additional class
   * @property {String} text - Text of tooltip
   * @property {Object} styleObj - Any valid css object
   * @property {String} style - inline style of tooltip. Will be overrided if styleObj is defined
   * @property {String} position - Position of tooltip. Any combination of left/right/center for horizontal and
   * top/bottom/center for vertical. Valid values - "left", "right top", "bottom left", "top center", "top", etc.
   * @property {jQuery} $tooltipWrapper - Wrapper element for tooltip
   * @property {String} tooltipWrapperSelector - Selector of wrapper element for tooltip.
   * Will be overrided if $tooltipWrapper if defined.
   *
   **/

  /**
   *
   * @type {DrawerJs.utilPlugins.Tooltip.defaultOptions}
   * @private
   */
  Tooltip.prototype._defaultOptions = {
    defaultClass: 'editable-canvas-tooltip',
    defaultPositionX: 'center',
    defaultPositionY: 'bottom',
    additionalClass: '',
    text: '',
    position: '',
    style: '',
    styleObj: {},
    $tooltipWrapper: '',
    tooltipWrapperSelector: undefined
  };

  /**
   * Tooltip is enabled
   * @type {boolean}
   * @private
   */
  Tooltip.prototype.enabled = true;

  /**
   * Default tooltip wrapper
   * @type {string}
   * @private
   */
  Tooltip.prototype._defaultWrapper = 'body';

  /**
   * Namespace string for events
   * @type {string}
   * @private
   */
  Tooltip.prototype._eventsNamespace = '.drawerTooltip';

  /**
   * Indicates that tooltip is not displayed yet
   * @type {boolean}
   * @private
   */
  Tooltip.prototype._firstShow = true;


  /**
   * Setup options
   * @param {Tooltip.defaultOptions | Object} [options] - Configuration object
   * @returns {Tooltip.defaultOptions}
   * @private
   */
  Tooltip.prototype._setupOptions = function (options) {
    var optionsFromElement = this._collectOptionsFromElement();
    this.options = $.extend(true, {}, this._defaultOptions || {}, optionsFromElement || {}, options || {});
    this._initialOptions = $.extend(true, {}, options);
    this._initialOptionsFromElement = $.extend(true, {}, optionsFromElement);
    return this.options;
  };


  /**
   * Check if jQuery element exists
   * @param {jQuery} [element]
   * @returns {jQuery|undefined}
   * @private
   */
  Tooltip.prototype._checkElement = function (element) {
    var result,
      $element;
    if (typeof element === 'string') {
      $element = $(element);
    }
    if (element instanceof $) {
      $element = element;
    }
    if ($element && $element.length) {
      result = $element.eq(0);
    }
    return result;
  };

  /**
   * Process collected options
   * @private
   */
  Tooltip.prototype._processOptions = function () {
    var $tooltipWrapper = this._checkElement(this.options.$tooltipWrapper),
      $tooltipWrapperFromSelector = this._checkElement(this.options.tooltipWrapperSelector),
      $defaultWrapper = this._checkElement(this._defaultWrapper);

    this.$tooltipWrapper = $tooltipWrapper || $tooltipWrapperFromSelector || $defaultWrapper;
  };


  /**
   * Setup tooltip element
   * @private
   */
  Tooltip.prototype._setupTooltip = function () {
    var tooltipHtml = this._generateTemplate(),
      $tooltip = $(tooltipHtml);

    if (this.options.styleObj) {
      $tooltip.css(this.options.styleObj);
    }
    this.$element.addClass('tooltip-trigger');
    this.$element.data('DrawerTooltip', this);
    this.$tooltipWrapper.append($tooltip);
    this.$tooltip = $tooltip;
  };

  /**
   * Generate html of tooltip
   * @returns {string}
   * @private
   */
  Tooltip.prototype._generateTemplate = function () {
    var html,
      content = this.options.text || '',
      classString = this.options.defaultClass + ' ' + this.options.additionalClass,
      styleString = this.options.style || '';

    html = '' +
      '<span ' +
      'class="' + classString + '"' +
      'style="' + styleString + '"' +
      '>' +
      content +
      '</span>';

    return html;
  };

  /**
   * Setup/attach event handlers
   * @private
   */
  Tooltip.prototype._attachEventHandlers = function () {
    var self = this,
      $trigger = this.$element;
    this._detachEventHandlers();
    $trigger.on('mouseenter' + this._eventsNamespace, function (e) {
      var $eventTrigger = $(e.currentTarget),
        $body = $('body'),
        isMoving = $body.hasClass('drawer-moving'),
        isResizing = $body.hasClass('drawer-resizing'),
        tooltipIsDisabled = $trigger.hasClass('disabled'),
        ignore = isMoving || isResizing || tooltipIsDisabled;
      if (!ignore) {
        self.showTooltip($eventTrigger);
      }
    });
    $trigger.on('mouseout' + this._eventsNamespace, function () {
      self.hideTooltip();
    });
  };

  /**
   * Detach event handlers
   * @private
   */
  Tooltip.prototype._detachEventHandlers = function () {
    this.$element.off('mouseenter' + this._eventsNamespace);
    this.$element.off('mouseout' + this._eventsNamespace);
  };

  /**
   * Helper function which provides ability to parse string for position values
   * @param {String} position
   * @returns {Object}
   * @private
   */
  Tooltip.prototype._parsePositionString = function (position) {
    position = position || this.options.position || '';
    var result = {},
      haveAxisX = position.indexOf('right') !== -1 || position.indexOf('left') !== -1,
      positionX = haveAxisX && (position.indexOf('right') !== -1 ? 'right' : 'left'),
      haveAxisY = position.indexOf('top') !== -1 || position.indexOf('bottom') !== -1,
      positionY = haveAxisY && (position.indexOf('top') !== -1 ? 'top' : 'bottom');


    positionX = positionX || (haveAxisY ? 'center' : this.options.defaultPositionX);
    positionY = positionY || (haveAxisX ? 'center' : this.options.defaultPositionY);

    result.positionX = positionX;
    result.positionY = positionY;

    return result;
  };

  /**
   *
   * @param onlyClasses
   * @private
   */
  Tooltip.prototype._resetTooltip = function (onlyClasses) {

  };

  /**
   * Adjust tooltip position
   * @param {String|undefined} [positionString] - Position of tooltip. Any combination of left/right/center for horizontal and
   * top/bottom/center for vertical. Valid values - "left", "right top", "bottom left", "top center", "top", etc.
   * @param {jQuery} [$trigger] - trigger element
   */
  Tooltip.prototype.adjustPosition = function (positionString, $trigger) {
    if (!this.enabled) {
      return;
    }
    $trigger = $trigger || this.$element;
    var position = this._parsePositionString(positionString),
      $tooltip = this.$tooltip;

    var top = 0;
    var left = 0;

    var arrowSize = 8,
      scroll = util.getScrollTopFromElement($trigger),
      triggerSizes = $trigger.get(0).getBoundingClientRect(),
      tooltipSizes = $tooltip.get(0).getBoundingClientRect();

    switch (position.positionX) {
      case 'right':
        left = scroll.left + triggerSizes.left + triggerSizes.width + arrowSize;
        break;
      case 'left':
        left = scroll.left + triggerSizes.left - tooltipSizes.width - arrowSize;
        break;
      default:
        left = scroll.left + triggerSizes.left + (triggerSizes.width - tooltipSizes.width) / 2;
        break;
    }

    switch (position.positionY) {
      case 'top':
        top = scroll.top + triggerSizes.top - tooltipSizes.height - arrowSize;
        break;
      case 'bottom':
        top = scroll.top + triggerSizes.top + triggerSizes.height + arrowSize;
        break;
      default:
        top = scroll.top + triggerSizes.top + (triggerSizes.height - tooltipSizes.height) / 2;
        break;
    }

    $tooltip.attr('positionX', position.positionX);
    $tooltip.attr('positionY', position.positionY);
    $tooltip.css({
      top: top + 'px',
      left: left + 'px'
    });
  };

  /**
   * Collect array of available options from trigger element
   * @param {jQuery} [$trigger]
   * @returns {Object}
   * @private
   */
  Tooltip.prototype._collectOptionsFromElement = function ($trigger) {
    $trigger = $trigger || this.$element;
    var result = {};

    result.items = [];
    var triggerText = $trigger.text(),
      text = $trigger.attr('tooltip-text'),
      style = $trigger.attr('tooltip-style') || '',
      position = $trigger.attr('tooltip-position') || '';
    result.text = text || triggerText;
    result.style = style;
    result.position = position;
    return result;
  };

  /**
   * Destroy current instance of tooltip. Remove all elements and detach events.
   */
  Tooltip.prototype.destroy = function () {
    this._detachEventHandlers();
    this.$tooltip.remove();
    this.$element.data('DrawerTooltip', undefined);
    this.$element.removeClass('tooltip-trigger');
    this.enabled = false;
  };

  /**
   * Show tooltip
   * @param {jQuery} [$trigger] - trigger element. For correct positioning
   */
  Tooltip.prototype.showTooltip = function ($trigger) {
    if (this.enabled) {
      this.$element.trigger('showTooltip');
      this._firstShow = false;
      if (!this.$tooltip.is(':visible')) {
        this.$tooltip.addClass('tooltip-transparent');
      }
      this.adjustPosition(undefined, $trigger);
      this.$tooltip.removeClass('tooltip-transparent');
      this.$tooltip.addClass('active');
      this.isVisible = true;
    }
  };

  /**
   * Hide tooltip
   */
  Tooltip.prototype.hideTooltip = function () {
    if (this.enabled && this.isVisible) {
      this.$tooltip.removeClass('active');
      this.isVisible = false;
    }
  };

  /**
   * @Function
   * @param {Object} [options]
   * @returns {Tooltip[]}
   * @memberOf external:"jQuery.fn"
   */
  $.fn.DrawerTooltip = function (options) {
    var instances = [];
    $(this).each(function (i, element) {
      var newInstance = new Tooltip(element, options);
      $(element).data('DrawerTooltip', newInstance);
      instances.push(newInstance);
    });
    return instances;
  };

  /**
   * @Function
   * @memberOf external:"jQuery.fn"
   * @returns {Tooltip|Tooltip[]}
   */
  $.fn.getDrawerTooltip = function () {
    var instances = [];
    $(this).each(function (i, element) {
      var currInstance = $(element).data('DrawerTooltip');
      if (currInstance) {
        instances.push(currInstance);
      }
    });
    return instances.length > 1 ? instances : instances[0];
  };

  utilPlugins.Tooltip = Tooltip;
})(window, jQuery, DrawerJs.util, DrawerJs.utilPlugins);
; (function (window, $, util, utilPlugins) {
  'use strict';

  /**
   * @typeDef {Object} returnObj
   * @memberOf DrawerJs.utilPlugins.TooltipManager
   * @property {TooltipManager} instance - instance of tooltip manager
   * @property {TooltipManager#createTooltip} createTooltip - Create tooltip
   * @property {TooltipManager#removeAllTooltips} removeAllTooltips - Destroy all tooltips and all attached events
   * @property {TooltipManager#hideAllTooltips} hideAllTooltips - Hide all tooltips
   **/

  /**
   * Provides ability for Drawer to create/use tooltips.
   * @param {Drawer} drawer - trigger element
   * @param {TooltipManager.defaultOptions} [options] - configuration object
   * @returns {DrawerJs.utilPlugins.TooltipManager.returnObj}
   * @memberOf DrawerJs.utilPlugins
   * @constructs TooltipManager
   */
  var TooltipManager = function (drawer, options) {
    this.drawerInstance = drawer;

    this._setupOptions(options);
    this._processOptions();
    this._createHelperElements();
    this._attachDrawerEventHandlers();
    this._attachEventHandlers();

    return {
      instance: this,
      createTooltip: this.createTooltip.bind(this)
    };
  };

  /**
   * @memberOf DrawerJs.utilPlugins.TooltipManager
   * @typeDef {Object} defaultOptions
   * @property {Object} [style] - Allows css customizations of buttons tooltips. Could be any valid css object
   *
   **/

  /**
   *
   * @type {DrawerJs.utilPlugins.TooltipManager.defaultOptions}
   * @private
   */
  TooltipManager.prototype._defaultOptions = {
    tooltipCss: {}
  };

  /**
   * Array of tooltip instances
   * @type {Array}
   * @private
   */
  TooltipManager.prototype._tooltipInstances = [];

  /**
   * Setup options
   * @param {DrawerJs.utilPlugins.TooltipManager.defaultOptions | Object} [options] - Configuration object
   * @returns {DrawerJs.utilPlugins.TooltipManager.defaultOptions}
   * @private
   */
  TooltipManager.prototype._setupOptions = function (options) {
    var optionsFromDrawer = {
      styleObj: this.drawerInstance.options.tooltipCss
    };
    this.options = $.extend(true, {}, this._defaultOptions || {}, optionsFromDrawer, options || {});
    this._initialOptions = $.extend(true, {}, options);
    return this.options;
  };

  /**
   * Process options
   * @private
   */
  TooltipManager.prototype._processOptions = function () {

  };


  /**
   * Create new tooltip(s)
   * @param {jQuery} elements
   * @param {DrawerJs.utilPlugins.Tooltip.defaultOptions} [options]
   * @returns {DrawerJs.utilPlugins.Tooltip[]}
   */
  TooltipManager.prototype.createTooltip = function (elements, options) {
    elements = elements || [];
    var self = this,
      newInstances = [],
      newTooltip,
      optionsForTooltip = $.extend(true, {}, this.options, options || {});
    elements.each(function (i, element) {
      newTooltip = new utilPlugins.Tooltip(element, optionsForTooltip);
      self._tooltipInstances.push(newTooltip);
      newInstances.push(newTooltip);
    });
    return newInstances;
  };

  /**
   * Destroy all tooltips and all attached events
   */
  TooltipManager.prototype.removeAllTooltips = function () {
    this._tooltipInstances.forEach(function (tooltip) {
      tooltip.destroy();
    });
    this._tooltipInstances = [];
  };

  /**
   * Hide all tooltips
   */
  TooltipManager.prototype.hideAllTooltips = function () {
    this._tooltipInstances.forEach(function (tooltip) {
      tooltip.hideTooltip();
    });
  };

  /**
   * Remove helper elements such as tooltip container
   * @private
   */
  TooltipManager.prototype._removeHelperElements = function () {
    if (this.drawerInstance.$tooltipContainer && this.drawerInstance.$tooltipContainer.length) {
      this.drawerInstance.$tooltipContainer.remove();
    }
  };

  /**
   * Create helper elements such as tooltip container
   * @private
   */
  TooltipManager.prototype._createHelperElements = function () {
    this._removeHelperElements();
    var currDrawerInstanceId = this.drawerInstance.id,
      tooltipContainerHtml = '' +
        '<div ' +
        'class="tooltip-container" ' +
        'data-drawer-instance="' + currDrawerInstanceId + '">' +
        '</div>';

    var $tooltipContainer = $(tooltipContainerHtml),
      $body = $('#' + this.drawerContainerID);

    $body.append($tooltipContainer);

    this.options.$tooltipWrapper = $tooltipContainer;
    this.drawerInstance.$tooltipContainer = $tooltipContainer;
  };

  /**
   * Setup/attach drawer handlers
   * @private
   */
  TooltipManager.prototype._attachDrawerEventHandlers = function () {
    var self = this;

    this.drawerInstance.on(this.drawerInstance.EVENT_CREATE_TOOLTIP, function (fEvent, elements, options) {
      return self.createTooltip(elements, options);
    });

    this.drawerInstance.on(this.drawerInstance.EVENT_HIDE_TOOLTIPS, function () {
      self.hideAllTooltips();
    });

    this.drawerInstance.on(this.drawerInstance.EVENT_DESTROY_TOOLTIPS, function () {
      self.removeAllTooltips();
    });

    this.drawerInstance.on('destroy', function () {
      self.removeAllTooltips();
    });
  };


  /**
   * Setup/attach event handlers
   * @private
   */
  TooltipManager.prototype._attachEventHandlers = function () {
    $('body').off('showTooltip').on('showTooltip', '.tooltip-trigger', function (e) {
      var $trigger = $(e.currentTarget),
        tooltipInstance = $trigger.data('DrawerTooltip');
      if (tooltipInstance._firstShow) {
        var $toolbarPlaceholder = $trigger.closest('.toolbar-placeholder'),
          toolbarPosition = $toolbarPlaceholder.attr('data-position'),
          tooltipPosition;
        switch (toolbarPosition) {
          case ToolbarPlaceholder.prototype.TOP_POSITION:
            tooltipPosition = ToolbarPlaceholder.prototype.BOTTOM_POSITION;
            break;
          case ToolbarPlaceholder.prototype.BOTTOM_POSITION:
            tooltipPosition = ToolbarPlaceholder.prototype.TOP_POSITION;
            break;
          case ToolbarPlaceholder.prototype.LEFT_POSITION:
            tooltipPosition = ToolbarPlaceholder.prototype.RIGHT_POSITION;
            break;
          case ToolbarPlaceholder.prototype.RIGHT_POSITION:
            tooltipPosition = ToolbarPlaceholder.prototype.LEFT_POSITION;
            break;
        }
        if (tooltipPosition) {
          tooltipInstance.options.position = tooltipPosition;
        }
      }
    });
  };

  utilPlugins.TooltipManager = TooltipManager;
})(window, jQuery, DrawerJs.util, DrawerJs.utilPlugins);
(function ($, pluginsNamespace) {
  'use strict';

  /**
   * Base class for all drawing tools.
   *
   * @param drawerInstance
   * @param options
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var BaseTool = function (drawerInstance, options) {
    if (!drawerInstance) {
      throw new Error("BaseTool CTOR : drawerInstance is not set!");
    }
    this._setupOptions(options);

    this.drawerInstance = drawerInstance;
    this.drawer = drawerInstance;

    // set handlers
    this._bindedOnToolbarCreated = this._onToolbarCreated.bind(this);
    this.drawerInstance.on(this.drawerInstance.EVENT_TOOLS_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
  };

  BaseTool.prototype.name = '';          // tool name
  BaseTool.prototype.type = null;        // tool type
  BaseTool.prototype.active = false;     // is active at the moment
  BaseTool.prototype.btnClass = 'btn';   // tool button css class
  BaseTool.prototype.faClass = '';       // tool icon css class
  BaseTool.prototype.tooltip = '';       //
  BaseTool.prototype.toolbar = null;     /** @type {DrawerToolbar} */
  BaseTool.prototype.$toolButton = null; // cached jQuery wrapper of button
  BaseTool.prototype.drawerInstance = null; // instance of DrawerJs


  /**
   * Handler for EVENT_DO_DEACTIVATE_ALL_TOOLS.
   */
  BaseTool.prototype._onDeactivateAllTools = function () {
    if (this.active) {
      var ns = '.tool-' + this.name;
      this.drawerInstance.trigger(this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL + ns, [this]);
    }
  };

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  BaseTool.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  /**
   * On toolbar created - create tool button.
   * @param {fabric.Event} ev
   * @param {DrawerToolbar} toolbar
   * @private
   */
  BaseTool.prototype._onToolbarCreated = function (ev, toolbar) {
    this.toolbar = toolbar;
    this.createButton(toolbar);

    this._setHandlers();

    if (this.onToolbarCreated) {
      this.onToolbarCreated(ev, toolbar);
    }
  };


  /**
   * On toolbar destroyed - destroy button, if it was our toolbar.
   */
  BaseTool.prototype.onToolbarDestroyed = function (ev, toolbar) {
    if (this.toolbar == toolbar) {
      this.removeTool();
    }
  };

  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listenin on toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  BaseTool.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    // removes button and unbind click on it
    if (this.$toolButton) {
      this.toolbar.removeButton(this.$toolButton);
    }

    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this._unsetHandlers();
      this.drawerInstance.off(this.drawerInstance.EVENT_TOOLS_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };


  BaseTool.prototype._setHandlers = function () {
    var ns = '.tool-' + this.name;

    this.drawerInstance.on(this.drawerInstance.EVENT_TOOLBAR_DESTROYED + ns, this.onToolbarDestroyed.bind(this));

    this.drawerInstance.on(this.drawerInstance.EVENT_DO_DEACTIVATE_ALL_TOOLS + ns, this._onDeactivateAllTools.bind(this));
    this.drawerInstance.on(this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL + ns, this._onDeactivateTool.bind(this));
    this.drawerInstance.on(this.drawerInstance.EVENT_DO_ACTIVATE_TOOL, this._onActivateTool.bind(this));
  };

  BaseTool.prototype._unsetHandlers = function () {
    var ns = '.tool-' + this.name;
    this.drawerInstance.off(this.drawerInstance.EVENT_TOOLBAR_DESTROYED + ns);
    this.drawerInstance.off(this.drawerInstance.EVENT_DO_DEACTIVATE_ALL_TOOLS + ns);
    this.drawerInstance.off(this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL + ns);
    this.drawerInstance.off(this.drawerInstance.EVENT_DO_ACTIVATE_TOOL + ns);
  };

  /**
   * Handler for EVENT_DO_DEACTIVATE_TOOL.
   * @param e event obj
   * @param tool tool object
   */
  BaseTool.prototype._onDeactivateTool = function (e, tool) {
    if (!tool || !tool.name) {
      throw new Error('BaseTool.onDeactivateTool() : no tool name is provided!');
    }
    if (this.active && (tool.name == this.name)) {
      this._deactivateTool();
    }
  };


  /**
   * Handler for EVENT_DO_ACTIVATE_TOOL.
   * @param e event obj
   * @param tool tool object
   */
  BaseTool.prototype._onActivateTool = function (e, tool) {
    if (!tool || !tool.name) {
      throw new Error('BaseTool._onActivateTool() : no tool name is provided!');
    }
    // Ignore, if event was for other tool, or if tool is already active
    if (tool.name === this.name && !this.active) {
      // ok, event is for this tool, and it is not active, so continue.
      // deactivate all tools.
      var dataToEvent = {
        beforeActivateTool: true
      };
      this.drawerInstance.trigger(this.drawerInstance.EVENT_DO_DEACTIVATE_ALL_TOOLS, dataToEvent);
      // now activate our tool!
      this._activateTool();
    }
  };


  /**
   * Creates tool button in toolbar provided.
   * @param {DrawerToolbar} toolbar toolbar, where this tool button will be created
   */
  BaseTool.prototype.createButton = function (toolbar) {
    var buttonConfig = {
      buttonOrder: this.options.buttonOrder,
      additionalClass: this.btnClass,
      iconClass: this.faClass,
      tooltipText: this.tooltip,
      clickHandler: this.onButtonClick.bind(this)
    };

    if (this.group) {
      buttonConfig.group = this.group;
      this.$toolButton = toolbar.addButtonToGroup(buttonConfig);
    } else {
      this.$toolButton = toolbar.addButton(buttonConfig);
    }
  };

  /**
   * On tool button click - trigger event to activate/deactivate tool
   */
  BaseTool.prototype.onButtonClick = function () {
    //    e.preventDefault();
    //    e.stopPropagation();

    var action = this.active ? this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL
      : this.drawerInstance.EVENT_DO_ACTIVATE_TOOL;

    this.drawerInstance.trigger(action, [this]);
  };

  /**
   * Base tool activation.
   * @protected
   */
  BaseTool.prototype._activateTool = function () {
    this.drawerInstance.log('TOOL', '[' + this.name + '] .activateTool() [BaseTool]');

    this.drawerInstance.fCanvas.discardActiveObject();
    this.drawerInstance.fCanvas.renderAll();

    this.active = true;
    this.drawerInstance.activeDrawingTool = this;

    this.highlightButton();
    this.drawerInstance.trigger(this.drawerInstance.EVENT_TOOL_ACTIVATED, [this]);
  };


  /**
   * Base tool deactivation.
   * @protected
   */
  BaseTool.prototype._deactivateTool = function () {
    this.drawerInstance.log('TOOL', '[' + this.name + '] : deactivateTool [BaseTool]');

    this.active = false;
    if (this.drawerInstance.activeDrawingTool === this) {
      this.drawerInstance.activeDrawingTool = null;
    }

    // make tool button not active
    this.highlightButtonOff();

    this.drawerInstance.fCanvas.renderAll();
    this.drawerInstance.trigger(this.drawerInstance.EVENT_TOOL_DEACTIVATED, [this]);
  };

  BaseTool.prototype.highlightButton = function () {
    if (this.group) {
      this.toolbar._setGroupButtonActive(this.group.name, this.btnClass);
    } else {
      this.toolbar.setActiveButton(this.btnClass);
    }
  };

  BaseTool.prototype.highlightButtonOff = function () {
    this.toolbar.clearActiveButton();
  };

  pluginsNamespace.BaseTool = BaseTool;
}(jQuery, DrawerJs.plugins));

(function ($, pluginsNamespace, BaseTool, util) {
  'use strict';


  var MOUSE_UP_TIMER = util.mouseUp('BaseBrushTimer');
  var MOUSE_DOWN_TIMER = util.mouseDown('BaseBrushTimer');
  /**
   * Base class for all brushes/free drawing tools.
   *
   * @param drawerInstance
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var BaseBrush = function (drawerInstance) {
    // call super constructor
    BaseTool.call(this, drawerInstance);

    // set type
    this.type = 'brush';

    // @todo : use it
    this.brushConstructor = fabric.ErasablePencilBrush;
  };


  BaseBrush.prototype = Object.create(BaseTool.prototype);
  BaseBrush.prototype.constructor = BaseBrush;

  BaseBrush.prototype.doNotZoomOnActivate = true;

  BaseBrush.prototype.createButton = function (toolbar) {
    var _this = this,
      clickHandler = function () {
        var action = _this.active ? _this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL
          : _this.drawerInstance.EVENT_DO_ACTIVATE_TOOL;

        _this.drawerInstance.trigger(action, [_this]);
      },
      buttonConfig = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: this.btnClass,
        iconClass: this.faClass,
        tooltipText: this.tooltip,
        clickHandler: clickHandler
      };


    this.$toolButton = toolbar.addButton(buttonConfig);
  };

  BaseBrush.prototype._activateTool = function () {
    // call _activateTool() of parent
    BaseTool.prototype._activateTool.call(this);
    this.drawerInstance.log('TOOL', this.name + ' : activateTool [BaseBrush]');

    var _this = this;
    var fCanvas = _this.drawerInstance.fCanvas;

    fCanvas.freeDrawingCursor =
      "url(/redactor.plugin.drawer/dist/cursor-fa-eraser.cur), default";
    fCanvas.isDrawingMode = true;

    // create brush
    if (!_this.brush) {
      if (!_this.createBrush) {
        _this.brush = new fabric.ErasablePencilBrush(_this.drawerInstance.fCanvas);
      } else {
        _this.brush = _this.createBrush();
      }
    }

    // save prev brush and set new
    _this.previousBrush = _this.drawerInstance.getBrush();
    _this.drawerInstance.setBrush(_this.brush);

    _this.drawerInstance.trigger(this.drawerInstance.EVENT_TOOL_ACTIVATED, [this]);
    // this method will be redefined in BaseBrush descendants
    this.afterActivateTool();
    this._attachTimerEvents();
  };

  /**
   * Attach event that indicates "Brush" drawing mode
   * @private
   */
  BaseBrush.prototype._attachTimerEvents = function () {
    var upperCanvasEl = this.drawerInstance.fCanvas.upperCanvasEl,
      $upperCanvasEl = $(upperCanvasEl);

    this._removeTimerEvents();
    // handle mouse down and mouse up
    $upperCanvasEl.on(MOUSE_DOWN_TIMER, this._onMouseDownTimer.bind(this));
    $(window.document).on(MOUSE_UP_TIMER, this._onMouseUpTimer.bind(this));
  };

  /**
   * Update indicator on mouse down
   * @private
   */
  BaseBrush.prototype._onMouseDownTimer = function () {
    this.drawerInstance.isBrushDrawing = true;
  };

  /**
   * Update indicator on mouse up
   * @private
   */
  BaseBrush.prototype._onMouseUpTimer = function () {
    var self = this;
    util.setTimeout(function () {
      self.drawerInstance.isBrushDrawing = false;
    }, 0);
  };

  /**
   * Remove events
   * @private
   */
  BaseBrush.prototype._removeTimerEvents = function () {
    var upperCanvasEl = this.drawerInstance.fCanvas.upperCanvasEl,
      $upperCanvasEl = $(upperCanvasEl);

    // handle mouse down and mouse up
    $upperCanvasEl.off(MOUSE_DOWN_TIMER);
    $(window.document).off(MOUSE_UP_TIMER);
  };

  /**
   *
   *
   * @private
   */
  BaseBrush.prototype._deactivateTool = function () {
    // call _deactivateTool() of parent
    BaseTool.prototype._deactivateTool.call(this);
    this.drawerInstance.log('TOOL', this.name + ' : deactivateTool [BaseBrush]');

    this.drawerInstance.fCanvas.isDrawingMode = false;

    // remove mouse events listening
    $(this.drawerInstance.fCanvas.upperCanvasEl)
      .off('mousedown.BaseBrush touchstart.BaseBrush');

    $(this.drawerInstance.fCanvas.upperCanvasEl)
      .off('mouseup.BaseBrush touchend.BaseBrush');

    this._removeTimerEvents();
    // restore prev brush
    this.drawerInstance.setBrush(this.previousBrush);
    this.brush = null;

    this.drawerInstance.trigger(this.drawerInstance.EVENT_TOOL_DEACTIVATED, [this]);
    // this method will be redefined in BaseBrush descendants
    this.afterDeactivateTool();
  };


  /**
   * Method MUST be redefined in descendants.
   * Method is called in the end of _deactivateTool().
   */
  BaseBrush.prototype.afterActivateTool = function () {
    throw new Error(this.name + ' should implement afterActivateTool() method.');
  };

  /**
   * Method MUST be redefined in descendants.
   * Method is called in the end of _deactivateTool().
   */
  BaseBrush.prototype.afterDeactivateTool = function () {
    throw new Error(this.name + ' should implement afterDeactivateTool() method.');
  };

  pluginsNamespace.BaseBrush = BaseBrush;

}(jQuery, DrawerJs.plugins, DrawerJs.plugins.BaseTool, DrawerJs.util));


(function ($, pluginsNamespace, BaseTool, util) {
  "use strict";

  /**
   * Provides mechanism to draw shapes like in photoshop.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * @memberof DrawerJs.plugins
   * @constructor
   */
  var BaseShape = function BaseShapeConstructor(drawerInstance) {
    if (!drawerInstance) {
      throw new Error("BaseShape CTOR : drawerInstance is not set!");
    }

    // call super constructor
    BaseTool.call(this, drawerInstance);
    var _this = this;
    this.drawer = drawerInstance;

    this.type = 'shape';
    this.objectBaseType = 'shape';
    /**
     * List of tool options to show when tool is activated
     * @type {Array}
     */
    this.toolOptionsList = ['color', 'border', 'opacity'];

    this.drawingInProgress = false;
    this.onlyOneItem = true;

    this.MOUSEMOVE = this.getEventId(['mousemove', 'touchmove']);
    this.MOUSEDOWN = this.getEventId(['mousedown', 'touchstart']);
    this.MOUSEUP = this.getEventId(['mouseup', 'touchend']);
  };


  BaseShape.prototype = Object.create(BaseTool.prototype);
  BaseShape.prototype.constructor = BaseShape;

  /**
   * Min size of shape in px. If less - shape will not be added
   * @type {number}
   */
  BaseShape.prototype.minShapeSize = 4;

  /**
   * Min size of shape in px for touch devices. If less - shape will not be added
   * @type {number}
   */
  BaseShape.prototype.minShapeSizeForTouch = 15;


  BaseShape.CENTERING_MODE = {
    NORMAL: 'normal',
    FROM_CENTER: 'from_center'
  };

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  BaseShape.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  BaseShape.prototype.getEventId = function (event) {
    var _this = this;

    if (event instanceof String) {
      event = [event];
    }

    if (event instanceof Array) {
      return event.map(function (e) {
        return e + '.drawerTool' + _this.btnClass;
      }).join(' ');
    }
  };

  BaseShape.prototype._activateTool = function () {
    if (this.active)
      return;

    // call _activateTool() of parent
    BaseTool.prototype._activateTool.call(this);

    this.drawerInstance.log('TOOL', this.name + ' : _activateTool() [BaseShape]');

    var _this = this;

    // show no tooltip on touch devices
    if (!this.drawerInstance.touchDevice) {
      _this.showHelpTooltip();
    }

    var fCanvas = this.drawerInstance.fCanvas;
    // remove all active selection
    fCanvas.disableSelection();

    fCanvas.renderAll();

    fCanvas.defaultCursor = 'crosshair';

    if (this.setUpHandlers) {
      this.setUpHandlers();
    } else {
      this._setUpHandlers();
    }
  };


  BaseShape.prototype._setUpHandlers = function () {
    var _this = this;
    var fCanvas = this.drawerInstance.fCanvas;

    $(fCanvas.upperCanvasEl).on(_this.MOUSEDOWN, function (event) {
      _this.drawerInstance.log('baseShape', 'mousedown');

      $(fCanvas.upperCanvasEl).off(_this.MOUSEDOWN);

      // no tooltip was shown on touch devices
      if (!_this.drawerInstance.touchDevice) {
        _this.removeHelpTooltip();
      }

      if (fCanvas.getActiveObject()) {
        return true;
      }

      if (_this.drawingInProgress) {
        _this.finishItemDraw();
        return true;
      }

      _this.drawingInProgress = true;
      _this.drawerInstance.drawingInProgress = true;
      var startPointCoords = _this.drawer.fCanvas.getPointer(event, true);

      if (!_this.createShape) {
        throw new Error('createShape method does not exist', _this);
      }

      _this.shape = _this.createShape(
        startPointCoords.x,
        startPointCoords.y
      );

      _this.drawerInstance.trigger(_this.drawerInstance.EVENT_ZOOM_SET);
      fCanvas.add(_this.shape);
      _this.drawerInstance.trigger(_this.drawerInstance.EVENT_ZOOM_RESTORE);

      $(document).on(_this.MOUSEMOVE, function (event) {
        var pointCoords = _this.drawer.fCanvas.getPointer(event);

        if (!_this.updateShape) {
          throw new Error('updateShape method does not exist', _this);
        }

        _this.updateShape(_this.shape,
          pointCoords.x,
          pointCoords.y
        );

        fCanvas.renderAll();
      });

      $(document).on(_this.MOUSEUP, function () {
        _this.drawerInstance.log('baseShape', 'mouseup');
        var minSize = _this.drawer.touchDevice ? _this.minShapeSizeForTouch : _this.minShapeSize,
          widthIsSmaller = _this.shape.width < minSize,
          heightIsSmaller = !_this.checkOnlyWidth && (_this.shape.height < minSize),
          shapeIsSmaller = _this.checkOnlyWidthOrHeight ? widthIsSmaller && heightIsSmaller : widthIsSmaller || heightIsSmaller,
          preventAddOfShape = shapeIsSmaller;

        // Check, if shape is too small.
        // for desktop this is ok - shape will still follow mouse and
        // drawing will be stopped when user make a second click
        // but for touch devices this will not work so we simply remove a
        // shape if its too small but allow him to draw another.

        if (preventAddOfShape) {
          _this.shape.remove();
          _this.shape = null;

          _this._deactivateTool();
          _this._activateTool();
          return;
        }

        // finish drawing
        _this.finishItemDraw();

        // some tools are supposed to draw one shape and then deactivate
        if (_this.onlyOneItem) {
          _this.drawerInstance.trigger(_this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL, _this);
        }

      });
    });
  };

  BaseShape.prototype.finishItemDraw = function () {
    var _this = this;
    var fCanvas = _this.drawerInstance.fCanvas;

    fCanvas.defaultCursor = 'default';

    if (!this.drawerInstance.touchDevice) {
      _this.removeHelpTooltip();
    }
    fCanvas.deactivateAll();
    fCanvas.calcOffset();

    _this.drawingInProgress = false;
    util.setTimeout(function () {
      _this.drawerInstance.drawingInProgress = false;
    });
    $(document).off(_this.MOUSEMOVE);
    $(document).off(_this.MOUSEUP);

    // call finishShape, if it is implemented
    if (this.finishShape) {
      this.finishShape(this.shape);
    }

    if (_this.shape) {
      if (_this.shape.width < 0) {
        _this.shape.set('width', _this.shape.width * -1);
        _this.shape.set('left', _this.shape.left - _this.shape.width);
        _this.shape.set('flipX', true);
      }
      if (_this.shape.height < 0) {
        _this.shape.set('height', _this.shape.height * -1);
        _this.shape.set('top', _this.shape.top - _this.shape.height);
        _this.shape.set('flipY', true);
      }

      var newShape = _this.shape.clone();

      _this.shape.remove();
      _this.shape = null;

      fCanvas.deactivateAll();

      fCanvas.add(newShape);

      fCanvas.restoreSelection();

      fCanvas.renderAll();
      fCanvas.calcOffset();
      fCanvas.renderAll();
    }
  };

  BaseShape.prototype._deactivateTool = function () {
    if (!this.active) {
      return;
    }
    // call _deactivateTool() of parent
    BaseTool.prototype._deactivateTool.call(this);

    this.drawerInstance.log('TOOL', this.name + ' : _deactivateTool() [BaseShape]');

    this.finishItemDraw();

    $(this.drawerInstance.fCanvas.upperCanvasEl)
      .off(this.MOUSEDOWN);

    this.drawerInstance.fCanvas.restoreSelection();
  };


  BaseShape.prototype.showHelpTooltip = function () {
    var _this = this;
    var fCanvas = _this.drawerInstance.fCanvas;

    var helpText = '';
    if (this.helpTooltipText) {
      helpText = this.helpTooltipText;
    } else {
      helpText = _this.drawerInstance.t('Click to start drawing a ') +
        '<i class="fa ' + _this.faClass + '"></i>';
    }

    _this.cursorTooltip = $(
      '<div class="drawer-tool-mouse-tooltip ' + this.btnClass + '">' +
      helpText +
      '</div>'
    );

    $('#' + this.drawerContainerID).append(_this.cursorTooltip);
    $(fCanvas.upperCanvasEl)
      .on('mousemove.drawer-tool-mouse-toolip', function (event) {
        _this.cursorTooltip.css('left', event.pageX);
        _this.cursorTooltip.css('top', event.pageY);
      });

    $(fCanvas.upperCanvasEl).on('mouseleave', function () {
      _this.cursorTooltip.css('opacity', 0);
    });

    $(fCanvas.upperCanvasEl).on('mouseenter', function () {
      _this.cursorTooltip.css('opacity', 1);
    });
  };

  BaseShape.prototype.removeHelpTooltip = function () {
    if (this.cursorTooltip) {
      $('body').off('mousemove.drawer-tool-mouse-toolip');
      this.cursorTooltip.fadeOut();
      this.cursorTooltip.remove();
    }
  };


  pluginsNamespace.BaseShape = BaseShape;

}(jQuery, DrawerJs.plugins, DrawerJs.plugins.BaseTool, DrawerJs.util));

(function ($, pluginsNamespace) {
  'use strict';

  /**
   * Base class for tool options plugins.
   *
   * @param drawer
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var BaseToolOptions = function (drawer, options) {
    if (!drawer) {
      throw new Error("BaseToolOptions CTOR : drawer is not set!");
    }
    this.drawer = drawer;
    this._setupOptions(options);

    // handle toolbar created/destroyed
    this._bindedOnToolbarCreated = this._onToolbarCreated.bind(this);
    drawer.on(drawer.EVENT_OPTIONS_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    drawer.on(drawer.EVENT_TOOLBAR_DESTROYED, this._onToolbarDestroyed.bind(this));

    // handle tool activation/deactivation
    drawer.on(drawer.EVENT_DO_DEACTIVATE_ALL_TOOLS, this._onDeactivateTool.bind(this));

    drawer.on(drawer.EVENT_DO_DEACTIVATE_TOOL, this._onDeactivateTool.bind(this));
    drawer.on(drawer.EVENT_DO_ACTIVATE_TOOL, this._onActivateTool.bind(this));

    drawer.on(drawer.EVENT_TOOL_DEACTIVATED, this._onDeactivateTool.bind(this));
    drawer.on(drawer.EVENT_TOOL_ACTIVATED, this._onActivateTool.bind(this));

    // handle object selection/deselection
    drawer.on(drawer.EVENT_OBJECT_SELECTED, this._onObjectSelected.bind(this));
    drawer.on(drawer.EVENT_SELECTION_CLEARED, this._onSelectionCleared.bind(this));

    // react on edit mode entering
    drawer.on(drawer.EVENT_TEXT_EDITING_ENTERED, this._onTextEditingEntered.bind(this));
    // react on edit mode exiting
    drawer.on(drawer.EVENT_TEXT_EDITING_EXITED, this._onTextEditingExited.bind(this));
  };

  /**
   * is active at the moment
   * @type {boolean}
   */
  BaseToolOptions.prototype.active = false;

  /** Instance of Drawer
   * @type {Drawer} */
  BaseToolOptions.prototype.drawer = null;

  /** Instance of toolbar
   * @type {DrawerToolbar} */
  BaseToolOptions.prototype.toolbar = null;

  /**
   * Tool name
   * @const
   * @type {string}
   */
  BaseToolOptions.prototype.name = '';

  /**
   * Tool type
   */
  BaseToolOptions.prototype.type = null;

  /**
   * Tool name. On selecting tool/object, if this.optionName is in array of
   * object allowed options - tool will show controls
   * @const
   * @type {String}
   */
  BaseToolOptions.prototype.optionName = '';

  /**
   * Css class of tool button
   * @default
   * @const
   * @type {String}
   */
  BaseToolOptions.prototype.btnClass = 'btn';

  /**
   * Tool icon font-awesome class
   * @const
   * @type {String}
   */
  BaseToolOptions.prototype.faClass = '';

  /**
   * Show tool only in edit mode
   * @default
   * @const
   * @type {boolean}
   */
  BaseToolOptions.prototype.showOnEditMode = false;

  /**
   * Hide tool in edit mode
   * @default
   * @const
   * @type {boolean}
   */
  BaseToolOptions.prototype.hideOnEditMode = true;

  /**
   * Current option data
   * @type {Object}
   */
  BaseToolOptions.prototype.data = {};

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  BaseToolOptions.prototype._setupOptions = function (options, pluginName, doNotSave) {
    options = options || {};
    var optionsFromDrawer = this._collectDefaultOptions(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options
      );

    var updatedResult = this._onOptionsSetup(this._defaultOptions, options, optionsFromDrawer, result);
    result = updatedResult || result;
    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  /**
   * Process options
   * @param {Object} [defaultOptions]
   * @param {Object} [options]
   * @param {Object} [optionsFromDrawer]
   * @param {Object} [result]
   * @returns {Object}
   * @private
   */
  BaseToolOptions.prototype._onOptionsSetup = function (defaultOptions, options, optionsFromDrawer, result) {
    return result;
  };

  /**
   * Setup data
   * @param {String} [pluginName] - name of plugin
   * @returns {Object}
   * @private
   */
  BaseToolOptions.prototype._collectDefaultOptions = function (pluginName) {
    pluginName = pluginName || this.name || this.optionName;
    var drawer = this.drawerInstance || this.drawer,
      result = drawer && drawer.getPluginConfig(pluginName);
    return result;
  };


  /**
   * On toolbar created - create tool button.
   * @param {fabric.Event} ev
   * @param {DrawerToolbar} toolbar
   * @private
   */
  BaseToolOptions.prototype._onToolbarCreated = function (ev, toolbar) {
    this.toolbar = toolbar;
    if (this.createControls) {
      this.createControls(toolbar);
    }

    if (this.useCombobox && this.$toolControl) {
      var comboBoxOptions = {
        drawer: this.drawer,
        editable: !this.onlyPredefined,
        buttonMode: this.buttonMode
      },
        $targetSelect = this.$toolControl.find('select');
      $targetSelect.ToolbarComboBox(comboBoxOptions);
      this.comboBox = $targetSelect.getComboBox();
    }
  };


  /**
   * On toolbar destroyed - destroy button, if it was our toolbar.
   * @param {fabric.Event} ev
   * @param {DrawerToolbar} toolbar
   * @private
   */
  BaseToolOptions.prototype._onToolbarDestroyed = function (ev, toolbar) {
    if (this.toolbar == toolbar) {
      this.removeTool();
    }
  };


  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listeners of toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  BaseToolOptions.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    if (this.deleteControls) {
      this.deleteControls();
    }

    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawer.off(this.drawer.EVENT_OPTIONS_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };

  /**
   * React on edit mode entering
   * @param  {fabric.Event} [fEvent]
   * @param  {fabric.Object} tool
   * @private
   */
  BaseToolOptions.prototype._onTextEditingEntered = function (fEvent, tool) {
    tool = tool && tool.target;

    var isEditMode = tool && tool.isEditing,
      showControls = !this.hideOnEditMode && (this.showOnEditMode || isEditMode);
    if (showControls) {
      this.showControls(true, tool);
    } else {
      this.hideControls();
    }
  };

  /**
   * React on edit mode exiting
   * @param  {fabric.Event} [fEvent]
   * @param  {fabric.Object} [tool]
   * @private
   */
  BaseToolOptions.prototype._onTextEditingExited = function (fEvent, tool) {
    var isEditMode = tool && tool.target && tool.target.isEditing,
      showControls = this.hideOnEditMode && !isEditMode;
    if (showControls) {
      this.showControls();
    } else {
      this.hideControls();
    }
  };

  /**
   * Handler for EVENT_DO_DEACTIVATE_TOOL.
   * Hides controls.
   * Calls this.onDeactivateTool() if it is defined
   *
   * @param e event obj
   * @param tool tool object
   * @private
   */
  BaseToolOptions.prototype._onDeactivateTool = function (e, tool) {
    // set active to false
    this.active = false;
    // hide controls
    var needToShow = this.options && this.options.alwaysVisible;
    if (!needToShow) {
      this.hideControls();
    } else {
      this.showControls();
    }
  };


  /**
   * Default handler for EVENT_DO_ACTIVATE_TOOL.
   * Calls this.updateOnTool()
   *
   * @param {fabric.Event} e event obj
   * @param {BaseTool} tool tool object
   * @private
   */
  BaseToolOptions.prototype._onActivateTool = function (e, tool) {
    this.updateOnTool(tool);
  };


  /**
   * Shows/hides controls depending on tool.toolOptionsList
   *
   * @param {BaseTool} tool tool object
   */
  BaseToolOptions.prototype.updateOnTool = function (tool) {
    // look if activated tool toolOptionsList has this option name
    var toolHasOption = tool.toolOptionsList && (tool.toolOptionsList.indexOf(this.optionName) !== -1),
      editModeIsCorrect = (!this.showOnEditMode || tool.isEditing),
      needToShowControls = toolHasOption && editModeIsCorrect;
    if (needToShowControls) {
      this.active = true;
      // show controls
      this.showControls();
      // and activate tool
      if (this.onActivateTool) {
        this.onActivateTool(tool);
      }
    } else {
      this.active = false;
      // show controls
      this.hideControls(tool.forceOptionsHide);
    }
  };


  /**
   * Shows/hides controls, depending on selected object optionName
   * @param  {Event} event
   * @param  {fabric.Event} fabricEvent [description]
   * @private
   */
  BaseToolOptions.prototype._onObjectSelected = function (event, fabricEvent) {
    var target = fabricEvent.target,
      toolHasOption = target.objectOptionsList && (target.objectOptionsList.indexOf(this.optionName) !== -1),
      editModeIsCorrect = (!this.showOnEditMode || target.isEditing),
      needToShowControls = toolHasOption && editModeIsCorrect;
    if (needToShowControls) {
      // show controls
      this.showControls();
      // update controls
      this.updateControlsFromObject(target);
    } else {
      this.hideControls();
    }
  };

  /**
   * React on object deselect
   * @private
   */
  BaseToolOptions.prototype.onSelectionCleared = function () {

  };


  /**
   * On selection cleared - hide option controls if not tool is active.
   * Else shows option controls for active tool.
   * Calls this.onSelectionCleared if defined.
   * @private
   */
  BaseToolOptions.prototype._onSelectionCleared = function () {
    // if tool is active now - show controls for it. If no - hide  controls
    if (this.drawer.activeDrawingTool) {
      this.updateOnTool(this.drawer.activeDrawingTool);
    } else {
      this.hideControls();
    }
    this.onSelectionCleared();
  };

  /**
   * Collect necessary data from object
   * @param  {fabric.Object} target - fabric object
   * @returns {object} result
   */
  BaseToolOptions.prototype.collectDataFromObject = function (target) {
    var result = {};
    return result;
  };

  /**
   * Update controls with actual data
   * @param {object} data
   */
  BaseToolOptions.prototype.updateControls = function (data) {
  };

  /**
   * Collect and update controls with data from target object
   * @param  {fabric.Object} target - fabric object
   */
  BaseToolOptions.prototype.updateControlsFromObject = function (target) {
    this.collectDataFromObject(target);
    this.updateControls(this.data);
  };

  /**
   * This have to be redefined in child class
   * @param {Boolean} [withUpdate] - need to update controls
   * @param  {fabric.Object} [tool] - for styles collecting
   */
  BaseToolOptions.prototype.showControls = function (withUpdate, tool) {
  };

  /**
   * This have to be redefined in child class
   * @param {Boolean} [force] - force hide ignoring any options
   */
  BaseToolOptions.prototype.hideControls = function (force) {
  };


  pluginsNamespace.BaseToolOptions = BaseToolOptions;
}(jQuery, DrawerJs.plugins));

(function ($, pluginsNamespace, BaseToolOptions, util) {
  'use strict';

  /**
   * Base class for text options plugins.
   *
   * @param drawer
   * @constructor
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseToolOptions
   */
  var BaseTextOptionTool = function (drawer) {
    BaseToolOptions.call(this, drawer);
    this.drawer = drawer;

    this._setEventHandlers();
    this._setGlobalClickHandler();
  };

  BaseTextOptionTool.prototype = Object.create(BaseToolOptions.prototype);
  BaseTextOptionTool.prototype.constructor = BaseToolOptions;

  /**
   * Button icon class - font Awesome
   * @constant
   * @type {string}
   */
  BaseTextOptionTool.prototype.buttonIconClass = '';
  BaseTextOptionTool.prototype.showOnEditMode = true;
  BaseTextOptionTool.prototype.hideOnEditMode = false;

  BaseTextOptionTool.prototype.focusTextOnChange = false;

  BaseTextOptionTool.prototype.onlyPredefined = false;
  BaseTextOptionTool.prototype.stylesToObject = false;

  BaseTextOptionTool.prototype.useCombobox = true;
  BaseTextOptionTool.prototype.buttonMode = true;

  BaseTextOptionTool.prototype.predefined = {};
  BaseTextOptionTool.prototype.valueType = {};

  BaseTextOptionTool.prototype.minValue = undefined;
  BaseTextOptionTool.prototype.maxValue = undefined;

  /**
   * Setup events
   * @private
   */
  BaseTextOptionTool.prototype._setEventHandlers = function () {
    // react on styles changes of current text object
    this.drawer.on(this.drawer.EVENT_TEXT_STYLES_CHANGED, this._onStylesChanged.bind(this));

    // get styles handler
    this.drawer.on(this.drawer.EVENT_TEXT_GET_STYLES, this._onGetStyles.bind(this));

    if (this._onObjectAdded) {
      this.drawer.on(this.drawer.EVENT_OBJECT_ADDED, this._onObjectAdded.bind(this));
    }
  };

  /**
   * Apply selected border style to added object.
   *
   * @param {fabric.Event} fEvent
   * @param {Object} styles
   * @param {Object} [objectStyles]
   * @param {Array} [stylesWithMultipleValues]
   * @private
   */
  BaseTextOptionTool.prototype._onStylesChanged = function (fEvent, styles, objectStyles, stylesWithMultipleValues) {
    this.updateControls(styles, objectStyles, stylesWithMultipleValues);
  };


  /**
   * Get styles of current control
   *
   * @param {fabric.Event} [fEvent]
   * @param {fabric.Object} [tool]
   * @param {Object} result
   * @private
   */
  BaseTextOptionTool.prototype._onGetStyles = function (fEvent, tool, result) {
    result = result || {};

    //@todo get active style

    result.defaultValues = $.extend(true, {}, result.defaultValues || {}, this.options.defaultValues || {});
  };

  /**
   * Fill controls with data
   * @param {Object} [styles]
   * @param {Object} [objectStyles]
   * @param {Array} [stylesWithMultipleValues]
   */
  BaseTextOptionTool.prototype.updateControls = function (styles, objectStyles, stylesWithMultipleValues) {
    styles = styles || this._lastData || {};
    objectStyles = objectStyles || {};
    stylesWithMultipleValues = stylesWithMultipleValues || [];

    var _self = this,
      $toolControl = this.$toolControl;
    if ($toolControl) {
      var $allControls = $toolControl.find('.controls-value-item');
      $allControls.each(function (i, currElement) {
        var $currElement = $(currElement),
          isInputType = $currElement.is('input, select, textarea'),
          currValueName = $currElement.data('name'),
          defaultValue = _self._defaultOptions.defaultValues[currValueName],
          inheritedValue = objectStyles[currValueName],
          multipleValues = stylesWithMultipleValues.indexOf(currValueName) !== -1,
          currValueIsInvalid = styles[currValueName] === undefined,
          neededValue = styles[currValueName] !== undefined ? styles[currValueName] : inheritedValue || defaultValue;

        if (multipleValues) {
          neededValue = '';
        }

        if (isInputType) {
          $currElement.val(neededValue);
        }

        _self._updateClasses(currValueIsInvalid, currValueIsInvalid && neededValue === inheritedValue, multipleValues);

        $toolControl.toggleClass('option-value-invalid', !!currValueIsInvalid);
        $toolControl.toggleClass('option-value-multiple', !!multipleValues);
        $toolControl.toggleClass('option-value-inherited', !!(currValueIsInvalid && neededValue === inheritedValue));

        var haveError = currValueIsInvalid || multipleValues,
          inheritedClassString = currValueIsInvalid && neededValue === inheritedValue ? ' option-value-inherited ' : '',
          invalidClassString = currValueIsInvalid ? ' option-value-invalid ' : '',
          multipleClassString = multipleValues ? ' option-value-multiple ' : '',
          classString = '' + inheritedClassString + invalidClassString + multipleClassString;

        if ($currElement.data('comboBox')) {
          $currElement.data('comboBox').updateSelectedValues(neededValue, classString);
        } else {
          $currElement.trigger('valueChanged', [{
            value: neededValue,
            valueName: currValueName,
            inherited: currValueIsInvalid || neededValue === inheritedValue,
            multipleValues: multipleValues,
            classString: classString
          }]);
        }

        _self.updateSingleControl(currValueName, neededValue);
      });
    }
  };

  /**
   * Update indicator classes
   * @param {Boolean} [valueIsInvalid] - control have invalid value
   * @param {Boolean} [valueIsInherited] - control have value inherited from object
   * @param {Boolean} [valueIsMultiple] - current selection have multiple values for current style
   * @private
   */
  BaseTextOptionTool.prototype._updateClasses = function (valueIsInvalid, valueIsInherited, valueIsMultiple) {
    this.$toolControl.toggleClass('option-value-invalid', !!valueIsInvalid);
    this.$toolControl.toggleClass('option-value-multiple', !!valueIsInherited);
    this.$toolControl.toggleClass('option-value-inherited', !!valueIsMultiple);
  };

  /**
   * React on edit mode entering
   * @param  {String} valueName
   * @param  {*} value
   */
  BaseTextOptionTool.prototype.updateSingleControl = function (valueName, value) {

  };

  /**
   * Apply styles
   * @param {object.<string>} [styles] - Styles object
   */
  BaseTextOptionTool.prototype.setStyles = function (styles) {
    //@TODO refactor - to many render
    styles = styles || this._lastData || this.getStylesFromControls();

    var currText = this.drawer.fCanvas.getActiveObject();
    if (currText) {
      var selectionIsEmpty = currText.selectionEnd === currText.selectionStart;
      if (selectionIsEmpty) {
        this.drawer.setTemporaryStyles(styles);
      } else {
        this.drawer.setTemporaryStyles();
        if (this.stylesToObject) {
          for (var styleName in styles) {
            currText[styleName] = styles[styleName];
          }
        } else {
          currText.setSelectionStyles(styles);
        }
      }
      this._lastData = styles;
      if (this.focusTextOnChange) {
        if (this.comboBox) {
          this.comboBox.hideDropdown();
        }
        if (currText.hiddenTextarea) {
          currText.hiddenTextarea.focus();
        }
      }
      currText.canvas.renderAll();
    }
  };

  /**
   * Validate and normalize value
   * @param {*} value
   * @param {string} valueName
   * @param {boolean} [doNotUseDefault]
   * @return {*} result - normalized value
   */

  BaseTextOptionTool.prototype.normalizeValue = function (value, valueName, doNotUseDefault) {
    var result,
      defaultValue = !doNotUseDefault ? this.options.defaultSize : undefined,
      validValue = this.validateValue(value, valueName),
      overrideWithDefault = !validValue;

    result = overrideWithDefault ? defaultValue : value;

    if (this.minValue && this.minValue[valueName] !== undefined) {
      result = result < this.minValue[valueName] ? this.minValue[valueName] : result;
    }

    if (this.maxValue && this.maxValue[valueName] !== undefined) {
      result = result > this.maxValue[valueName] ? this.maxValue[valueName] : result;
    }
    return result;
  };

  /**
   * Attach events for control element
   * @private
   */
  BaseTextOptionTool.prototype._attachEvents = function () {
    if (this.$toolControl) {
      this.$toolControl.on('input change toolbarOptionChange', this.onInputChange.bind(this));
    }
  };

  /**
   * Create controls
   * @returns {string} result - html of controls
   */
  BaseTextOptionTool.prototype.generateControlHtml = function () {
    var result = '';

    if (this.controlTemplate) {
      if (typeof this.controlTemplate === 'function') {
        result = this.controlTemplate();
      } else {
        result = this.controlTemplate;
      }
    }
    return result;
  };

  /**
   * Create controls.
   * @param  {DrawerToolbar} toolbar to add control to
   * @param  {Function} [changeCallback]
   */
  BaseTextOptionTool.prototype.createControls = function (toolbar, changeCallback) {
    this.changeCallback = changeCallback;

    var toolControlHtml = this.generateControlHtml();
    this.$toolControl = $(toolControlHtml);

    toolbar.addControl(this.$toolControl, this.options.buttonOrder);
    this.setupControl(toolbar, this.$toolControl, changeCallback);
    this._attachEvents();

    return this.$toolControl;
  };

  /**
   * Create controls.
   * @param  {DrawerToolbar} toolbar - to add control to
   * @param  {jQuery} [$toolControl] - tool control element
   * @param  {Function} [changeCallback]
   */
  BaseTextOptionTool.prototype.setupControl = function (toolbar, $toolControl, changeCallback) {
  };


  /**
   * Setup data
   * @param {String} pluginName - name of plugin
   * @returns {Object}
   * @private
   */
  BaseTextOptionTool.prototype._collectDefaultOptions = function (pluginName) {
    var textConfig = this.drawer.getPluginConfig('Text'),
      result = {
        predefined: $.extend(true, {}, textConfig.predefined || {}),
        defaultValues: $.extend(true, {}, textConfig.defaultValues || {})
      };
    return result;
  };

  /**
   * Process options
   * @param {Object} [defaultOptions]
   * @param {Object} [options]
   * @param {Object} [optionsFromDrawer]
   * @param {Object} [result]
   * @returns {Object}
   * @private
   */
  BaseTextOptionTool.prototype._onOptionsSetup = function (defaultOptions, options, optionsFromDrawer, result) {
    options = options || {};
    defaultOptions = defaultOptions || {};
    optionsFromDrawer = optionsFromDrawer || {};

    var valueName,
      defVal,
      drawVal,
      optVal;

    /**
     * Because https://bugs.jquery.com/ticket/9477
     * Bug - extend (deep) merges arrays fields instead of replacing them
     * **/
    for (valueName in result.predefined) {
      defVal = (defaultOptions.predefined && defaultOptions.predefined[valueName]);
      drawVal = (optionsFromDrawer.predefined && optionsFromDrawer.predefined[valueName]);
      optVal = (options.predefined && options.predefined[valueName]);
      result.predefined[valueName] = optVal !== undefined && optVal !== false ? optVal :
        drawVal !== undefined && drawVal !== false ? drawVal :
          defVal !== undefined && defVal !== false ? defVal : [];
    }
    return result;
  };

  /**
   * Handle change of values via controls
   * @param  {Event} e - Event that modifies control's values
   * @private
   * @todo rename as private
   */
  BaseTextOptionTool.prototype.onInputChange = function (e) {
    var valueFromEvent;
    valueFromEvent = this.getStylesFromChangeEvent(e);
    if (!valueFromEvent) {
      this.getStylesFromControls();
    }
    this.setStyles(valueFromEvent);
    this._updateClasses();
  };

  /**
   * Collect data from change event
   * @param  {Event} e - event that modifies control's values
   */
  BaseTextOptionTool.prototype.getStylesFromChangeEvent = function (e) {

  };

  /**
   * Collect data from controls
   * @returns {Object}
   */
  BaseTextOptionTool.prototype.getStylesFromControls = function () {
    var _self = this,
      result = {},
      $toolControl = this.$toolControl;
    if ($toolControl) {
      var $allControls = $toolControl.find('input, select, textarea');
      $allControls.each(function (i, currElement) {
        var $currElement = $(currElement),
          currValueName = $currElement.data('name'),
          currValue = $currElement.val(),
          validatedValue = _self.normalizeValue(currValue, currValueName);

        if (currValueName !== undefined && currValue !== undefined) {
          result[currValueName] = validatedValue;
        }
      });
    }
    this._lastData = result;
    return result;
  };

  /**
   * Validates value
   * @param {*} value
   * @param {string} valueName
   * @param {Boolean} [strictMode]
   * @return {boolean}
   */

  BaseTextOptionTool.prototype.validateValue = function (value, valueName, strictMode) {
    var valueIsValid,
      checkForMatchWithPredefined = this.onlyPredefined && this.predefined && this.predefined[valueName],
      equalToPredefinedValue = checkForMatchWithPredefined && this.predefined[valueName].indexOf(value) !== -1;

    switch (this.valueType[valueName]) {
      case 'number':
        value = parseFloat(value);
        valueIsValid = typeof value === 'number' && isFinite(value);
        break;
      case 'string':
        valueIsValid = typeof value === 'string';
        break;
      case 'color':
        var isString = typeof value === 'string',
          isColorInstance = value instanceof fabric.Color;
        valueIsValid = strictMode ? isColorInstance : isString;
        break;
      default:
        valueIsValid = value !== undefined;
        break;
    }

    valueIsValid = checkForMatchWithPredefined ? (valueIsValid && equalToPredefinedValue) : valueIsValid;
    return valueIsValid;
  };

  /**
   * Hides controls
   */
  BaseTextOptionTool.prototype.hideControls = function () {
    if (this.$toolControl) {
      this.$toolControl.addClass('hidden');
    }
  };


  /**
   * Shows controls
   * @param {Boolean} [withUpdate] - need to update controls
   * @param  {fabric.Object} [tool] - for styles collecting
   */
  BaseTextOptionTool.prototype.showControls = function (withUpdate, tool) {
    if (withUpdate) {
      tool = tool || this.drawer.fCanvas.getActiveObject();
      var currentStyles = tool.getSelectionStyles(),
        objStyles = tool.getObjStyles();
      this.updateControls(currentStyles, objStyles);
    }
    if (this.$toolControl) {
      this.$toolControl.removeClass('hidden');
    }
  };

  /**
   * Removes tool
   */
  BaseTextOptionTool.prototype.remove = function () {
    if (this.$toolControl) {
      this.$toolControl.remove();
    }
  };

  /** @TODO check
   * Set listeners for clicks - to properly close controls on outside clicks
   * @private
   */
  BaseTextOptionTool.prototype._setGlobalClickHandler = function () {
    var self = this,
      $html = $('html');

    $html.off('click.textOptionTool').on('click.textOptionTool', function (e) {
      self.hideControls();
    });
  };

  pluginsNamespace.BaseTextOptionTool = BaseTextOptionTool;
}(jQuery, DrawerJs.plugins, DrawerJs.plugins.BaseToolOptions, DrawerJs.util));
(function ($, DrawerApi, util) {
  'use strict';
  var emptyFunc = function () { };

  /**
   * Check if image have valid width/height
   * @param {Image} image
   * @returns {Boolean}
   */
  function checkImageSizes(image) {
    var result = false;
    if (image) {
      var widthIsValid = image.naturalWidth !== undefined && image.naturalWidth !== 0,
        heightIsValid = image.naturalHeight !== undefined && image.naturalHeight !== 0,
        sizesAreValid = widthIsValid && heightIsValid;
      result = sizesAreValid;
    }
    return result;
  }

  /**
   * Set image from url as inactive background image of drawer
   * @param {String} imageUrl - url of image
   * @private
   */
  function _setImageAsInactiveBackground(imageUrl) {
    var styleSelector = '.editable-canvas-not-edited',
      styleRules = '' +
        'background: url(' + imageUrl + ') !important;' +
        'background-repeat: no-repeat !important;' +
        'background-position: center !important;' +
        'background-size: contain !important;';
    util.addStyleToStyleSheet(styleSelector, styleRules, '#canvasInactiveImage', true);
  }


  /**
   * Sets background image for inactive canvas from given image object.
   *
   * @param {Image|String} image - js Image object or url string
   */
  DrawerApi.prototype.setInactiveDrawerImage = function (image) {
    if (image) {
      var valueIsUrl = typeof image === 'string' && image.length,
        valueIsImage = image instanceof Image,
        valueIsValid = valueIsUrl || valueIsImage,
        urlOfImage = valueIsUrl ? image : image.src,
        imgIsLoaded,
        imgIsValid;

      if (valueIsValid) {
        imgIsLoaded = valueIsImage && image.complete && checkImageSizes(image);
        if (imgIsLoaded) {
          _setImageAsInactiveBackground(urlOfImage);
        } else {
          util.loadImage(urlOfImage, null, null, true).then(function (imgFromPromise) {
            imgIsValid = checkImageSizes(imgFromPromise);
            if (imgIsValid) {
              _setImageAsInactiveBackground(imgFromPromise.src);
            }
          });
        }
      }
    }
  };



})(jQuery, DrawerJs.DrawerApi, DrawerJs.util);
(function ($, BaseTool, pluginsNamespace, util) {
  /**
   * Tool to add and upload background image to canvas.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {String} [options.maxImageSizeKb='5120']
   * Max size of image to upload in KB. Default is 5 MB;
   * <br><br>
   * If null, or 0 is set - then size is unlimited.
   * Negative values are considered as 0;
   *
   * @param {String} [options.imagePosition='stretch']
   * Positioning of background-image.
   * Acceptable values are : 'center', 'stretch', 'top-left', 'top-right', 'bottom-left', 'bottom-right'
   *
   * @param {String[]} [options.acceptedMIMETypes=['image/jpeg', 'image/png', 'image/gif']]
   * Array with accepted MIME file types.
   *
   * @param {String} [options.fixedBackgroundUrl]
   * Url of background to display, If set - tool button will be hidden.
   *
   * @param {Boolean} [options.dynamicRepositionImage=true]
   * If set to true - background image is dynamically smoothly resized with canvas.
   * If false - background image is resized to fit canvas after canvas was resized.
   *
   * @param {Number} [options.dynamicRepositionImageThrottle=100]
   * If options.dynamicRepositionImage is true - this is throttle time in ms for
   * resizing to be done. For example 'dynamicRepositionImageThrottleL : 200'
   * means that resizing background image will occur once in 200ms.
   * This option greatly reduces CPU load during resize of canvas with background image.
   * If set to 0 - no throttling is used, image is resized on every mouse move.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var BackgroundImageTool = function BackgroundImageToolConstructor(drawerInstance, options) {
    // call base c-tor
    BaseTool.call(this, drawerInstance);

    var _this = this;
    this.drawer = drawerInstance;
    this.name = 'BackgroundImage';
    this.btnClass = 'btn-image';
    this.faClass = 'fa-image';
    this.tooltip = drawerInstance.t('Insert a background image');

    this._setupOptions(options);

    var needsRepositionOnResize = ['stretch', 'center', 'bottom-left', 'bottom-right', 'top-right'].indexOf(this.options.imagePosition) != -1;
    // add listeners on 'drawer:resize', if we need reposition image in canvas
    if (needsRepositionOnResize) {
      if (this.options.dynamicRepositionImage) {
        var throttle = this.options.dynamicRepositionImageThrottle > 0 ? parseInt(this.options.dynamicRepositionImageThrottle) : 0;

        var listener;
        if (throttle) {
          // throttled listener - which do resize every X milliseconds, not immediately
          listener = this._throttle(this._repositionImage.bind(this), throttle);
        } else {
          // using no throttle
          listener = this._repositionImage.bind(this);
        }
        // continuously call throttled listener during resize
        this.drawerInstance.on(this.drawerInstance.EVENT_CANVAS_RESIZING, listener);
      } else {
        // call listener after canvas resize is done
        this.drawerInstance.on(this.drawerInstance.EVENT_CANVAS_STOP_RESIZE, this._repositionImage.bind(this));
      }
    }

    if (this.options.fixedBackgroundUrl) {
      this.drawerInstance.on(this.drawerInstance.EVENT_CANVAS_READY, function () {
        _this.loadImageFromUrl(_this.options.fixedBackgroundUrl, options, true);
      });
    }
  };

  // Derive BackgroundImageTool from BaseTool
  BackgroundImageTool.prototype = Object.create(BaseTool.prototype);
  BackgroundImageTool.prototype.constructor = BackgroundImageTool;

  /**
   * Default options.
   */
  BackgroundImageTool.prototype._defaultOptions = {
    cropIsActive: true,
    maxImageSizeKb: 5120, // 5 MB
    imagePosition: 'stretch',
    dynamicRepositionImage: true,
    dynamicRepositionImageThrottle: 100,
    acceptedMIMETypes: ['image/jpeg', 'image/png', 'image/gif']
  };


  /**
   * Tool activation method.
   * Is called in lifecycle of event Drawer.EVENT_DO_ACTIVATE_TOOL.
   * Calls  BaseTool._activateTool .
   *
   * @private
   */
  BackgroundImageTool.prototype._activateTool = function () {
    var _this = this;
    this.drawerInstance.log('TOOL', 'BackgroundImageTool._activateTool()');
    BaseTool.prototype._activateTool.call(this);

    this._showDialog();

    // deactivate tool. Slight delay is needed, because without it
    // tool is deactivated before listeners on EVENT_DO_ACTIVATE_TOOL in drawer are executed
    // which lead to incorrect way of setting drawer.lastUsedPluginName
    util.setTimeout(function () {
      _this.drawerInstance.trigger(_this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL, [_this]);
    }, 300);

  };


  /**
   * Shows file open dialog
   * @private
   */
  BackgroundImageTool.prototype._showDialog = function () {
    var acceptedMIMEStr = this.options.acceptedMIMETypes.join(',');
    $el = $('<input type="file" accept="' + acceptedMIMEStr + '">');
    $el.on('change', this._processFileInput.bind(this));

    $el.click();

  };

  /**
   * Callback to process user selected files.
   *
   * @param {Event} e
   * @private
   */
  BackgroundImageTool.prototype._processFileInput = function (e) {
    var _this = this;
    var files = e.target.files;

    // check there was file chosen
    if (files.length < 1) {
      _this.drawerInstance.showError(this.drawerInstance.t('No file selected.'));
      return;
    }
    var file = files[0];

    // check file
    if (!this._checkFile(file)) {
      return;
    }

    var triggerImageCrop = this.options.cropIsActive && this.drawer._pluginsInstances.ImageCrop;
    if (triggerImageCrop) {
      this.readFile(file, this._triggerImageCrop.bind(this));
    } else {
      this.readFile(file, this.loadImageFromUrl.bind(this));
    }
  };


  /**
   * Init image cropper
   *
   * @param {string} imageSrc src of image, can be base64 encoded url
   * @param {Object} [options] - options of placement
   */
  BackgroundImageTool.prototype._triggerImageCrop = function (imageSrc, options) {
    var dataToEvent = {
      url: imageSrc,
      callback: this.loadImageFromUrl.bind(this)
    };
    this.drawerInstance.trigger(this.drawerInstance.EVENT_IMAGE_CROP, dataToEvent);
  };

  /**
   * Reads file, on success - calls callback with result
   * @param {File}     file
   * @param {function} callback
   */
  BackgroundImageTool.prototype.readFile = function (file, callback) {
    var _this = this;
    var fileReader = new FileReader();
    // on file load - call callbcack, which will create HTML5 Image from it
    fileReader.onload = function (onloadEvent) {
      _this.drawerInstance.log('IMAGE LOADED:', file.name);
      callback(fileReader.result);
    };

    fileReader.readAsDataURL(file);
  };


  /**
   * Makes some checks  to file.
   *
   * @param {File} file
   * @returns {boolean}
   * @private
   */
  BackgroundImageTool.prototype._checkFile = function (file) {
    var _this = this;
    // crude check of file type
    if (file.type.indexOf('image') < 0) {
      _this.drawerInstance.showError(this.drawerInstance.t('Incorrect file type.'));
      return false;
    }

    // check for maxSize
    if ((this.options.maxImageSizeKb > 0) &&
      (file.size > this.options.maxImageSizeKb * 1024)) {
      var err = this.drawerInstance.t('File is to big!. Maximum file size is ');
      err = err + this.options.maxImageSizeKb + ' KB';
      _this.drawerInstance.showError(err);
      return false;
    }

    return true;
  };


  /**
   * Creates image from data, then calld makeImageBackground()
   *
   * @param {string} imageSrc src of image, can be base64 encoded url
   * @param {Object} [options] - options of placement
   * @param {Boolean} [throwError] - throw/ignore errors
   */
  BackgroundImageTool.prototype.loadImageFromUrl = function (imageSrc, options, throwError) {
    var _this = this;
    var image = new Image();

    // after Image was created from file imageSrc- create fabric.Image from it
    image.onload = function () {
      _this.makeImageBackground(image, options);
    };

    // show error on fail
    if (throwError) {
      image.onerror = function () {
        var err = _this.drawerInstance.t('Image failed to create!');
        _this.drawerInstance.showError(err);
      };
    }

    // this will start creating image
    image.src = imageSrc;
  };


  /**
   * Makes given image background.
   * Position of background image depends on options.
   */
  BackgroundImageTool.prototype.makeImageBackground = function (image, options) {
    var _this = this;
    var fCanvas = this.drawerInstance.fCanvas;
    var fabricImage = new fabric.Image(image);

    // if no options - use tool config options
    options = options ? options : this.options;
    // calc coords of image
    var positionOpts = this._calcPositioningOptions(fabricImage, options.imagePosition);

    fCanvas.setBackgroundImage(fabricImage, function () {
      fCanvas.renderAll();
    },
      positionOpts);
  };


  /**
   * Returns background image positioning options.
   *
   * @param  {fabric.Image} bgImage backgroundImage
   * @param  {String} imagePosition
   * @return {Object}
   */
  BackgroundImageTool.prototype._calcPositioningOptions = function (bgImage, imagePosition) {
    var fCanvas = this.drawerInstance.fCanvas;

    var opts = {};
    switch (imagePosition) {
      case 'stretch':
        opts.left = 0;
        opts.top = 0;
        opts.width = fCanvas.width;
        opts.height = fCanvas.height;
        break;
      case 'center':
        opts.originX = 'center';
        opts.originY = 'center';
        opts.left = fCanvas.getCenter().left;
        opts.top = fCanvas.getCenter().top;
        break;
      case 'top-left':
        opts.left = 0;
        opts.top = 0;
        break;
      case 'top-right':
        opts.left = fCanvas.width - bgImage.width;
        opts.top = 0;
        break;
      case 'bottom-left':
        opts.left = 0;
        opts.top = fCanvas.height - bgImage.height;
        break;
      case 'bottom-right':
        opts.left = fCanvas.width - bgImage.width;
        opts.top = fCanvas.height - bgImage.height;
        break;
      case 'default':  // unknown option, will be 'stretch'  then
        opts.left = 0;
        opts.top = 0;
        opts.width = fCanvas.width;
        opts.height = fCanvas.height;
    }

    return opts;
  };



  /**
   * If option options.imagePosition == 'stretch' || 'center' || 'top-right' || 'bottom-left' || 'bottom-right',
   * this method do reposition of background image.
   * Is called on every resize.
   *
   * @private
   */
  BackgroundImageTool.prototype._repositionImage = function () {
    var fCanvas = this.drawerInstance.fCanvas;
    if (!fCanvas.backgroundImage)
      return;

    var bgImage = fCanvas.backgroundImage;

    var opts = {};
    switch (this.options.imagePosition) {
      case 'stretch':
        opts.width = fCanvas.width;
        opts.height = fCanvas.height;
        break;
      case 'center':
        opts.originX = 'center';
        opts.originY = 'center';
        opts.left = fCanvas.getCenter().left;
        opts.top = fCanvas.getCenter().top;
        break;
      case 'top-right':
        opts.left = fCanvas.width - bgImage.width;
        opts.top = 0;
        break;
      case 'bottom-right':
        opts.left = fCanvas.width - bgImage.width;
        opts.top = fCanvas.height - bgImage.height;
        break;
      default:
        return;
    }

    bgImage.set(opts);
    bgImage.setCoords();

    fCanvas.renderAll();
  };


  /**
  *  Hides tool button, in case of fixed background.
  */
  BackgroundImageTool.prototype.createButton = function (toolbar) {
    // create no button if background is fixed
    if (!this.options.fixedBackgroundUrl) {
      BaseTool.prototype.createButton.call(this, toolbar);
    }
  };



  /**
   * Throttle function, used to throttle image resize.
   * source code : https://learn.javascript.ru/task/throttle
   *
   * @param {function} func
   * @param {Number} ms
   * @returns {function}
   * @private
   */
  BackgroundImageTool.prototype._throttle = function (func, ms) {
    var isThrottled = false,
      savedArgs,
      savedThis;

    function wrapper() {
      if (isThrottled) { // (2)
        savedArgs = arguments;
        savedThis = this;
        return;
      }


      func.apply(this, arguments); // (1)

      isThrottled = true;
      util.setTimeout(function () {
        isThrottled = false; // (3)
        if (savedArgs) {
          wrapper.apply(savedThis, savedArgs);
          savedArgs = savedThis = null;
        }
      }, ms);
    }
    return wrapper;
  };

  pluginsNamespace.BackgroundImage = BackgroundImageTool;

}(jQuery, DrawerJs.plugins.BaseTool, DrawerJs.plugins, DrawerJs.util));

(function (DrawerApi) {

  /**
   * Sets background image from given url.
   *
   * @param {String} imageUrl
   * @param {Object} placement options
   */
  DrawerApi.prototype.setBackgroundImageFromUrl = function (imageUrl, options, callback) {
    this.drawer.api.checkIsActive();
    var tool = this.drawer.getPluginInstance('BackgroundImage');
    tool.loadImageFromUrl(imageUrl, options);
  };

  /**
   * Sets background image from given image object.
   *
   * @param {Image}   image
   * @param {Object} placement options
   */
  DrawerApi.prototype.setBackgroundImage = function (image, options) {
    this.drawer.api.checkIsActive();
    var tool = this.drawer.getPluginInstance('BackgroundImage');
    tool.makeImageBackground(image, options);
  };


})(DrawerJs.DrawerApi);
(function ($, BaseBrush, pluginsNamespace, util) {
  /**
   * Provides a a simple eraser button which activates free drawing mode and
   * makes a brush with white color.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {number} options.brushSize
   * Eraser default brush size
   *
   * @param {String} options.cursorUrl
   * Custom CSS url for eraser cursor.
   *
   * Example:
   * <code><pre>url(path/to/cursor.cur), default</pre></code>
   *
   * Note the word 'default' at the end: that is the name of cursor that will
   * be used when url is unavailable.
   *
   * More information about css cursor property could be found here:
   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor}
   *
   * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor}
   *
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseBrush
   * @constructor
   */
  var SimpleWhiteEraser = function SimpleWhiteEraserPlugin(drawerInstance,
    options) {
    var _this = this;

    BaseBrush.call(_this, drawerInstance);

    this.name = 'SimpleWhiteEraser';
    this.btnClass = 'btn-simple-eraser';
    this.faClass = 'fa-eraser';
    this.tooltip = drawerInstance.t('SimpleWhiteEraser');

    _this._defaultOptions = {
      brushSize: 3,
      cursorUrl: 'eraser'
    };

    this._setupOptions(options);

    if (_this.options.cursorUrl == 'eraser') {
      var drawerFolderUrl = util.getDrawerFolderUrl();
      if (drawerFolderUrl) {
        _this.options.cursorUrl = 'url(' + drawerFolderUrl +
          'src/cursor-fa-eraser.cur), default';
      }
    }

    /**
     * Color value before eraser tool.
     * @type {String}
     */
    this.previousColor = null;

    /**
     * BrushSize before eraser tool. Used to separate eraser brush size from
     * other brushes.
     * @type {Number}
     */
    this.previousBrushSize = null;

    /**
     * Last used eraser brush size. Used to restore eraser size after another
     * brush tool.
     * @type {Number}
     */
    this.savedBrushSize = this.options.brushSize;
  };

  SimpleWhiteEraser.prototype = Object.create(BaseBrush.prototype);
  SimpleWhiteEraser.prototype.constructor = SimpleWhiteEraser;

  /**
   * This method is called in BaseBrush._activateTool()
   * Children of BaseBrush MUST implement afterActivateTool().e
   */
  SimpleWhiteEraser.prototype.afterActivateTool = function () {
    this.previousColor = this.drawerInstance.fCanvas.freeDrawingBrush.color;
    this.drawerInstance.fCanvas.freeDrawingBrush.color = '#fff';
    this.drawerInstance.fCanvas.freeDrawingBrush.fill = '#fff';
    this.drawerInstance.fCanvas.freeDrawingBrush.opacity = this.drawerInstance.activeOpacity;

    this._previousCursor = this.drawerInstance.fCanvas.freeDrawingCursor;
    this.drawerInstance.fCanvas.freeDrawingCursor = this.options.cursorUrl;

    this.previousBrushSize = this.drawerInstance.freeDrawingBrushSize;
    this.drawerInstance.setFreeDrawingBrushSize(this.savedBrushSize);
  };

  /**
   * This method is called in BaseBrush._deactivateTool()
   * Children of BaseBrush MUST implement afterDeactivateTool().
   */
  SimpleWhiteEraser.prototype.afterDeactivateTool = function () {
    this.drawerInstance.fCanvas.freeDrawingCursor = this._previousCursor;
    this.drawerInstance.fCanvas.freeDrawingBrush.color = this.previousColor;
    this.drawerInstance.fCanvas.freeDrawingBrush.fill = this.previousColor;

    this.savedBrushSize = this.drawerInstance.freeDrawingBrushSize;
    this.drawerInstance.setFreeDrawingBrushSize(this.previousBrushSize);
  };

  pluginsNamespace.SimpleWhiteEraser = SimpleWhiteEraser;

}(jQuery, DrawerJs.plugins.BaseBrush, DrawerJs.plugins, DrawerJs.util));
(function ($, brushes, BaseBrush, pluginsNamespace, util) {
  'use strict';

  var MOUSE_UP = util.mouseUp('Eraser');
  var MOUSE_DOWN = util.mouseDown('Eraser');
  var MOUSE_MOVE = util.mouseMove('Eraser');
  var MOUSE_ENTER_CURSOR = 'mouseenter.EraserCursor';
  var MOUSE_LEAVE_CURSOR = 'mouseleave.EraserCursor';

  /**
   * Provides an eraser which allows to erase any drawing/shapes.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {number} options.brushSize
   * Eraser default brush size
   *
   * @memberof DrawerJs.plugins
   *
   * @constructor
   * @augments DrawerJs.plugins.BaseBrush
   */
  var Eraser = function (drawerInstance, options) {
    var _this = this;

    BaseBrush.call(_this, drawerInstance);

    this.name = 'Eraser';
    this.btnClass = 'btn-eraser';
    this.faClass = 'fa-eraser';
    this.tooltip = drawerInstance.t('Eraser');

    this.separatePathOnShapes = true;

    _this._defaultOptions = {
      cursorUrl: 'none',
      brushSize: 3,
      useCoordsQueue: false
    };

    this._setupOptions(options);

    /**
     * List of tool options to show when tool is activated.
     * Deviating from BaseShape tool, Line has no 'color', only 'border'.
     * @type {String[]}
     */
    this.toolOptionsList = ['brushSize'];

    /**
     *
     * @type {boolean}
     */
    this.forceOptionsHide = true;

    /**
     * Color value before eraser tool.
     * @type {String}
     */
    this.previousColor = null;

    /**
     * BrushSize before eraser tool. Used to separate eraser brush size from
     * other brushes.
     * @type {Number}
     */
    this.previousBrushSize = null;

    /**
     * Last used eraser brush size. Used to restore eraser size after another
     * brush tool.
     * @type {Number}
     */
    this.savedBrushSize = this.options.brushSize;

    /**
     * Boolean flag showing if we are in erasing mode
     * @type {Boolean}
     */
    this.erasingNow = false;

    /**
     * Eraser brush
     * @type {fabric.Brush}
     */
    this.brush = null;
  };


  Eraser.prototype = Object.create(BaseBrush.prototype);
  Eraser.prototype.constructor = Eraser;


  ////// STATR/STOP TOOL METHODS ////////////////////////////////////////////////////////////////////
  /**
   * This method is called in BaseBrush._activateTool()
   * Children of BaseBrush MUST implement afterActivateTool()
   */
  Eraser.prototype.afterActivateTool = function () {
    var drw = this.drawerInstance;
    var fCanvas = drw.fCanvas;

    // set cursor
    this._previousCursor = drw.fCanvas.freeDrawingCursor;
    drw.fCanvas.freeDrawingCursor = this.options.cursorUrl;

    // set eraser brush
    this.brush = this._createBrush();
    drw.setBrush(this.brush);

    // on brush size change - update our cursor size
    drw.on(drw.EVENT_BRUSH_SIZE_CHANGED, this._updateCursorShapeSize.bind(this));

    // set all mouse handlers:
    // handle mouse move while tool is active
    $(fCanvas.upperCanvasEl).on(MOUSE_MOVE, this._onMouseMove.bind(this));

    // handle mouse down and mouse up
    $(fCanvas.upperCanvasEl).on(MOUSE_DOWN, this._onMouseDown.bind(this));
    $(fCanvas.upperCanvasEl).on(MOUSE_UP, this._onMouseUp.bind(this));

    // create cursor
    this._createEraserCursorShape();

    // handle mouse leave / enter canvas (to hide/show cursor)
    $(fCanvas.upperCanvasEl).on(MOUSE_ENTER_CURSOR, this._showEraserCursorShape.bind(this));
    $(fCanvas.upperCanvasEl).on(MOUSE_LEAVE_CURSOR, this._hideEraserCursorShape.bind(this));


    // on eraser path created - do work
    fCanvas.on('path:created', this._onEraserPathCreated.bind(this));
  };


  /**
   * After tool deactivation - remove all event handlers
   * This method is called in BaseBrush._activateTool()
   * Children of BaseBrush MUST implement afterDeactivateTool.
   */
  Eraser.prototype.afterDeactivateTool = function () {
    var fCanvas = this.drawerInstance.fCanvas;

    // switch erasing off
    this.erasingNow = false;

    fCanvas.off('path:created');

    // restore cursor
    fCanvas.freeDrawingCursor = this._previousCursor;
    // remove cursorShape
    this._removeEraserCursorShape();

    $(fCanvas.upperCanvasEl).off(MOUSE_MOVE);
    $(fCanvas.upperCanvasEl).off(MOUSE_UP);
    $(fCanvas.upperCanvasEl).off(MOUSE_DOWN);

    $(fCanvas.upperCanvasEl).off(MOUSE_ENTER_CURSOR);
    $(fCanvas.upperCanvasEl).off(MOUSE_LEAVE_CURSOR);
  };


  /**
   * Create eraser brush
   */
  Eraser.prototype._createBrush = function () {
    var brush = new brushes.EraserBrush(this.drawerInstance.fCanvas);

    brush.color = '#fff';
    brush.opacity = this.drawerInstance.activeOpacity;
    brush.width = this.options.brushSize;

    return brush;
  };


  ////// ERASER CURSOR METHODS //////////////////////////////////////////////////////////////////////
  /**
   * Create shape, that will be used as eraser cursor
   */
  Eraser.prototype._createEraserCursorShape = function () {
    // eraser shape setup
    var eraserPolyRadius = this.drawerInstance.getBrushSize() / 2;

    this.eraserCursorShape = new fabric.PCircle({
      radius: eraserPolyRadius
    });

    this.drawerInstance.fCanvas.add(this.eraserCursorShape);

    this.eraserCursorShape.isEraserBrush = true;
    this.eraserCursorShape.set('fill', 'transparent');
    this.eraserCursorShape.set('stroke', 'black');
    this.eraserCursorShape.set('strokeWidth', 1);
    this.eraserCursorShape.set('originX', 'center');
    this.eraserCursorShape.set('originY', 'center');

    this._updateCursorShapeSize();
  };


  /**
   * Remove eraser shape, that was used as eraser cursor
   */
  Eraser.prototype._removeEraserCursorShape = function () {
    if (this.eraserCursorShape) {
      this.eraserCursorShape.remove();
      delete this.eraserCursorShape;
    }
  };

  /**
   * Show eraser cursor shape
   */
  Eraser.prototype._showEraserCursorShape = function () {
    this.eraserCursorShape.set('visible', true);
    this.drawerInstance.fCanvas.renderAll();
  };

  /**
   * Hide eraser cursor shape
   */
  Eraser.prototype._hideEraserCursorShape = function () {
    this.eraserCursorShape.set('visible', false);
    this.drawerInstance.fCanvas.renderAll();
  };


  /**
   * Update our cursor shape size to be same as brush size
   */
  Eraser.prototype._updateCursorShapeSize = function () {
    if (!this.eraserCursorShape) {
      return;
    }

    var eraserPolyRadius = this.drawerInstance.getBrushSize() / 2;
    var eraserPolyDashSize = (2 * Math.PI * eraserPolyRadius) / 20;
    this.eraserCursorShape.set('radius', eraserPolyRadius);
    this.eraserCursorShape.set('strokeDashArray', [
      eraserPolyDashSize, eraserPolyDashSize
    ]);
  };



  ////// ERASER PATH METHODS //////////////////////////////////////////////////////////////////////
  /**
   * After eraser path was created - apply it to all affected shapes.
   * @param  {fabric.Event} e
   */
  Eraser.prototype._onEraserPathCreated = function (e) {
    var fCanvas = this.drawerInstance.fCanvas;

    e.path.set('eraserPath', true);
    e.path.set('visible', true);

    for (var i = 0; i < this.affectedShapes.length; i++) {
      var shape = this.affectedShapes[i];
      delete shape.eraserAffected;
      shape.addEraserPath(e.path);
    }

    fCanvas.renderAll();
  };


  /**
   * Look, which erasable objects are under eraser.
   * Add them to affectedShapes[]
   *
   * @param {number} x
   * @param {number} y
   * @private
   */
  Eraser.prototype._affectShapesUnderCoords = function (x, y) {
    var fCanvas = this.drawerInstance.fCanvas;
    var allObjects = fCanvas.getObjects();

    this.eraserCursorShape._lastCenterPosition = { x: x, y: y };
    for (var i = 0; i < allObjects.length; i++) {
      var obj = allObjects[i];
      // if object is non-erasable or if already affected by our eraser- skip it
      if (!obj.isErasable || obj.eraserAffected || obj.isEraserBrush)
        continue;

      var addPathToObject = this._checkObjectIntersection(obj, x, y);
      if (addPathToObject) {
        obj.eraserAffected = true;
        this.affectedShapes.push(obj);
      }
    }
  };

  /**
   * Check if object is intersected with eraser brush
   *
   * @param {object} obj
   * @param {number} x
   * @param {number} y
   * @private
   */

  Eraser.prototype._checkObjectIntersection = function (obj, x, y) {
    var result,
      circleObj = this.eraserCursorShape,
      rectsAreIntersects = circleObj.intersectsWithObject(obj),
      objContainsCenterOfBrush,
      objContainsPointFromPerimeter;

    if (rectsAreIntersects) {
      objContainsCenterOfBrush = !obj.canvas.isTargetTransparent(obj, x, y);
      if (!objContainsCenterOfBrush) {
        var deltaX = circleObj._lastCenterPosition ? x - circleObj._lastCenterPosition.x : undefined,
          deltaY = circleObj._lastCenterPosition ? y - circleObj._lastCenterPosition.y : undefined,
          perimeterPoints = circleObj.getPerimeterPoints(deltaX, deltaY);
        perimeterPoints.forEach(function (pointCoords, i) {
          if (!objContainsPointFromPerimeter) {
            objContainsPointFromPerimeter = !obj.canvas.isTargetTransparent(obj, pointCoords.x, pointCoords.y);
          }
        });
      }
    }
    result = objContainsCenterOfBrush || objContainsPointFromPerimeter;
    return result;
  };


  ////// MOSUE HANDLERS //////////////////////////////////////////////////////////////////////
  /**
   * Set erasingNow to true.
   * Eraser path will start by current brush, independently.
   * @param {Event} e - mouse down event
   */
  Eraser.prototype._onMouseDown = function (e) {
    var rightClick = e.which === 3,
      middleClick = e.which === 2;
    if (!rightClick && !middleClick) {
      // reset affectedShapes[]
      this.affectedShapes = [];
      // turn erasing on
      this.erasingNow = true;
    }
  };


  /**
   * On mouse up - set erasingNow to false.
   * Eraser path will be created soon, and
   * main work on erasing will be  done in _onEraserPathCreated()
   */
  Eraser.prototype._onMouseUp = function () {
    this.erasingNow = false;
    this.drawerInstance.fCanvas.renderAll();
  };

  /**
   * Listens for mouse movement when eraser is active.
   *
   * @param {Event} event
   * @private
   */
  Eraser.prototype._onMouseMove = function (event) {
    // calc mouse event coords relative to canvas
    var pointCoords = this.drawer.fCanvas.getPointer(event);
    var left = pointCoords.x;
    var top = pointCoords.y;

    // move eraser shape to make it follow mouse pointer
    if (this.eraserCursorShape) {
      // eraser shape could not be created on touch devices because there's no mouseenter events
      this.eraserCursorShape.set('left', pointCoords.x);
      this.eraserCursorShape.set('top', pointCoords.y);
      this.eraserCursorShape.setCoords();

      this.drawerInstance.fCanvas.renderAll();
    }

    if (this.erasingNow) {
      // Look, if there are erasable shapes under cursor
      if (this.separatePathOnShapes) {
        this._affectShapesUnderCoords(left, top);
      }
    }
  };



  pluginsNamespace.Eraser = Eraser;

}(
  jQuery,
  DrawerJs.brushes,
  DrawerJs.plugins.BaseBrush,
  DrawerJs.plugins,
  DrawerJs.util
));
(function (namespace) {

  /**
   * EraserBrush class.
   * The main purpose of this class is to replace created Path objects
   * with custom EraserPath so canvas could know how to render them.
   *
   * @class EraserBrush
   * @extends fabric.PencilBrush
   *
   * @memberof DrawerJs.brushes
   */
  namespace.EraserBrush = fabric.util.createClass(fabric.PencilBrush, {

    /**
     * Constructor
     * @param {fabric.Canvas} canvas
     * @return {DrawerJs.EraserBrush} Instance of a eraser brush
     */
    initialize: function (canvas) {
      this.callSuper('initialize', canvas);
    },

    /**
     * Overriding fabric.PencilBrush._render
     * Method code is exactly copy of fabric.PencilBrush._render,
     * only change is trigger events
     *
     * @see {fabric.PencilBrush._render}
     * @private
     */
    _render: function () {
      this.canvas.fire('pencil:move:before');
      this.callSuper('_render');
      this.canvas.fire('pencil:move:after');
    },

    /**
     * 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.EraserPath(pathData, {
        fill: null,
        stroke: this.color,
        strokeWidth: this.width,
        strokeLineCap: this.strokeLineCap,
        strokeLineJoin: this.strokeLineJoin,
        strokeDashArray: this.strokeDashArray,
        originX: 'center',
        originY: 'center'
      });

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

      return path;
    }
  });

})(DrawerJs.brushes);
(function (global) {
  'use strict';

  /**
   * Path created by eraser brush.
   */
  fabric.EraserPath = fabric.util.createClass(fabric.Path, {

    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'EraserPath',

    // to avoid issue in fabric.js with clipping of cached objects [bounding rect do not include brush size]
    objectCaching: false,
    evented: false,
    selectable: false,

    initialize: function (path, options) {
      options = options || {};

      this.polygonOffsetX = options.polygonOffsetX || null;
      this.polygonOffsetY = options.polygonOffsetY || null;

      this.callSuper('initialize', path, options);
    },

    /**
     * Returns string representation of an instance
     * @return {String} string representation of an instance
     */
    toString: function () {
      return '#<fabric.EraserPath (' + 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 = fabric.util.object.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;
      }

      if (this.polygonOffsetX && this.polygonOffsetY) {
        o.polygonOffsetX = this.polygonOffsetX;
        o.polygonOffsetY = this.polygonOffsetY;
      }

      return o;
    }
  });

  fabric.EraserPath.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.EraserPath(object.path, object));
    }
  };

  /**
   * Indicates that instances of this type are async
   * @static
   * @memberOf fabric.Path
   * @type Boolean
   * @default
   */
  fabric.EraserPath.async = true;

})(typeof exports !== 'undefined' ? exports : this);
(function ($, BaseBrush, pluginsNamespace, util) {
  'use strict';

  /**
   * Provides a pencil button which activates free drawing mode.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {String} options.cursorUrl
   * Custom CSS url for pencil cursor.
   *
   * 'pencil' value could be specified: in this case built-in
   * pencil cursor will be user which is located in
   * <code>assets/cursor-fa-pencil.cur</code>
   *
   * Example:
   * <code><pre>url(path/to/cursor.cur), default</pre></code>
   *
   * Note the word 'default' at the end: that is the name of cursor that will
   * be used when url is unavailable.
   *
   * More information about css cursor property could be found here:
   * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor}
   *
   * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor}
   *
   * @param {Number} options.brushSize
   * Default brush size.
   *
   * @memberof DrawerJs.plugins
   *
   * @constructor
   * @augments DrawerJs.plugins.BaseBrush
   */
  var Pencil = function (drawerInstance, options) {
    var _this = this;

    BaseBrush.call(_this, drawerInstance);

    this.name = 'Pencil';

    /**
     * List of tool options to show when tool is activated.
     * Deviating from BaseShape tool, Line has no 'color', only 'border'.
     * @type {String[]}
     */
    this.toolOptionsList = ['color', 'opacity', 'brushSize'];

    this.btnClass = 'btn-pencil';
    this.faClass = 'fa-pencil';
    this.tooltip = drawerInstance.t('Free drawing mode');

    _this._defaultOptions = {
      cursorUrl: 'pencil',
      brushSize: 2
    };

    this._setupOptions(options);

    if (_this.options.cursorUrl == 'pencil') {
      var drawerFolderUrl = util.getDrawerFolderUrl();
      if (drawerFolderUrl) {
        _this.options.cursorUrl = 'url(' + drawerFolderUrl +
          'src/cursor-fa-pencil.cur), default';
      }
    }
  };

  Pencil.prototype = Object.create(BaseBrush.prototype);
  Pencil.prototype.constructor = Pencil;

  Pencil.prototype.createBrush = function () {
    var brush = new fabric.ErasablePencilBrush(this.drawerInstance.fCanvas);

    brush.color = this.drawerInstance.activeColor;
    brush.opacity = this.drawerInstance.activeOpacity;
    brush.width = this.options.brushSize;

    return brush;
  };

  /**
   * This method is called in BaseBrush._activateTool()
   * Children of BaseBrush MUST implement afterActivateTool.
   *
   * Save previous fabricJs cursor.
   */
  Pencil.prototype.afterActivateTool = function () {
    var fCanvas = this.drawerInstance.fCanvas;

    this._previousCursor = fCanvas.freeDrawingCursor;
    fCanvas.freeDrawingCursor = this.options.cursorUrl;
  };


  /**
   * This method is called in BaseBrush._deactivateTool()
   * Children of BaseBrush MUST implement afterDeactivateTool.
   *
   * Restore previous fabricJs cursor.
   */
  Pencil.prototype.afterDeactivateTool = function () {
    var fCanvas = this.drawerInstance.fCanvas;
    fCanvas.freeDrawingCursor = this._previousCursor;
  };

  pluginsNamespace.Pencil = Pencil;

}(jQuery, DrawerJs.plugins.BaseBrush, DrawerJs.plugins, DrawerJs.util));
(function ($, pluginsNamespace) {
  'use strict';

  /**
   * Provides a modal popup for controlling canvas properties
   * like width,height, and aligment.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * @param {Object} options
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var CanvasProperties = function CanvasPropertiesConstructor(drawerInstance, options) {
    var _this = this;
    if (!drawerInstance.redactorInstance) {
      console.error("'CanvasProperties' plugin can work only in redactor mode!");
      return;
    }

    this.drawerInstance = drawerInstance;
    this.drawer = drawerInstance;
    this.name = 'DrawerPluginCanvasProperties';
    this.LOGTAG = _this.name;

    this._setupOptions(options);

    _this.drawerInstance.on(_this.drawerInstance.EVENT_CONFIG_TOOLBAR_CREATED,
      function (event, toolbar) {
        var redactorInstance = _this.drawerInstance.redactorInstance;

        var modalTemplate = '' +
          '<section id="redactor-drawer-properties">' +
          '<label class="drawer-properties-size-label">' +
          drawerInstance.t('Size (px)') +
          '</label>' +
          '<input class="drawer-properties-width" type="number" ' +
          'value="' + drawerInstance.width + '" />' +
          '<span class="size-separator">x</span>' +
          '<input class="drawer-properties-height" type="number" ' +
          'value="' + drawerInstance.height + '"/>' +
          '<label class="drawer-properties-align-label">' +
          drawerInstance.t('Position') +
          '</label>' +
          '<select class="drawer-properties-align">' +
          '<option value="inline">' +
          drawerInstance.t('Inline') +
          '</option>' +
          '<option value="left">' +
          drawerInstance.t('Left') +
          '</option>' +
          '<option value="center">' +
          drawerInstance.t('Center') +
          '</option>' +
          '<option value="right">' +
          drawerInstance.t('Right') +
          '</option>' +
          '<option value="floating">' +
          drawerInstance.t('Floating') +
          '</option>' +
          '</select>' +
          '<span class="group-transparency">' +
          '<label class="drawer-properties-background">' +
          drawerInstance.t('Background') +
          '</label>' +
          '<input type="checkbox" id="background-transparency" ' +
          'class="background-transparency"/>' +
          '<label for="background-transparency" ' +
          'class="background-transparency">' +
          drawerInstance.t('transparent') +
          '</label>' +
          '</span>' +
          '</section>';

        redactorInstance.modal.addTemplate('drawer-properties', modalTemplate);

        var clickHandler = function canvasPropsClickHandler() {
          _this.openPopup();
          return false;
        },
          buttonConfig = {
            buttonOrder: 9,
            additionalClass: 'btn-canvas-properties',
            iconClass: 'fa-cog',
            tooltipText: drawerInstance.t('Canvas properties'),
            clickHandler: clickHandler
          };
        toolbar.addButton(buttonConfig);
      });
  };

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  CanvasProperties.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  CanvasProperties.prototype.openPopup = function () {
    var _this = this;

    var redactorInstance = _this.drawerInstance.redactorInstance;
    var drawerInstance = _this.drawerInstance;
    var $image = _this.drawerInstance.$imageElement;

    redactorInstance.modal.load(
      'drawer-properties', drawerInstance.t('Canvas properties'), 260
    );

    redactorInstance.modal.createCancelButton();
    $('.redactor-modal-close-btn').html(drawerInstance.t('Cancel'));
    var button = redactorInstance.modal.createActionButton(
      drawerInstance.t('Save'));

    var $properties = $('#redactor-drawer-properties');

    var currentAlign = drawerInstance.getAlign();

    $properties.find('select').val(currentAlign);
    $properties.find('.drawer-properties-width').val(drawerInstance.width);
    $properties.find('.drawer-properties-height').val(drawerInstance.height);

    _this.currentAlign = currentAlign;
    _this.currentHeight = drawerInstance.height;
    _this.currentWidth = drawerInstance.width;

    // transparency checkbox var
    _this.$backgroundTransparencyInput =
      $properties.find('input.background-transparency');
    // set it
    _this.$backgroundTransparencyInput.attr('checked', drawerInstance.options.transparentBackground);

    // transparency checkbox click handler
    $properties.find('.background-transparency').click(function () {
      var checked = _this.$backgroundTransparencyInput.attr('checked');
      _this.$backgroundTransparencyInput.attr('checked', !checked);
    });

    button.on('click', function () {
      _this.saveProperties();
    });

    var $alignControl = $properties.find('.drawer-properties-align');

    function updateTransparencyVisible() {
      var selectedAlign = $properties.find('.drawer-properties-align').val();
      if (selectedAlign == 'floating') {
        $properties.find('.group-transparency').show();
      } else {
        $properties.find('.group-transparency').hide();
      }
    }

    updateTransparencyVisible();

    $alignControl.change(function () {
      updateTransparencyVisible();
    });

    redactorInstance.modal.show();

    // restore body overflow immediately to avoid content jumping
    // Jumping happens because with overflow:hidden page has no scrollbar
    // and its size extends by 24px
    $(document.body)
      .css('overflow', redactorInstance.modal.bodyOveflow);
  };

  CanvasProperties.prototype.saveProperties = function () {
    var _this = this;

    var redactorInstance = _this.drawerInstance.redactorInstance;
    var drawerInstance = _this.drawerInstance;

    var fullscreenPlugin =
      _this.drawerInstance.getPluginInstance('Fullscreen');

    var modal = $('#redactor-drawer-properties');
    var width = modal.find('input.drawer-properties-width').val();
    var height = modal.find('input.drawer-properties-height').val();
    var align = modal
      .find('select.drawer-properties-align option:selected')
      .val();

    if (_this.currentWidth != width || _this.currentHeight != height) {
      if (fullscreenPlugin && fullscreenPlugin.isInFullscreenMode()) {
        fullscreenPlugin.exitFullscreen();
      }

      drawerInstance.setSize(width, height);
    }

    drawerInstance.options.align = align;
    if (_this.currentAlign != align) {
      if (fullscreenPlugin && fullscreenPlugin.isInFullscreenMode()) {
        fullscreenPlugin.exitFullscreen();
      }

      drawerInstance.setAlign(align);
    }

    var transparent = _this.$backgroundTransparencyInput.attr('checked');
    drawerInstance.options.transparentBackground = !!transparent;

    redactorInstance.modal.close();
    drawerInstance.onCanvasModified();

    drawerInstance.trigger(drawerInstance.EVENT_OPTIONS_CHANGED);
  };

  pluginsNamespace.CanvasProperties = CanvasProperties;
}(jQuery, DrawerJs.plugins));
(function ($, pluginsNamespace) {
  "use strict";

  /**
   * Provides a button to destroy canvas.
   *
   * @param {DrawerJs.Drawer} drawer
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var CloseButton = function CloseButtonConstructor(drawer, options) {
    /**
     * @type {Drawer}
     */
    this.drawer = drawer;
    this.name = 'CloseButton';
    this._setupOptions(options);

    this._bindedOnToolbarCreated = this._onToolbarCreated.bind(this);
    drawer.on(drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    drawer.on(drawer.EVENT_MINIMIZED_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
  };

  CloseButton.prototype._defaultOptions = {
    buttonOrder: 1
  };

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  CloseButton.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };


  /**
   * On toolbar created - create tool button.
   */
  CloseButton.prototype._onToolbarCreated = function (ev, toolbar) {
    this.toolbar = toolbar;
    if (this.createControls) {
      this.createControls(toolbar);
    }
  };


  CloseButton.prototype.createControls = function (toolbar) {
    this._createAndAddButton(toolbar);
  };

  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listeners of toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  CloseButton.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    if (this.deleteControls) {
      this.deleteControls();
    }

    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawer.off(this.drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };





  /**
   * Creates and adds button to toolbar.
   * @param  {DrawerToolbar} toolbar
   */
  CloseButton.prototype._createAndAddButton = function (toolbar) {
    var buttonConfig = {
      buttonOrder: this.options.buttonOrder,
      additionalClass: 'btn-delete-canvas',
      iconClass: 'fa-times',
      tooltipText: this.drawer.t('Delete this canvas'),
      clickHandler: this._onCloseButtonClick.bind(this)
    };
    toolbar.addButton(buttonConfig);
  };


  /**
   * On close - ask user, then delete canvas
   */
  CloseButton.prototype._onCloseButtonClick = function () {
    var self = this;

    var question = self.drawer.t('Are you sure want to delete this canvas?');
    if (confirm(question)) {
      self.drawer.destroy();
    }
  };


  pluginsNamespace.CloseButton = CloseButton;

})(jQuery, DrawerJs.plugins, DrawerJs.util);

(function ($, BaseTool, pluginsNamespace, util) {

  var UploadImgData = function UploadImgDataConstructor(drawerInstance, options) {
    var _this = this;

    BaseTool.call(_this, drawerInstance);

    this.drawer = drawerInstance;
    this.name = 'UploadImgData';
    this.btnClass = 'btn-save';
    this.faClass = 'fa-save';
    this.tooltip = drawerInstance.t('Save');
    this._setupOptions(options);
  };

  // Derive ImageTool from BaseTool
  UploadImgData.prototype = Object.create(BaseTool.prototype);
  UploadImgData.prototype.constructor = UploadImgData;

  /**
   * Tool activation method.
   * Is called in lifecycle of event Drawer.EVENT_DO_ACTIVATE_TOOL.
   * Calls  BaseTool._activateTool .
   *
   * @private
   */
  UploadImgData.prototype._activateTool = function () {
    var _this = this;
    this.drawerInstance.log('TOOL', 'UploadImgData._activateTool()');
    BaseTool.prototype._activateTool.call(this);

    var designer = this.drawer.designer;

    if (designer != null && designer != undefined) {
      designer.save();
    }

    // deactivate tool. Slight delay is needed, because without it
    // tool is deactivated before listeners on EVENT_DO_ACTIVATE_TOOL in drawer are executed
    // which lead to incorrect way of setting drawer.lastUsedPluginName
    util.setTimeout(function () {
      _this.drawerInstance.trigger(_this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL, [_this]);
    }, 300);

  };

  pluginsNamespace.UploadImgData = UploadImgData;

}(jQuery, DrawerJs.plugins.BaseTool, DrawerJs.plugins, DrawerJs.util));


(function ($, BaseTool, pluginsNamespace, util) {

  var ClearButton = function ClearButtonConstructor(drawerInstance, options) {
    var _this = this;

    BaseTool.call(_this, drawerInstance);

    this.drawer = drawerInstance;
    this.name = 'ClearButton';
    this.btnClass = 'btn-delete-canvas';
    this.faClass = 'fa-times';
    this.tooltip = drawerInstance.t('Clean');
    this._setupOptions(options);
  };

  // Derive ImageTool from BaseTool
  ClearButton.prototype = Object.create(BaseTool.prototype);
  ClearButton.prototype.constructor = ClearButton;

  /**
   * Tool activation method.
   * Is called in lifecycle of event Drawer.EVENT_DO_ACTIVATE_TOOL.
   * Calls  BaseTool._activateTool .
   *
   * @private
   */
  ClearButton.prototype._activateTool = function () {
    var _this = this;
    this.drawerInstance.log('TOOL', 'ClearButton._activateTool()');
    BaseTool.prototype._activateTool.call(this);

    var designer = this.drawer.designer;

    if (designer != null && designer != undefined) {
      designer.clear();
    }

    // deactivate tool. Slight delay is needed, because without it
    // tool is deactivated before listeners on EVENT_DO_ACTIVATE_TOOL in drawer are executed
    // which lead to incorrect way of setting drawer.lastUsedPluginName
    util.setTimeout(function () {
      _this.drawerInstance.trigger(_this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL, [_this]);
    }, 300);

  };

  pluginsNamespace.ClearButton = ClearButton;

}(jQuery, DrawerJs.plugins.BaseTool, DrawerJs.plugins, DrawerJs.util));

(function ($, pluginsNamespace, util) {
  "use strict";

  /**
   * Provides a button to enter fullscreen mode.
   *
   * @param {DrawerJs.Drawer} drawer
   * @param {Object} options
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var Fullscreen = function FullscreenConstructor(drawer, options) {
    var _this = this;

    /**
     * @type {Drawer}
     */
    _this.drawer = drawer;
    _this.LOGTAG = _this.name;

    _this._setupOptions(options);

    _this.previousWidth = null;
    _this.previousHeight = null;
    _this.previousOffset = null;

    _this.$enterButton = null;
    _this.$exitButton = null;

    _this.onlyForEditing = false;

    // set handlers on toolbar creation
    this._bindedOnToolbarCreated = this._onToolbarCreated.bind(this);
    drawer.on(drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._bindedOnToolbarCreated);


    // befre loading
    _this.drawer
      .on(_this.drawer.EVENT_EDIT_START, function () {
        var dataBeforeFullscreen = _this.drawer
          .$imageElement.attr('data-before-fullscreen');

        if (dataBeforeFullscreen) {
          _this.$enterButton.hide();
          _this.$exitButton.show();
        } else {
          _this.$enterButton.show();
          _this.$exitButton.hide();
        }

      });

    _this.drawer.on(_this.drawer.EVENT_EDIT_STOP, function () {
      if (_this.onlyForEditing) {
        _this.exitFullscreen();
      }
    });

    $(window).on('resize', function () {
      if (_this.isInFullscreenMode()) {
        _this.adjustFullscreenSize();
      }
    });
  };

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  Fullscreen.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  /**
   * On toolbar created - create tool button.
   */
  Fullscreen.prototype._onToolbarCreated = function (ev, toolbar) {
    this.toolbar = toolbar;
    var enterButtonConfig = {
      buttonOrder: this.options.buttonOrder,
      additionalClass: 'btn-fullscreen',
      iconClass: 'fa-expand',
      tooltipText: this.drawer.t('Enter fullscreen mode'),
      clickHandler: this.enterFullscreen.bind(this)
    },
      exitButtonConfig = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-fullscreen',
        iconClass: 'fa-compress',
        tooltipText: this.drawer.t('Exit fullscreen mode'),
        clickHandler: this.exitFullscreen.bind(this)
      };

    // add button expand
    this.$enterButton = toolbar.addButton(enterButtonConfig);

    // add button shrink
    this.$exitButton = toolbar.addButton(exitButtonConfig);
    this.$exitButton.hide();
  };


  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listeners of toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  Fullscreen.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    if (this.deleteControls) {
      this.deleteControls();
    }

    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawer.off(this.drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };





  Fullscreen.prototype.isInFullscreenMode = function () {
    var $image = this.drawer.$imageElement;

    if ($image.attr('data-before-fullscreen') !== undefined) {
      return true;
    }

    return false;
  };

  Fullscreen.prototype.adjustFullscreenSize = function () {
    var _this = this;
    var redactorInstance = _this.drawer.redactorInstance;

    var $image = _this.drawer.$imageElement;

    var toolbarSize = _this.drawer.options.toolbarSize;

    var width = redactorInstance.$box.width() - toolbarSize;
    var height = redactorInstance.$box.height() - toolbarSize;

    var offset = redactorInstance.$box.offset();

    _this.drawer.$canvasEditContainer.css({
      'left': offset.left + 'px',
      'top': (offset.top + toolbarSize) + 'px',
      'width': width + 'px',
      'height': height + 'px'
    });

    _this.drawer.fCanvas.setWidth(width);
    _this.drawer.fCanvas.setHeight(height);

    if (!_this.onlyForEditing) {
      $image.css({
        'position': 'absolute',
        'left': '0px',
        'top': '0px',
        'width': width + 'px',
        'height': height + 'px'
      });
    }
  };

  Fullscreen.prototype.enterFullscreen = function () {
    var _this = this;
    var redactorInstance = _this.drawer.redactorInstance;

    var $image = _this.drawer.$imageElement;

    var toolbarSize = _this.drawer.options.toolbarSize;

    // this.drawer.trigger(this.drawer.EVENT_HIDE_TOOLTIPS);

    _this.$enterButton.hide();
    _this.$exitButton.show();

    $image.attr('data-before-fullscreen',
      JSON.stringify({
        imageCss: {
          'position': $image.css('position'),
          'display': $image.css('display'),
          'left': $image.css('float'),
          'top': $image.css('top'),
          'margin-left': $image.css('margin-left'),
          'margin-right': $image.css('margin-right'),
          'width': $image.css('width'),
          'height': $image.css('height')
        },
        canvasCss: {
          'width': _this.drawer.width,
          'height': _this.drawer.height,
          'offset': _this.drawer.$canvasEditContainer.offset()
        }
      })
    );

    if (!this.onlyForEditing) {
      $image.css('position', 'absolute');
    }

    _this.adjustFullscreenSize();

    if (this.onlyForEditing) {
      var duration = util.getTransitionDuration(
        _this.drawer.$canvasEditContainer[0]
      );

      util.setTimeout(function () {
        redactorInstance.$box.css('opacity', '0');
      }, duration);
    }

    // @todo: rework this!
    _this.drawer.toolbars.toolOptionsToolbar
      .removeClass('toolbar-bottomLeft').detach();
    _this.drawer
      .appendToolbar(_this.drawer.toolbars.toolOptionsToolbar, 'topRight');

    _this.drawer.toolbars.setToolbarButtonsSize();
    _this.drawer.onCanvasModified();
  };

  Fullscreen.prototype.exitFullscreen = function () {
    var _this = this;
    var $image = _this.drawer.$imageElement;

    $('.editable-canvas-tooltip').removeClass('active');

    _this.$enterButton.show();
    _this.$exitButton.hide();

    var dataBeforeFullscreenStr = $image.attr('data-before-fullscreen');
    var dataBeforeFullscreen = JSON.parse(dataBeforeFullscreenStr);
    $image.attr('data-before-fullscreen', null);

    if (this.onlyForEditing) {
      _this.drawer.redactorInstance.$box.css('opacity', '1');
    } else {
      $image.css(dataBeforeFullscreen.imageCss);
    }

    if (_this.drawer.$canvasEditContainer) {
      var duration = util.getTransitionDuration(
        _this.drawer.$canvasEditContainer[0]
      );

      var previousWidth = dataBeforeFullscreen.canvasCss.width;
      var previousHeight = dataBeforeFullscreen.canvasCss.height;

      _this.drawer.$canvasEditContainer.css({
        'left': dataBeforeFullscreen.canvasCss.offset.left + 'px',
        'top': dataBeforeFullscreen.canvasCss.offset.top + 'px',
        'width': previousWidth + 'px',
        'height': previousHeight + 'px'
      });

      _this.drawer.$canvasEditContainer.find('.canvas-container')
        .css({
          'width': previousWidth + 'px',
          'height': previousHeight + 'px'
        });

      util.setTimeout(function () {
        _this.drawer.fCanvas.setWidth(previousWidth);
        _this.drawer.fCanvas.setHeight(previousHeight);
      }, duration);

      _this.drawer.width = previousWidth;
      _this.drawer.height = previousHeight;

      _this.drawer.adjustEditContainer(true, true);
    }

    _this.drawer.toolbars.toolOptionsToolbar
      .removeClass('toolbar-topRight').detach();
    _this.drawer
      .appendToolbar(_this.drawer.toolbars.toolOptionsToolbar, 'bottomLeft');

    _this.drawer.toolbars.setToolbarButtonsSize();
    _this.drawer.onCanvasModified();
  };

  pluginsNamespace.Fullscreen = Fullscreen;
}(jQuery, DrawerJs.plugins, DrawerJs.util));
(function ($, pluginsNamespace, util, DrawerApi) {
  "use strict";

  var isFF = util.checkBrowser('mozilla'),
    isSafari = util.checkBrowser('safari'),
    isWebkit = util.checkBrowser('webkit'),
    emptyFunc = function () { },
    isTouchDevice;

  /**
   * Provides a button to minimize canvas.
   *
   * @param {DrawerJs.Drawer} drawer
   * @param {Object} options
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var FullscreenModeButton = function FullscreenModeButtonConstructor(drawer, options) {
    /**
     * @type {Drawer}
     */
    this.name = 'FullscreenModeButton';
    this.drawer = drawer;
    isTouchDevice = this.drawer.touchDevice;

    this._setupOptions(options);
    this._attachEvents();
    this._setupFullscreenHandlers();

    this.drawer.on(this.drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._onConfigToolbarCreated.bind(this));
  };

  /**
   * Attach global events
   * @private
   */
  FullscreenModeButton.prototype._attachEvents = function () {
    var self = this,
      eventsList = '' +
        'onfullscreenchange.FullscreenModeButton ' +
        'webkitfullscreenchange.FullscreenModeButton ' +
        'fullscreenchange.FullscreenModeButton ' +
        'mozfullscreenchange.FullscreenModeButton ' +
        'MSFullscreenChange.FullscreenModeButton ';
    $(document).off(eventsList).on(eventsList, function () {
      self.globalFullscreenMode = !self.globalFullscreenMode;
      self.reactOnResize = true;
      util.setTimeout(function () {
        self.reactOnResize = false;
      }, 2000);
      if (self.globalFullscreenMode) {
        self.setFullscreenStateOn(true);
      } else {
        self.setFullscreenStateOff(true);
      }
    });

    window.removeEventListener('resize', self._onWindowResize.bind(self));
    window.addEventListener('resize', self._onWindowResize.bind(self));

    $(document).off('keypress.FullscreenModeButton').on('keypress.FullscreenModeButton', function (e) {
      var isFullscreen = (self.globalFullscreenMode && self.fullscreenMode),
        isEscKey = e.keyCode == 27,
        turnOffFullscreen = isFullscreen && isEscKey;
      if (turnOffFullscreen) { // ESC key
        self.fullscreenOff(true);
      }
    });
  };

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  FullscreenModeButton.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  /**
   * React on window resize
   * @private
   */
  FullscreenModeButton.prototype._onWindowResize = function () {
    var self = this;

    if (isFF && !self.reactOnResize && !self.globalFullscreenMode) {
      self._saveCurrentSizes();
    }

    if (self.reactOnResize) {
      if (!self.globalFullscreenMode) {
        self._restoreNormalSizes();
      } else {
        self._refreshFullscreenSize(false);
      }
    }



    var turnOff = false && !isFF, //global, but not for firefox
      ffTurnOff = !self.fullscreenMode && self.reactOnResize && isFF && !self.globalFullscreenMode,
      webkitTurnOff = self.fullscreenMode && !self.globalFullscreenMode && isWebkit,
      needToTurnOff = turnOff || ffTurnOff || webkitTurnOff;
    if (needToTurnOff) {
      self.setFullscreenStateOff();
    }
    self.reactOnResize = false;
  };

  FullscreenModeButton.prototype._saveCurrentSizes = function (oldWidth, oldHeight) {
    if (!oldWidth || !oldHeight) {
      oldWidth = this.drawer.$canvasEditContainer.outerWidth();
      oldHeight = this.drawer.$canvasEditContainer.outerHeight();
    }

    this.savedWidth = oldWidth;
    this.savedHeight = oldHeight;
  };

  /**
   * Set drawer size - all available space
   * @param {Boolean} [save] - save current size for further restore
   * @private
   */
  FullscreenModeButton.prototype._refreshFullscreenSize = function (save) {
    var $window = $(window),
      viewportWidth = $window.width(),
      viewportHeight = $window.height(),
      oldWidth = this.drawer.width,
      oldHeight = this.drawer.height,
      $editContainer = this.drawer.$canvasEditContainer,
      drawerHaveAnimatedClass = $editContainer.hasClass('animated');

    if (save) {
      this._saveCurrentSizes(oldWidth, oldHeight);
    }
    $editContainer.removeClass('animated');
    this.drawer.setSize(
      viewportWidth,
      viewportHeight
    );
    this.drawer.trigger(this.drawer.EVENT_RESTORE_DEFAULT_ZOOM);
    $editContainer.toggleClass('animated', !!drawerHaveAnimatedClass);
  };

  /**
   * Restore drawer size
   * @private
   */
  FullscreenModeButton.prototype._restoreNormalSizes = function () {
    var self = this,
      $editContainer = this.drawer.$canvasEditContainer,
      drawerHaveAnimatedClass = $editContainer.hasClass('animated');
    $editContainer.removeClass('animated');
    $editContainer.css('position', this.savedPosition);
    console.info(this.savedWidth, this.savedHeight);
    this.drawer.setSize(
      this.savedWidth,
      this.savedHeight
    );
    util.setTimeout(function () {
      self.drawer.adjustEditContainer();
      self.drawer.trigger(self.drawer.EVENT_RESTORE_DEFAULT_ZOOM);
      util.setTimeout(function () {
        $editContainer.toggleClass('animated', !!drawerHaveAnimatedClass);
      }, 0);
    }, 0);
  };

  /**
   * Setup request/cancel handlers depending on browser
   * @private
   */
  FullscreenModeButton.prototype._setupFullscreenHandlers = function () {
    var element = document.documentElement,
      fullscreenCancelFunc,
      fullscreenRequest_defaultFunc = function () {
        element.requestFullScreen();
      },
      fullscreenRequest_default = element.requestFullScreen && fullscreenRequest_defaultFunc,

      fullscreenRequest_msFunc = function () {
        element.msRequestFullscreen();
      },
      fullscreenRequest_ms = element.msRequestFullscreen && fullscreenRequest_msFunc,
      fullscreenRequest_mozillaFunc = function () {
        element.mozRequestFullScreen();
      },
      fullscreenRequest_mozilla = element.mozRequestFullScreen && fullscreenRequest_mozillaFunc,
      fullscreenRequest_webkitFunc = function () {
        element.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
      },
      fullscreenRequest_webkit = element.webkitRequestFullScreen && fullscreenRequest_webkitFunc;

    this.fullscreenRequest = fullscreenRequest_default ||
      fullscreenRequest_ms ||
      fullscreenRequest_mozilla ||
      fullscreenRequest_webkit ||
      this.setFullscreenStateOn.bind(this);
    fullscreenCancelFunc = document.cancelFullScreen ||
      document.msExitFullscreen ||
      document.mozCancelFullScreen ||
      document.webkitCancelFullScreen ||
      this.setFullscreenStateOff.bind(this);
    this.fullscreenCancel = function () {
      fullscreenCancelFunc.call(document);
    };
  };


  /**
   * React on toolbar created - create tool button.
   * @param {fabric.Event} ev
   * @param {DrawerToolbar} toolbar
   * @private
   */
  FullscreenModeButton.prototype._onConfigToolbarCreated = function (ev, toolbar) {
    this.fullscreenEl = this.drawer.$canvasEditContainer.get(0);
    this._createAndAddButton(toolbar);
  };

  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listeners of toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} [doDeleteToolbarCreationListeners]
   */
  FullscreenModeButton.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    if (this.deleteControls) {
      this.deleteControls();
    }
    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawer.off(this.drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };


  /**
   * Creates and adds minimize button to toolbar.
   * @param  {DrawerToolbar} toolbar
   */
  FullscreenModeButton.prototype._createAndAddButton = function (toolbar) {
    var toggleModeButtonConfig = {
      buttonOrder: 10,
      additionalClass: 'btn-fullscreen-canvas',
      iconClass: 'fa-window-restore',
      tooltipText: this.drawer.t('Toggle fullscreen mode'),
      clickHandler: this._onFullscreenModeButtonClick.bind(this)
    },
      $fullscreenModeButton = toolbar.addButton(toggleModeButtonConfig);
    this.$fullscreenModeButton = $fullscreenModeButton;
  };

  /**
   * Toggle fullscreen state
   * @param {Boolean} [forceCancel]
   * @private
   */
  FullscreenModeButton.prototype._toggleFullScreen = function (forceCancel) {
    var currStateIsFullscreen_default = (document.fullScreenElement && document.fullScreenElement !== null),
      currStateIsFullscreen_moz = (!document.mozFullScreen && !document.webkitIsFullScreen),
      currStateIsFullscreen = currStateIsFullscreen_default || currStateIsFullscreen_moz,
      turnOn = forceCancel === false || (!forceCancel && currStateIsFullscreen);
    if (turnOn) {
      this.fullscreenRequest();
    } else {
      this.fullscreenCancel();
    }
  };

  /**
   * On minimize button click handler
   * @private
   */
  FullscreenModeButton.prototype._onFullscreenModeButtonClick = function () {
    if (this.fullscreenMode) {
      this.fullscreenOff();
    } else {
      this.fullscreenOn();
    }
  };

  /**
   * Drawer - remove fullscreen state
   */
  FullscreenModeButton.prototype.setFullscreenStateOff = function () {
    var canvasContainerIsValid = this.drawer.$canvasEditContainer || this.drawer.$canvasEditContainer.length,
      currStateIsValid = canvasContainerIsValid;
    this.changeStateInProgress = true;
    if (currStateIsValid) {
      this.drawer.trigger(this.drawer.EVENT_DO_DEACTIVATE_ALL_TOOLS);
      this.drawer.trigger(this.drawer.EVENT_DESTROY_TOOLTIPS);
      var self = this,
        $editContainer = this.drawer.$canvasEditContainer,
        drawerHaveAnimatedClass = $editContainer.hasClass('animated');

      this.fullscreenMode = false;
      this.drawer.fullscreenMode = false;
      $editContainer.addClass('fullscreen-in-progress');
      $editContainer.removeClass('animated');
      $editContainer.removeClass('fullscreen');

      this.drawer.$imageElement.show();
      this._restoreNormalSizes();

      util.setTimeout(function () {
        self.drawer.toolbars.resetAllToolbars();
        if (self.$fullscreenModeButton) {
          self.$fullscreenModeButton.removeClass('active');
        }
        if (isSafari) {
          self._restoreNormalSizes();
        }
        $editContainer.toggleClass('animated', !!drawerHaveAnimatedClass);
        $editContainer.removeClass('fullscreen-in-progress');
        self.changeStateInProgress = false;
      }, 0);
    }
  };

  /**
   * Drawer - set fullscreen state
   */
  FullscreenModeButton.prototype.setFullscreenStateOn = function () {
    this.changeStateInProgress = true;
    var canvasContainerIsValid = this.drawer.$canvasEditContainer || this.drawer.$canvasEditContainer.length,
      currStateIsValid = canvasContainerIsValid;
    if (currStateIsValid) {
      this.drawer.trigger(this.drawer.EVENT_DO_DEACTIVATE_ALL_TOOLS);
      this.drawer.trigger(this.drawer.EVENT_DESTROY_TOOLTIPS);
      var self = this,
        $editContainer = this.drawer.$canvasEditContainer,
        drawerHaveAnimatedClass = $editContainer.hasClass('animated');

      this.fullscreenMode = true;
      this.drawer.fullscreenMode = true;
      this.savedPosition = $editContainer.css('position');

      $editContainer.addClass('fullscreen-in-progress');
      $editContainer.removeClass('animated');
      $editContainer.addClass('fullscreen');
      this.drawer.$imageElement.hide();

      self._refreshFullscreenSize(!isFF);

      util.setTimeout(function () {
        self.drawer.toolbars.resetAllToolbars();
        self.drawer.toolbars.settingsToolbar.$toolbar.parent().addClass('fullscreenOverOther');
        if (self.$fullscreenModeButton) {
          self.$fullscreenModeButton.addClass('active');
        }
        $editContainer.toggleClass('animated', !!drawerHaveAnimatedClass);
        $editContainer.removeClass('fullscreen-in-progress');
        self.changeStateInProgress = false;
      }, 50);
    }
  };

  /**
   * Turn fullscreen mode on
   */
  FullscreenModeButton.prototype.fullscreenOn = function () {
    this._toggleFullScreen(false);
  };

  /**
   * Turn fullscreen mode off
   */
  FullscreenModeButton.prototype.fullscreenOff = function () {
    this._toggleFullScreen(true);
  };

  /**
   * Provide API method - fullscreenOn
   */
  DrawerApi.prototype.fullscreenOn = function () {
    var tool = this.drawer.getPluginInstance('FullscreenModeButton');
    tool.fullscreenOn();
  };

  /**
   * Provide API method - fullscreenOff
   */
  DrawerApi.prototype.fullscreenOff = function () {
    var tool = this.drawer.getPluginInstance('FullscreenModeButton');
    tool.fullscreenOff();
  };

  pluginsNamespace.FullscreenModeButton = FullscreenModeButton;
})(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.DrawerApi);
(function ($, pluginsNamespace, util) {
  "use strict";

  /**
   * Provides ability to use image cropper
   *
   * @param {DrawerJs.Drawer} drawer
   * @param {object} [options]
   * options
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var ImageCrop = function ImageCropConstructor(drawer, options) {
    /**
     * @type {Drawer}
     */
    this.name = 'ImageCrop';
    this.drawer = drawer;
    this._setupOptions(options);
    this.drawer.on(this.drawer.BEFORE_CREATE_TOOLBARS, this._init.bind(this));
    this.drawer.on(this.drawer.EVENT_IMAGECROP_TOOLBAR_CREATED, this._onToolbarCreated.bind(this));
    this.drawer.on(this.drawer.EVENT_IMAGE_CROP, this._onImageCropTrigger.bind(this));
  };

  ImageCrop.prototype._defaultOptions = {
    toolbarState: 'hidden'
  };
  ImageCrop.prototype._defaultCropOptions = {};

  /**
   * Init cropper - create elements and instance of crop plugin
   * @private
   */
  ImageCrop.prototype._init = function () {
    this.enabled = this.drawer.options.enableImageCrop && pluginsNamespace.ImageCropPlugin;
    if (this.enabled) {
      var cropOptions = this._setupCropOptions();
      this._removeElements();
      this._createElements();
      this.cropper = new pluginsNamespace.ImageCropPlugin(this.drawer, this.$cropperContainer, cropOptions);
    }
  };

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  ImageCrop.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  /**
   * React on create of toolbar
   * @param  {fabric.Event} fEvent
   * @param  {DrawerToolbar} toolbar
   */
  ImageCrop.prototype._onToolbarCreated = function (fEvent, toolbar) {
    this.cropContainerSizesUpdated = false;
    this.$toolbar = toolbar.$toolbar;
    this._resizeCropWrapper();
    this._createToolbarButtons(toolbar);
  };

  /**
   * Creates and adds buttons to crop toolbar
   * @param  {DrawerToolbar} toolbar
   * @private
   */
  ImageCrop.prototype._createToolbarButtons = function (toolbar) {
    var $anotherCropButton,
      $applyButton,
      $undoCropButton,
      $useOriginButton,
      $cancelButton,
      anotherCropButtonConf = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-another-crop',
        iconClass: 'fa-scissors',
        tooltipText: this.drawer.t('Crop image'),
        clickHandler: this._onAnotherCropButtonClick.bind(this)
      },
      applyButtonConf = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-apply-crop',
        iconClass: 'fa-check',
        tooltipText: this.drawer.t('Apply current image'),
        clickHandler: this._onApplyCropButtonClick.bind(this)
      },
      undoCropButtonConf = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-undo-crop',
        iconClass: 'fa-undo',
        tooltipText: this.drawer.t('Undo crop'),
        clickHandler: this._onUndoCropButtonClick.bind(this)
      },
      useOriginButtonConf = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-useorigin-crop',
        iconClass: 'fa-image',
        tooltipText: this.drawer.t('Use origin image'),
        clickHandler: this._onUseOriginButtonClick.bind(this)
      },
      cancelButtonConf = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-cancel-crop',
        iconClass: 'fa-ban',
        tooltipText: this.drawer.t('Cancel'),
        clickHandler: this._onCancelCropButtonClick.bind(this)
      };



    $anotherCropButton = toolbar.addButton(anotherCropButtonConf);
    $applyButton = toolbar.addButton(applyButtonConf);
    $undoCropButton = toolbar.addButton(undoCropButtonConf);
    // $useOriginButton = toolbar.addButton(useOriginButtonConf);
    $cancelButton = toolbar.addButton(cancelButtonConf);

    this.$anotherCropButton = $anotherCropButton;
    this.$applyButton = $applyButton;
    this.$undoCropButton = $undoCropButton;
    this.$useOriginButton = $useOriginButton;
    this.$cancelButton = $cancelButton;
  };

  /**
   * On 'another crop' button click handler
   * @private
   */
  ImageCrop.prototype._onAnotherCropButtonClick = function () {
    this.cropper.applyCrop(true);
    this.$undoCropButton.removeClass('disabled');
  };

  /**
   * On 'apply crop' button click handler
   * @private
   */
  ImageCrop.prototype._onApplyCropButtonClick = function () {
    this.cropper.applyCrop();
  };


  /**
   * On 'undo crop' button click handler
   * @private
   */
  ImageCrop.prototype._onUndoCropButtonClick = function () {
    var cropper = this.cropper,
      ableToUndo = cropper.prevImages && cropper.prevImages.length;
    if (ableToUndo) {
      cropper.undoCrop(true);
      if (!cropper.prevImages.length) {
        this.$undoCropButton.addClass('disabled');
      }
    }
  };

  /**
   * On 'use origin' button click handler
   * @private
   */
  ImageCrop.prototype._onUseOriginButtonClick = function () {
    var cropper = this.cropper;
    cropper.success(cropper.originalImage);
  };

  /**
   * On 'cancel' button click handler
   * @private
   */
  ImageCrop.prototype._onCancelCropButtonClick = function () {
    this.cropper.success();
  };

  /**
   * On 'another crop' button click handler
   * @private
   */
  ImageCrop.prototype._resizeCropWrapper = function () {
    var imageCropToolbar = this.drawer.toolbars.cropImageToolbar,
      toolbarHeight = imageCropToolbar && imageCropToolbar.$toolbar && imageCropToolbar.$toolbar.height(),
      $cropperContainer = this.cropper && this.cropper.$element;
    if ($cropperContainer) {
      $cropperContainer.css('top', toolbarHeight || 0);
    }
    if (toolbarHeight && toolbarHeight > 0) { // some webkit issue - sometimes height can be less than 0 (??)
      this.cropContainerSizesUpdated = true;
    }
  };

  /**
   * React on crop plugin activity
   * @param {Function} callback
   * @returns {Function}
   * @private
   */
  ImageCrop.prototype._getCropCallback = function (callback) {
    var self = this;
    return function (result) {
      if (callback && typeof callback === 'function') {
        callback(result);
      }
      self.hideCropper();
      self.hideCropToolbar();
      self.cropper.reset();
    };
  };

  /**
   * Init image crop
   * @param {fabric.Event} fEvent
   * @param {Object} data
   * @private
   */
  ImageCrop.prototype._onImageCropTrigger = function (fEvent, data) {
    if (this.enabled) {
      var dataIsValid = data && data.url;
      data.newImage = true;
      if (dataIsValid) {
        var callback = this._getCropCallback(data.callback);
        this.showCropToolbar();
        this.showCropper();
        this.cropper._bind(data, callback);
      }
    }
  };

  /**
   * Setup options for crop plugin
   * @param {Object} [cropOptions]
   * @private
   */
  ImageCrop.prototype._setupCropOptions = function (cropOptions) {
    var result = {};
    $.extend(true, result, this._defaultCropOptions, cropOptions || {});
    this.cropOptions = result;
  };

  /**
   * Create helper elements
   * @private
   */
  ImageCrop.prototype._createElements = function () {
    var placeCropperInsideCanvas = true,
      cropperContainerHtml = '<div class="image-crop-wrapper hidden"></div>',
      $cropperContainer = $(cropperContainerHtml),
      $container;

    if (placeCropperInsideCanvas) {
      $container = this.drawer.$canvasEditContainer;
    } else {
      $container = $('#' + this.drawerContainerID);
    }
    $container.append($cropperContainer);

    this.drawer.$cropperContainer = $cropperContainer;
    this.$cropperContainer = $cropperContainer;
  };

  /**
   * Remove helper elements
   * @private
   */
  ImageCrop.prototype._removeElements = function () {
    if (this.$cropperContainer) {
      this.$cropperContainer.remove();
      delete this.$cropperContainer;
    }

    if (this.drawer.$cropperContainer) {
      delete this.drawer.$cropperContainer;
    }
    if (this.cropper) {
      this.cropper._destroy();
      delete this.cropper;
    }
  };

  /**
   * Show crop container
   */
  ImageCrop.prototype.showCropToolbar = function () {
    this.$undoCropButton.addClass('disabled');
    this.drawer.trigger(this.drawer.EVENT_RESIZER_HIDE);
    this.drawer.trigger(this.drawer.EVENT_TOOLBAR_CHANGE_STATE, [{
      excludeElements: this.$toolbar,
      state: this.options.toolbarState,
      turnOn: true
    }]);
    this.drawer.trigger(this.drawer.EVENT_OVERCANVAS_BUTTON_HIDE, [true]);

    this.drawer.toolbars.cropImageToolbar.showToolbar();
    if (!this.cropContainerSizesUpdated) {
      this._resizeCropWrapper();
    }
  };

  /**
   * Hide crop container
   */
  ImageCrop.prototype.hideCropToolbar = function () {
    this.drawer.toolbars.cropImageToolbar.hideToolbar();
    this.drawer.trigger(this.drawer.EVENT_RESIZER_SHOW);
    this.drawer.trigger(this.drawer.EVENT_TOOLBAR_CLEAR_STATE);
    this.drawer.trigger(this.drawer.EVENT_OVERCANVAS_BUTTON_SHOW);
  };


  /**
   * Show crop container
   */
  ImageCrop.prototype.showCropper = function () {
    if (this.$cropperContainer && this.$cropperContainer.length) {
      this.$cropperContainer.removeClass('hidden');
    }
  };

  /**
   * Hide crop container
   */
  ImageCrop.prototype.hideCropper = function () {
    if (this.$cropperContainer && this.$cropperContainer.length) {
      this.$cropperContainer.addClass('hidden');
    }
  };

  pluginsNamespace.ImageCrop = ImageCrop;
})(jQuery, DrawerJs.plugins, DrawerJs.util);
(function ($, pluginsNamespace, util) {
  "use strict";

  var MOUSE_DOWN = util.mouseDown('drawerJsCrop');
  var MOUSE_UP = util.mouseUp('drawerJsCrop');
  var MOUSE_MOVE = util.mouseMove('drawerJsCrop');

  var emptyFunc = function () { },
    cssPrefixes = ['Webkit', 'Moz', 'ms'],
    emptyStyles = document.createElement('div').style,
    CSS_TRANS_ORG,
    CSS_TRANSFORM,
    CSS_USERSELECT;

  function vendorPrefix(prop) {
    if (prop in emptyStyles) {
      return prop;
    }

    var capProp = prop[0].toUpperCase() + prop.slice(1),
      i = cssPrefixes.length;

    while (i--) {
      prop = cssPrefixes[i] + capProp;
      if (prop in emptyStyles) {
        return prop;
      }
    }
  }

  CSS_TRANSFORM = vendorPrefix('transform');
  CSS_TRANS_ORG = vendorPrefix('transformOrigin');
  CSS_USERSELECT = vendorPrefix('userSelect');

  /**
   * Provides a button to minimize canvas.
   *
   * @param {DrawerJs.Drawer} drawer
   * @param {jQuery} $element
   * @param {object} [options]
   * options
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var ImageCropPlugin = function ImageCropPluginConstructor(drawer, $element, options) {
    /**
     * @type {Drawer}
     */
    this.name = 'ImageCropPlugin';
    this.drawer = drawer;
    this.$element = $element;

    this._resetData();
    this.options = $.extend(true, {}, this._defaultOptions, options || {});

    this._init();
    return this;
  };

  ImageCropPlugin.prototype._defaultOptions = {
    boundary: {},
    enableExif: false,
    minSize: 10,
    enableOrientation: false,
    update: emptyFunc,
    controlsCss: {},
    controlsTouchCss: {}
  };

  ImageCropPlugin.prototype.globals = {
    translate: 'translate3d'
  };

  /**
   *
   * @param {String|Object} bindOptions
   * @param {Function} [callback]
   * @returns {Promise}
   * @private
   */
  ImageCropPlugin.prototype._bind = function (bindOptions, callback) {
    var url,
      callbackIsValid = callback && typeof callback === 'function';

    if (typeof bindOptions === 'string') {
      this.data = {
        url: bindOptions
      };
    } else {
      this.data = $.extend(true, {}, bindOptions || {});
    }

    this._resetData();

    this.successCallback = callbackIsValid ? callback : this.successCallback || emptyFunc;

    if (this.data && this.data.newImage) {
      this.originalImage = this.data.url;
      this.prevImages = [];
    }
    this.currentImage = this.data.url;

    this.elements.$fullsizePreview.attr('src', this.data.url);
    var loadImagePromise = util.loadImage(this.data.url, this.elements.$img.get(0), this.data).then(this.startCropping.bind(this));
    return loadImagePromise;
  };

  /** Draw data and get canvas
   * @param {Object} data
   * @returns {HTMLElement}
   * @private
   */
  ImageCropPlugin.prototype._getCanvas = function (data) {
    var points = data.points,
      left = parseInt(points[0], 10),
      top = parseInt(points[1], 10),
      right = parseInt(points[2], 10),
      bottom = parseInt(points[3], 10),
      width = right - left,
      height = bottom - top,
      canvas = document.createElement('canvas'),
      ctx = canvas.getContext('2d'),
      outWidth = width,
      outHeight = height,
      startX = 0,
      startY = 0,
      canvasWidth = outWidth,
      canvasHeight = outHeight,
      customDimensions = (data.outputWidth && data.outputHeight),
      outputRatio = 1;

    if (!outputRatio && customDimensions) {
      canvasWidth = data.outputWidth;
      canvasHeight = data.outputHeight;
      outputRatio = canvasWidth / outWidth;
    }

    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    if (data.backgroundColor) {
      ctx.fillStyle = data.backgroundColor;
      ctx.fillRect(0, 0, outWidth, outHeight);
    }

    if (left < 0) {
      startX = Math.abs(left);
      left = 0;
    }
    if (top < 0) {
      startY = Math.abs(top);
      top = 0;
    }
    if (right > this.originalWidth) {
      width = this.originalWidth - left;
      outWidth = width;
    }
    if (bottom > this.originalHeight) {
      height = this.originalHeight - top;
      outHeight = height;
    }

    if (outputRatio !== 1) {
      startX *= outputRatio;
      startY *= outputRatio;
      outWidth *= outputRatio;
      outHeight *= outputRatio;
    }
    ctx.drawImage(this.elements.$fullsizePreview.get(0), left, top, width, height, startX, startY, outWidth, outHeight);
    return canvas;
  };

  /**
   *
   * @param {Object} data
   * @returns {jQuery}
   * @private
   */
  ImageCropPlugin.prototype._getHtmlResult = function (data) {
    var points = data.points,
      $div = $('<div class="croppie-result">'),
      $img = $('<img>'),
      width = points[2] - points[0],
      height = points[3] - points[1];

    $div.append($img);
    $img.css({
      left: (-1 * points[0]) + 'px',
      top: (-1 * points[1]) + 'px'
    });
    img.src = data.url;
    $div.css({
      width: width + 'px',
      height: height + 'px'
    });
    return $div;
  };

  /**
   * @param {Object} data
   * @returns {String}
   * @private
   */
  ImageCropPlugin.prototype._getBase64Result = function (data) {
    var result = this._getCanvas(data).toDataURL(data.format, data.quality);
    return result;
  };

  /**
   *
   * @param {Object} data
   * @returns {Promise}
   * @private
   */
  ImageCropPlugin.prototype._getBlobResult = function (data) {
    var self = this;
    return new Promise(function (resolve, reject) {
      self._getCanvas(data).toBlob(function (blob) {
        resolve(blob);
      }, data.format, data.quality);
    });
  };


  /**
   * Get current state of cropper
   * @return {Object} - current state data
   * @private
   */
  ImageCropPlugin.prototype._get = function () {
    var previewEl = this.elements.$img.get(0),
      viewportEl = this.elements.$viewport.get(0),
      imgData = previewEl.getBoundingClientRect(),
      vpData = viewportEl.getBoundingClientRect(),
      x1 = vpData.left - imgData.left,
      y1 = vpData.top - imgData.top,
      x2 = x1 + vpData.width,
      y2 = y1 + vpData.height,
      currZoom = (parseInt(this._currentZoom * 100) / 100) || 1;

    x1 = Math.max(0, x1 / currZoom);
    y1 = Math.max(0, y1 / currZoom);
    x2 = Math.max(0, x2 / currZoom);
    y2 = Math.max(0, y2 / currZoom);

    var p1 = parseFloat(x1).toFixed(4),
      p2 = parseFloat(y1).toFixed(4),
      p3 = parseFloat(x2).toFixed(4),
      p4 = parseFloat(y2).toFixed(4);

    return {
      points: [p1, p2, p3, p4],
      zoom: currZoom
    };
  };

  var RESULT_DEFAULTS = {
    type: 'canvas',
    format: 'png',
    quality: 1
  },
    RESULT_FORMATS = ['jpeg', 'webp', 'png'];

  /**
   * Get result of image crop
   * @param {Object} [options]
   * @returns {Promise}
   * @private
   */
  ImageCropPlugin.prototype._result = function (options) {
    var self = this,
      data = this._get(),
      opts = $.extend(true, {}, RESULT_DEFAULTS, options || {}),
      resultType = (typeof (options) === 'string' ? options : (opts.type || 'base64')),
      format = opts.format,
      quality = opts.quality,
      backgroundColor = opts.backgroundColor,
      viewport = this.elements.$viewport.get(0),
      vpRect = viewport.getBoundingClientRect(),
      prom;

    /*
    if (size === 'viewport') {
      data.outputWidth = vpRect.width;
      data.outputHeight = vpRect.height;
    } else if (typeof size === 'object') {
      if (size.width && size.height) {
        data.outputWidth = size.width;
        data.outputHeight = size.height;
      } else if (size.width) {
        data.outputWidth = size.width;
        data.outputHeight = size.width / ratio;
      } else if (size.height) {
        data.outputWidth = size.height * ratio;
        data.outputHeight = size.height;
      }
    }
    */

    if (RESULT_FORMATS.indexOf(format) > -1) {
      data.format = 'image/' + format;
      data.quality = quality;
    }

    data.url = this.data.url;
    data.backgroundColor = backgroundColor;

    prom = new Promise(function (resolve, reject) {
      switch (resultType.toLowerCase()) {
        case 'rawcanvas':
          resolve(self._getCanvas(data));
          break;
        case 'canvas':
        case 'base64':
          resolve(self._getBase64Result(data));
          break;
        case 'blob':
          self._getBlobResult(data).then(resolve);
          break;
        default:
          resolve(self._getHtmlResult(data));
          break;
      }
    });
    return prom;
  };

  /**
   * @param {Image} img
   * @param {Function} cb - callback
   */
  ImageCropPlugin.prototype.getExifOrientation = function (img, cb) {
    if (!window.EXIF) {
      cb(0);
    }

    EXIF.getData(img, function () {
      var orientation = EXIF.getTag(this, 'Orientation');
      cb(orientation);
    });
  };

  /**
   *
   * @param {HTMLCanvasElement} canvas
   * @param {Image} img
   * @param {Number} orientation
   */
  ImageCropPlugin.prototype.drawCanvas = function (canvas, img, orientation) {
    var width = img.width,
      height = img.height,
      ctx = canvas.getContext('2d');

    canvas.width = img.width;
    canvas.height = img.height;

    ctx.save();
    switch (orientation) {
      case 2:
        ctx.translate(width, 0);
        ctx.scale(-1, 1);
        break;

      case 3:
        ctx.translate(width, height);
        ctx.rotate(180 * Math.PI / 180);
        break;

      case 4:
        ctx.translate(0, height);
        ctx.scale(1, -1);
        break;

      case 5:
        canvas.width = height;
        canvas.height = width;
        ctx.rotate(90 * Math.PI / 180);
        ctx.scale(1, -1);
        break;

      case 6:
        canvas.width = height;
        canvas.height = width;
        ctx.rotate(90 * Math.PI / 180);
        ctx.translate(0, -height);
        break;

      case 7:
        canvas.width = height;
        canvas.height = width;
        ctx.rotate(-90 * Math.PI / 180);
        ctx.translate(-width, height);
        ctx.scale(1, -1);
        break;

      case 8:
        canvas.width = height;
        canvas.height = width;
        ctx.translate(0, width);
        ctx.rotate(-90 * Math.PI / 180);
        break;
    }
    ctx.drawImage(img, 0, 0, width, height);
    ctx.restore();
  };

  /**
   * @param {Number} [customOrientation]
   * @private
   */
  ImageCropPlugin.prototype._transferImageToCanvas = function (customOrientation) {
    customOrientation = this.options.enableOrientation && customOrientation;
    var $canvas = this.elements.$canvas,
      canvas = $canvas.get(0),
      img = this.elements.$img.get(0),
      ctx = canvas.get(0).getContext('2d'),
      exif = this.options.enableExif && window.EXIF;

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    canvas.width = img.width;
    canvas.height = img.height;

    if (exif) {
      this.getExifOrientation(img, function (orientation) {
        this.drawCanvas(canvas, img, parseInt(orientation, 10));
        if (customOrientation) {
          this.drawCanvas(canvas, img, customOrientation);
        }
      });
    } else {
      if (customOrientation) {
        this.drawCanvas(canvas, img, customOrientation);
      }
    }
  };

  /**
   * React on image crop
   * @param {String} [result]
   * @private
   */
  ImageCropPlugin.prototype._onCropSuccess = function (result) {
    this.success(result);
  };

  /**
   * Success callback
   * @param {String} [result]
   */
  ImageCropPlugin.prototype.success = function (result) {
    if (this.successCallback) {
      this.successCallback(result);
    }
  };

  /**
   * Reset data of cropper plugin
   * @private
   */
  ImageCropPlugin.prototype._resetData = function () {
    this.weight = 0;

    this.originalWidth = null;
    this.originalHeight = null;
    this.actualWidth = null;
    this.actualHeight = null;

    this.croppedWidth = null;
    this.croppedHeight = null;
    this.croppedLeft = null;
    this.croppedTop = null;
  };

  /**
   * Destroy instance of image crop plugin
   * @private
   */
  ImageCropPlugin.prototype._destroy = function () {

  };

  /**
   * Update size values of cropper elements
   * @private
   */
  ImageCropPlugin.prototype._updateViewportSizes = function () {
    var currentSizes = this.elements.$img.get(0).getBoundingClientRect(),
      parentCurrentSizes = this.elements.$img.parent().get(0).getBoundingClientRect(),
      offsetLeft = currentSizes && parentCurrentSizes && (currentSizes.left - parentCurrentSizes.left),
      offsetTop = currentSizes && parentCurrentSizes && (currentSizes.top - parentCurrentSizes.top),
      maxAvailableWidth = (currentSizes && currentSizes.width) || this.originalWidth,
      maxAvailableHeight = (currentSizes && currentSizes.height) || this.originalHeight;

    this.actualWidth = maxAvailableWidth;
    this.actualHeight = maxAvailableHeight;
    this.offsetLeft = offsetLeft;
    this.offsetTop = offsetTop;
    this._currentZoom = parseInt(this.actualWidth / this.originalWidth * 100) / 100;
  };

  /**
   * Update viewport position
   * @param {String} [styles] - style attribute
   * @private
   */
  ImageCropPlugin.prototype._refreshViewportPosition = function (styles) {
    this._updateViewportSizes();
    var stylesForControls = {
      'width': this.croppedWidth ? this.croppedWidth : this.actualWidth,
      'height': this.croppedHeight ? this.croppedHeight : this.actualHeight,
      'max-width': this.actualWidth,
      'max-height': this.actualHeight,
      'left': this.croppedLeft ? this.croppedLeft : this.offsetLeft || 0,
      'top': this.croppedTop ? this.croppedTop : this.offsetTop || 0
    };
    if (styles) {
      this.elements.$viewport.attr('style', styles);
    } else {
      this.elements.$viewport.css(stylesForControls);
    }
  };

  /**
   * Restore prev state of cropper
   */
  ImageCropPlugin.prototype.undoCrop = function () {
    var imageToUndo = this.prevImages.pop();
    if (imageToUndo) {
      this._bind(imageToUndo);
    }
  };

  /**
   * Create cropper elements
   * @private
   */
  ImageCropPlugin.prototype._setupCropElements = function () {
    var controlsBlockHtml,
      assetsUrl = util.getDrawerFolderUrl() + 'src/',
      cursorStyleAttr = 'style="cursor: url(' + assetsUrl + 'cursor-fa-crop-right.cur), crosshair"',
      borderStyleAttr = 'style="background: url(' + assetsUrl + 'border.gif);"';

    controlsBlockHtml = '' +
      '<div class="imager-crop-container">' +
      '<div class="crop-corner crop-top-left"></div>' +
      '<div class="crop-corner crop-top-right"></div>' +
      '<div class="crop-corner crop-bottom-right"></div>' +
      '<div class="crop-corner crop-bottom-left"></div>' +
      '<div class="crop-border crop-border-top" ' + borderStyleAttr + '></div>' +
      '<div class="crop-border crop-border-right" ' + borderStyleAttr + '></div>' +
      '<div class="crop-border crop-border-bottom" ' + borderStyleAttr + '></div>' +
      '<div class="crop-border crop-border-left" ' + borderStyleAttr + '></div>' +
      '</div>';

    this.elements = this.elements || {};
    this.elements.$viewport = $(controlsBlockHtml);

    this.elements.$img = $('<img class="cr-image">');
    this.elements.$fullsizePreview = $('<img class="cr-image-fullsize">');
    this.elements.$overlay = $('<div class="cr-overlay">');
    this.elements.$preview = this.elements.$img;

    this.$element.append(this.elements.$fullsizePreview);
    this.$element.append(this.elements.$preview);
    this.$element.append(this.elements.$overlay);
    this.$element.append(this.elements.$viewport);
  };

  /**
   * Init cropper
   * @private
   */
  ImageCropPlugin.prototype._init = function () {
    this._setupCropElements();
  };

  /**
   * Save current state of cropper
   * @private
   */
  ImageCropPlugin.prototype._save = function () {

    this.prevImages.push({
      viewportStyleAttr: this.elements.$viewport.attr('style'),
      points: this._get(),
      url: this.currentImage
    });
  };

  /**
   * Process crop with current settings
   * @param {Boolean} [replaceCurrentImageWithResult]
   */
  ImageCropPlugin.prototype.applyCrop = function (replaceCurrentImageWithResult) {
    var self = this;
    self._result().then(function (result) {
      if (replaceCurrentImageWithResult) {
        self._save();
        self._bind(result);
      } else {
        self._onCropSuccess(result);
      }
    });
  };

  /**
   * @param {Image} img
   */
  ImageCropPlugin.prototype.startCropping = function (img) {
    var _this = this;

    img.style.opacity = 1;

    _this.enableRendering = false;
    _this.renderCropped = false;

    var $body = $('body');

    _this.originalWidth = img.naturalWidth;
    _this.originalHeight = img.naturalHeight;

    _this.makePreview();
    _this._refreshViewportPosition(_this.data && _this.data.viewportStyleAttr);
    _this.viewportStyleAttr = false;

    var $corners = _this.elements.$viewport.find('.crop-corner');

    if (_this.drawer.touchDevice) {
      $corners.css(_this.options.controlsTouchCss);
    } else {
      $corners.css(_this.options.controlsCss);
    }

    $corners.on(MOUSE_DOWN, function (clickEvent) {
      var controlItem = this,
        controlItemSize = this.getBoundingClientRect().width / 2;

      _this.drawer.croppingNow = true;

      var startPos = util.getEventPosition(clickEvent);

      var viewportSizeBox = _this.elements.$viewport.get(0).getBoundingClientRect(),
        parentSizeBox = _this.elements.$viewport.parent().get(0).getBoundingClientRect(),
        startControlsLeft = viewportSizeBox.left - parentSizeBox.left,
        startControlsTop = viewportSizeBox.top - parentSizeBox.top,
        startControlsWidth = viewportSizeBox.width,
        startControlsHeight = viewportSizeBox.height;

      var isTopLeft = $(controlItem).hasClass('crop-top-left'),
        isTopRight = $(controlItem).hasClass('crop-top-right'),
        isBottomLeft = $(controlItem).hasClass('crop-bottom-left'),
        isBottomRight = $(controlItem).hasClass('crop-bottom-right');

      $body.on(MOUSE_MOVE, function (moveEvent) {
        var movePos = util.getEventPosition(moveEvent);

        var diffLeft = movePos.left - startPos.left;
        var diffTop = movePos.top - startPos.top;

        if (isTopLeft) {
          _this.croppedLeft = startControlsLeft + diffLeft;
          _this.croppedTop = startControlsTop + diffTop;

          _this.croppedWidth = startControlsWidth - diffLeft;
          _this.croppedHeight = startControlsHeight - diffTop;
        }

        if (isTopRight) {
          _this.croppedLeft = startControlsLeft;
          _this.croppedTop = startControlsTop + diffTop;

          _this.croppedWidth = startControlsWidth - (diffLeft * -1);
          _this.croppedHeight = startControlsHeight - diffTop;
        }

        if (isBottomRight) {
          _this.croppedLeft = startControlsLeft;
          _this.croppedTop = startControlsTop;

          _this.croppedWidth = startControlsWidth - (diffLeft * -1);
          _this.croppedHeight = startControlsHeight + diffTop;
        }

        if (isBottomLeft) {
          _this.croppedLeft = startControlsLeft + diffLeft;
          _this.croppedTop = startControlsTop;

          _this.croppedWidth = startControlsWidth - diffLeft;
          _this.croppedHeight = startControlsHeight + diffTop;
        }

        // bounds validation
        if (_this.croppedLeft < _this.offsetLeft) {
          _this.croppedLeft = _this.offsetLeft;
        }

        if ((_this.croppedLeft + controlItemSize) > (_this.offsetLeft + _this.actualWidth)) {
          _this.croppedLeft = _this.offsetLeft + _this.actualWidth - _this.options.minSize;
          _this.croppedWidth = _this.options.minSize;
        }

        if (_this.croppedTop < _this.offsetTop) {
          _this.croppedTop = _this.offsetTop;
        }

        if ((_this.croppedTop + controlItemSize) > (_this.offsetTop + _this.actualHeight)) {
          _this.croppedTop = _this.offsetTop + _this.actualHeight - _this.options.minSize;
          _this.croppedHeight = _this.options.minSize;
        }

        if (_this.croppedLeft + _this.croppedWidth > (_this.actualWidth + _this.offsetLeft)) {
          _this.croppedWidth = _this.actualWidth - _this.croppedLeft + _this.offsetLeft;
        }

        if (_this.croppedTop + _this.croppedHeight > (_this.actualHeight + _this.offsetTop)) {
          _this.croppedHeight = _this.actualHeight - _this.croppedTop + _this.offsetTop;
        }

        _this.elements.$viewport.css({
          left: _this.croppedLeft,
          top: _this.croppedTop,
          width: _this.croppedWidth,
          height: _this.croppedHeight
        });

        moveEvent.preventDefault();
        moveEvent.stopPropagation();
        return false;
      });

      $body.on(MOUSE_UP, function () {
        util.setTimeout(function () {
          _this.drawer.croppingNow = false;
        }, 50);
        $body.off(MOUSE_MOVE);
        $body.off(MOUSE_UP);
      });
    });
  };

  /**
   * Create preview element
   * @param {Number} originalWidth
   * @param {Number} originalHeight
   */
  ImageCropPlugin.prototype.makePreview = function (originalWidth, originalHeight) {
    var _this = this;

    _this.$preview = $('' +
      '<div class="imager-crop-preview-container">' +
      '<canvas class="imager-crop-preview"></canvas>' +
      '</div>').css('position', 'absolute').css('top', '50px').css({
        width: originalWidth,
        height: originalHeight,
        position: 'absolute',
        right: '50px',
        top: '50px'
      });

    _this.previewCanvas = _this.$preview.find('canvas.imager-crop-preview')[0];
    _this.previewCanvas.__previewCanvas = true;

    _this.previewCanvas.width = originalWidth * 1.5;
    _this.previewCanvas.height = originalHeight * 1.5;

    $(_this.previewCanvas).css({
      height: '400px'
    });

    _this.$element.append(this.$preview);
  };

  /**
   * Reset data
   * @private
   */
  ImageCropPlugin.prototype.reset = function () {
    this.croppedLeft = null;
    this.croppedTop = null;
    this.croppedWidth = null;
    this.croppedHeight = null;

    this.sizeBeforeCrop = null;
  };

  /* CSS Transform Prototype */
  var TRANSLATE_OPTS = {
    'translate3d': {
      suffix: ', 0px'
    },
    'translate': {
      suffix: ''
    }
  };
  var Transform = function (x, y, scale) {
    this.x = parseFloat(x);
    this.y = parseFloat(y);
    this.scale = parseFloat(scale);
  };

  Transform.parse = function (v) {
    if (v) {
      var isJquery = v && v instanceof jQuery && v.length,
        isNode = v instanceof Node,
        node = isJquery ? v.get(0) : (isNode && v),
        nodeStyle = node && node.style;
      if (nodeStyle) {
        return Transform.parse(nodeStyle[CSS_TRANSFORM]);
      } else {
        if (v.indexOf('matrix') > -1 || v.indexOf('none') > -1) {
          return Transform.fromMatrix(v);
        } else {
          return Transform.fromString(v);
        }
      }
    }
  };

  Transform.fromMatrix = function (v) {
    var vals = v.substring(7).split(',');
    if (!vals.length || v === 'none') {
      vals = [1, 0, 0, 1, 0, 0];
    }
    var xVal = parseInt(vals[4], 10),
      yVal = parseInt(vals[5], 10),
      scaleVal = parseFloat(vals[0]);

    return new Transform(xVal, yVal, scaleVal);
  };

  Transform.fromString = function (v) {
    var values = v.split(') '),
      translate = values[0].substring(ImageCropPlugin.prototype.globals.translate.length + 1).split(','),
      scale = values.length > 1 ? values[1].substring(6) : 1,
      x = translate.length > 1 ? translate[0] : 0,
      y = translate.length > 1 ? translate[1] : 0;

    return new Transform(x, y, scale);
  };

  Transform.prototype.toString = function () {
    var suffix = TRANSLATE_OPTS[ImageCropPlugin.prototype.globals.translate].suffix || '',
      prefix = ImageCropPlugin.prototype.globals.translate,
      style = '(' + this.x + 'px, ' + this.y + 'px' + suffix + ') scale(' + this.scale + ')',
      result = prefix + style;

    return result;
  };

  var TransformOrigin = function (el) {
    var elIsJquery = el instanceof jQuery;
    el = elIsJquery ? el.get(0) : el;
    if (!el || !el.style[CSS_TRANS_ORG]) {
      this.x = 0;
      this.y = 0;
      return;
    }
    var css = el.style[CSS_TRANS_ORG].split(' ');
    this.x = parseFloat(css[0]);
    this.y = parseFloat(css[1]);
  };

  TransformOrigin.prototype.toString = function () {
    return this.x + 'px ' + this.y + 'px';
  };
  /***********/
  pluginsNamespace.ImageCropPlugin = ImageCropPlugin;
})(jQuery, DrawerJs.plugins, DrawerJs.util);
(function ($, pluginsNamespace, util, DrawerApi) {
  "use strict";

  /**
   * Provides a button to minimize canvas.
   *
   * @param {DrawerJs.Drawer} drawer
   * @param {Object} options
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var MinimizeButton = function MinimizeButtonConstructor(drawer, options) {
    /**
     * @type {Drawer}
     */
    this.name = 'MinimizeButton';
    this.drawer = drawer;
    this._setupOptions(options);

    drawer.on(drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._onConfigToolbarCreated.bind(this));
    drawer.on(drawer.EVENT_MINIMIZED_TOOLBAR_CREATED, this._onMinimizedToolbarCreated.bind(this));
  };


  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  MinimizeButton.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  /**
   * On toolbar created - create tool button.
   */
  MinimizeButton.prototype._onConfigToolbarCreated = function (ev, toolbar) {
    this._createAndAddButton_minimize(toolbar);
  };

  /**
   * On toolbar created - create tool button.
   */
  MinimizeButton.prototype._onMinimizedToolbarCreated = function (ev, toolbar) {
    var captionValue = this.drawer.options.captionText,
      captionItem = $('<li class="canvas-caption">' + captionValue + '</li>');
    toolbar.$toolbar.find('.toolbar-content-wrapper').prepend(captionItem);
    this._createAndAddButton_restore(toolbar);
  };

  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listeners of toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  MinimizeButton.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    if (this.deleteControls) {
      this.deleteControls();
    }
    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawer.off(this.drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };

  /**
   * Turn on minimized mode
   */
  MinimizeButton.prototype.minimizeCanvas = function () {
    var $editContainer = this.drawer.$canvasEditContainer,
      drawerHaveAnimatedClass = $editContainer.hasClass('animated');
    $editContainer.removeClass('animated');
    $editContainer.addClass('minimized');
    util.setTimeout(function () {
      $editContainer.toggleClass('animated', !!drawerHaveAnimatedClass);
    }, 0);
  };

  /**
   * Turn off minimized mode
   */
  MinimizeButton.prototype.restoreCanvas = function () {
    var $editContainer = this.drawer.$canvasEditContainer,
      drawerHaveAnimatedClass = $editContainer.hasClass('animated');
    $editContainer.removeClass('animated');
    $editContainer.removeClass('minimized');
    util.setTimeout(function () {
      $editContainer.toggleClass('animated', !!drawerHaveAnimatedClass);
    }, 0);
  };

  /**
   * Creates and adds restore button to toolbar.
   * @param  {DrawerToolbar} toolbar
   */
  MinimizeButton.prototype._createAndAddButton_restore = function (toolbar) {
    var restoreButtonConf = {
      buttonOrder: this.options.buttonOrder,
      additionalClass: 'btn-restore-canvas',
      iconClass: 'fa-expand',
      tooltipText: this.drawer.t('Restore canvas'),
      clickHandler: this._onRestoreSizeButtonClick.bind(this)
    },
      $restoreButton = toolbar.addButton(restoreButtonConf);
    this.$restoreButton = $restoreButton;
  };

  /**
   * Creates and adds minimize button to toolbar.
   * @param  {DrawerToolbar} toolbar
   */
  MinimizeButton.prototype._createAndAddButton_minimize = function (toolbar) {
    var minimizeButtonConf = {
      buttonOrder: this.options.buttonOrder,
      additionalClass: 'btn-minimize-canvas',
      iconClass: 'fa-compress',
      tooltipText: this.drawer.t('Minimize canvas'),
      clickHandler: this._onMinimizeButtonClick.bind(this)
    },
      $minimizeButton = toolbar.addButton(minimizeButtonConf);
    this.$minimizeButton = $minimizeButton;
  };


  /**
   * On minimize button click handler
   */
  MinimizeButton.prototype._onMinimizeButtonClick = function () {
    this.minimizeCanvas();
  };

  /**
   * On minimize button click handler
   */
  MinimizeButton.prototype._onRestoreSizeButtonClick = function () {
    this.restoreCanvas();
  };

  /**
   * Provide API method - minimizeCanvas
   */
  DrawerApi.prototype.minimizeCanvas = function () {
    this.drawer.api.checkIsActive();
    var tool = this.drawer.getPluginInstance('MinimizeButton');
    tool.minimizeCanvas();
  };

  /**
   * Provide API method - restoreCanvas
   */
  DrawerApi.prototype.restoreCanvas = function () {
    this.drawer.api.checkIsActive();
    var tool = this.drawer.getPluginInstance('MinimizeButton');
    tool.restoreCanvas();
  };

  pluginsNamespace.MinimizeButton = MinimizeButton;
})(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.DrawerApi);
(function ($, pluginsNamespace, /*BaseToolOptions,*/ util) {
  "use strict";
  var MOUSE_DOWN = util.mouseDown('MovableFloatingMode');
  var MOUSE_MOVE = util.mouseMove('MovableFloatingMode');
  var MOUSE_UP = util.mouseUp('MovableFloatingMode');


  /**
   * Allows moving of canvas when it is in 'floating' mode.
   *
   * @param {DrawerJs.Drawer} drawer
   * @param {Object} options
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var MovableFloatingMode = function MovableFloatingModePlugin(drawer, options) {
    this.drawer = drawer;
    this._setupOptions(options);

    // set handlers
    this._bindedOnToolbarCreated = this._onToolbarCreated.bind(this);
    drawer.on(drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    drawer.on(drawer.EVENT_MINIMIZED_TOOLBAR_CREATED, this._bindedOnToolbarCreated);

    // if options.align is set and not 'floating' - plugin will not work, warn user about this
    if (drawer.options.align && (drawer.options.align !== 'floating')) {
      console.warn("MovableFloatingMode plugin: options.align is set and it is not 'floating', so canvas will not move. Please use MovableFloatingMode plugin with 'floating' align");
    }

    // if align is not set - make it 'floating'
    if (!drawer.options.align) {
      drawer.options.align = 'floating';
    }

    this.drawer.on(this.drawer.EVENT_CANVAS_READY, this.onOptionsChanged.bind(this));
    this.drawer.on(this.drawer.EVENT_OPTIONS_CHANGED, this.onOptionsChanged.bind(this));
  };

  // MovableFloatingMode.prototype = Object.create(BaseToolOptions.prototype);
  // MovableFloatingMode.prototype.constructor = BaseToolOptions;

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  MovableFloatingMode.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };


  /**
   * On toolbar created - create tool button.
   */
  MovableFloatingMode.prototype._onToolbarCreated = function (ev, toolbar) {
    this.toolbar = toolbar;
    this.createControls(toolbar);
  };



  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listeners of toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  MovableFloatingMode.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    if (this.deleteControls) {
      this.deleteControls();
    }

    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawer.off(this.drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
      this.drawer.off(this.drawer.EVENT_MINIMIZED_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };



  /**
   * Is called from Base
   * @param  {DrawerToolbar} toolbar
   */
  MovableFloatingMode.prototype.createControls = function (toolbar) {
    this.makeMoveButton(toolbar);
  };

  MovableFloatingMode.prototype.makeMoveButton = function (toolbar) {
    // create move button
    var moveButtonConf = {
      buttonOrder: this.options.buttonOrder,
      additionalClass: 'btn-move',
      iconClass: 'fa-arrows',
      tooltipText: this.drawer.t('Move canvas')
    },
      $moveButton = toolbar.addButton(moveButtonConf);
    this.$moveButton = $moveButton;

    // set handler
    this.$moveButton.on(MOUSE_DOWN, this.buttonOnMouseDown.bind(this));
  };


  /**
   * Handler of mouse down on button.
   * @param  {MouseEvent} event [description]
   */
  MovableFloatingMode.prototype.buttonOnMouseDown = function (event) {
    if (event.type.indexOf('touch') > -1) {
      event = event.originalEvent;
    }

    $('.tooltip-btn-move').css('display', 'none');

    // save coords of drawer
    this.startLeft = this.drawer.$imageElement
      .css('left').replace('px', '') | 0;
    this.startTop = this.drawer.$imageElement
      .css('top').replace('px', '') | 0;

    // save coords of click
    this.mouseStartLeft = event.pageX;
    this.mouseStartTop = event.pageY;

    // set handler for mouse move
    var $body = $('body'),
      $document = $(window.document);
    $body.addClass('drawer-moving');
    $document.on(MOUSE_MOVE, this.buttonOnMouseMove.bind(this));
    // set handler for mouse up
    $document.on(MOUSE_UP, this.buttonOnMouseUp.bind(this));

    return false;
  };


  /**
   * Handler of mouse movement, when mouse button is down.
   * @param  {Event} moveEvent        [description]
   * @return {Boolean}                returns false, to stop event propagation
   */
  MovableFloatingMode.prototype.buttonOnMouseMove = function (moveEvent) {
    var self = this,
      moveEventPos = util.getEventPosition(moveEvent),
      moveDrawerFunc = function moveDrawerFunc() {
        self.moveDrawer(moveEventPos.left, moveEventPos.top);
      };

    util.requestAnimationFrame(moveDrawerFunc);

    moveEvent.preventDefault();
    moveEvent.stopPropagation();
    return false;
  };


  /**
   * Mouse up handler.
   * @param  {MouseEvent} event
   * @return {Boolean}    returns false, to stop event propagation
   */
  MovableFloatingMode.prototype.buttonOnMouseUp = function (event) {
    $('.tooltip-btn-move').css('display', 'block');

    var $body = $('body'),
      $document = $(window.document);
    $body.removeClass('drawer-moving');
    $document.off(MOUSE_MOVE);
    $document.off(MOUSE_UP);

    return false;
  };


  /**
   * Moves drawer according to mouse movement.
   * x, y here are coords of mouse pointer.
   *
   * @param  {integer} x x coord of mouse pointer
   * @param  {integer} y y coord of mouse pointer
   */
  MovableFloatingMode.prototype.moveDrawer = function (x, y) {
    // calc offset of mouse pointer from click position
    var diffLeft = x - this.mouseStartLeft;
    var diffTop = y - this.mouseStartTop;

    // new coords of drawer
    var newLeft = this.startLeft + diffLeft;
    var newTop = this.startTop + diffTop;
    newLeft = (newLeft < 0) ? 0 : newLeft;
    newTop = (newTop < 0) ? 0 : newTop;

    // change drawer position
    var drawer = this.drawer;
    drawer.$imageElement.css({
      left: newLeft,
      top: newTop
    });

    drawer.updateAligmentCss();
    drawer.adjustEditContainer(false, true);
  };

  /**
   * Shows/hide move button, depending on current align mode.
   */
  MovableFloatingMode.prototype.onOptionsChanged = function () {
    // try to get fullscreen plugin
    var fullScreenPlugin;
    try {
      fullScreenPlugin = this.drawer.getPluginInstance('Fullscreen');
    }
    catch (e) {
      // do nothing
    }

    // see, if we are in fullscreen mode
    var isInFullscreen = fullScreenPlugin && fullScreenPlugin.isInFullscreenMode();

    // show button if no fullscreen mode and align is 'floating'
    if (!isInFullscreen && (this.drawer.options.align == 'floating')) {
      this.showMoveButton();
    } else {
      this.hideMoveButton();
    }
  };


  /**
   * Shows plugin button.
   */
  MovableFloatingMode.prototype.showMoveButton = function () {
    if (!this.$moveButton) {
      console.warn('MovableFloatingMode.removeMoveButton() : no button \'Move\' present');
      return;
    }
    this.$moveButton.show();
  };

  /**
   * Hides plugin button.
   */
  MovableFloatingMode.prototype.hideMoveButton = function () {
    if (!this.$moveButton) {
      console.warn('MovableFloatingMode.removeMoveButton() : no button \'Move\' present');
      return;
    }
    this.$moveButton.hide();
  };


  pluginsNamespace.MovableFloatingMode = MovableFloatingMode;
}(jQuery, DrawerJs.plugins, /*DrawerJs.plugins.BaseToolOptions,*/ DrawerJs.util));
(function ($, pluginsNamespace, util) {
  "use strict";

  var emptyFunc = function () { };

  var MOUSE_UP = util.mouseUp('OpenPopupButton');
  var MOUSE_DOWN = util.mouseDown('OpenPopupButton');
  var MOUSE_MOVE = util.mouseMove('OpenPopupButton');

  /**
   * Provides a button to open popup
   *
   * @param {DrawerJs.Drawer} drawer
   * @param {Object} options
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var OpenPopupButton = function OpenPopupButtonConstructor(drawer, options) {
    /**
     * @type {Drawer}
     */
    this.drawer = drawer;
    this._setupOptions(options);

    drawer.on(drawer.EVENT_OPTIONS_TOOLBAR_CREATED, this._onToolbarCreated.bind(this));
    drawer.on(drawer.EVENT_FLOATING_TOOLBAR_CREATED, this._init.bind(this));
  };

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  OpenPopupButton.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  /**
   * Init
   * @private
   */
  OpenPopupButton.prototype._init = function (ev, toolbar) {
    if (this.needToInitButton) {
      this.createControls(toolbar);

      this.drawer.off(this.drawer.EVENT_OBJECT_SELECTED, this._onObjectSelected.bind(this));
      this.drawer.on(this.drawer.EVENT_OBJECT_SELECTED, this._onObjectSelected.bind(this));

      this.drawer.off(this.drawer.EVENT_SELECTION_CLEARED, this._onSelectionCleared.bind(this));
      this.drawer.on(this.drawer.EVENT_SELECTION_CLEARED, this._onSelectionCleared.bind(this));

      this.drawer.off(this.drawer.EVENT_DO_ACTIVATE_TOOL, this._onActivateTool.bind(this));
      this.drawer.on(this.drawer.EVENT_DO_ACTIVATE_TOOL, this._onActivateTool.bind(this));

      this.drawer.off(this.drawer.EVENT_DO_DEACTIVATE_TOOL, this._onDeactivateTool.bind(this));
      this.drawer.on(this.drawer.EVENT_DO_DEACTIVATE_TOOL, this._onDeactivateTool.bind(this));

      this.drawer.off(this.drawer.EVENT_DO_DEACTIVATE_ALL_TOOLS, this._onDeactivateAllTools.bind(this));
      this.drawer.on(this.drawer.EVENT_DO_DEACTIVATE_ALL_TOOLS, this._onDeactivateAllTools.bind(this));

      this.drawer.off(this.drawer.EVENT_CANVAS_START_RESIZE, this.hideButton.bind(this));
      this.drawer.on(this.drawer.EVENT_CANVAS_START_RESIZE, this.hideButton.bind(this));

      this.drawer.off(this.drawer.EVENT_CANVAS_STOP_RESIZE, this.refreshPosition.bind(this));
      this.drawer.on(this.drawer.EVENT_CANVAS_STOP_RESIZE, this.refreshPosition.bind(this));

      this.drawer.off(this.drawer.EVENT_OVERCANVAS_BUTTON_HIDE, this.hideButton.bind(this));
      this.drawer.on(this.drawer.EVENT_OVERCANVAS_BUTTON_HIDE, this.hideButton.bind(this));

      this.drawer.off(this.drawer.EVENT_OVERCANVAS_BUTTON_SHOW, this.showButton.bind(this));
      this.drawer.on(this.drawer.EVENT_OVERCANVAS_BUTTON_SHOW, this.showButton.bind(this));

      this.drawer.off(this.drawer.EVENT_IMAGE_CROP, this.hideButton.bind(this));
      this.drawer.on(this.drawer.EVENT_IMAGE_CROP, this.hideButton.bind(this));
    }
  };

  /**
   * React on toolbar created
   * @private
   */
  OpenPopupButton.prototype._onToolbarCreated = function (ev, toolbar) {
    this.toolbar = toolbar;

    var toolOptionsToolbarHavePopupPosition = toolbar.options.compactType === toolbar.POPUP,
      toolbarIsHidden = toolbar.options.hidden,
      needToInitButton = toolOptionsToolbarHavePopupPosition && !toolbarIsHidden;

    this.needToInitButton = needToInitButton;
  };

  /**
   * React on object selected
   * @private
   */

  OpenPopupButton.prototype._onObjectSelected = function (event) {
    this.showButton();
  };


  /**
   * React on tool activation
   * @private
   */

  OpenPopupButton.prototype._onActivateTool = function (event) {
    this.showButton();
  };

  /**
   * React on tool deactivation
   * @private
   */

  OpenPopupButton.prototype._onDeactivateTool = function (event) {
    this.hideButton();
  };

  /**
   * React on tools deactivation
   * @private
   */

  OpenPopupButton.prototype._onDeactivateAllTools = function (event, dataFromEvent) {
    var isBeforeActivateTool = dataFromEvent && dataFromEvent.beforeActivateTool,
      needToHide = !isBeforeActivateTool;
    if (needToHide) {
      this.hideButton();
    }
  };

  /**
   * React on selection change
   * @private
   */

  OpenPopupButton.prototype._onSelectionCleared = function (event, fabricEvent) {
    var needToHideButton = !this.drawer.activeDrawingTool;
    if (needToHideButton) {
      this.hideButton();
    }
  };

  /**
   * @param {DrawerToolbar} toolbar
   * @private
   */
  OpenPopupButton.prototype.createControls = function (toolbar) {
    this._createAndAddButton(toolbar);
    this._setInitialPosition();
    this.showButton();
  };

  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listeners of toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  OpenPopupButton.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    if (this.deleteControls) {
      this.deleteControls();
    }

    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawer.off(this.drawer.EVENT_FLOATING_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };

  /**
   * Restore initial position of button
   * @private
   */
  OpenPopupButton.prototype._setInitialPosition = function () {
    var offsetLeft = 0,
      offsetTop = 0,
      insidePlaceholders = this.drawer.toolbars.toolbarPlaceholders.inside,
      $insideTopPlaceholderEl = insidePlaceholders && insidePlaceholders.top && insidePlaceholders.top.$element,
      $insideLeftPlaceholderEl = insidePlaceholders && insidePlaceholders.left && insidePlaceholders.left.$element;
    if ($insideTopPlaceholderEl && $insideTopPlaceholderEl.length) {
      $insideTopPlaceholderEl.find('ul.editable-canvas-toolbar:visible').each(function (i, element) {
        var $toolbar = $(element);
        offsetTop += $toolbar.height();
      });
    }
    if ($insideLeftPlaceholderEl && $insideLeftPlaceholderEl.length) {
      $insideLeftPlaceholderEl.find('ul.editable-canvas-toolbar:visible').each(function (i, element) {
        var $toolbar = $(element);
        offsetLeft += $toolbar.width();
      });
    }
    this.positionInitialized = true;
    this.moveButton(offsetLeft, offsetTop);
  };

  /**
   * Move button over canvas
   * @param {Number} [left] - left offset of button
   * @param {Number} [top] - topoffset of button
   */
  OpenPopupButton.prototype.moveButton = function (left, top) {
    if (!this.positionLimit) {
      this.refreshPositionLimits();
    }
    left = left > this.positionLimit.left ? this.positionLimit.left : left;
    top = top > this.positionLimit.top ? this.positionLimit.top : top;

    left = left < 0 ? 0 : left;
    top = top < 0 ? 0 : top;


    this.latestState = {
      left: left || 0,
      top: top || 0
    };
    this.$button[0].style.left = left + 'px';
    this.$button[0].style.top = top + 'px';
  };


  /**
   * Refresh current position of button
   */
  OpenPopupButton.prototype.refreshPosition = function () {
    var buttonLeft = (this.latestState && this.latestState.left) || 0,
      buttonTop = (this.latestState && this.latestState.top) || 0;
    this.showButton();
    this.refreshPositionLimits();
    this.moveButton(buttonLeft, buttonTop);

  };

  /**
   * Creates and adds button to toolbar.
   * @param  {DrawerToolbar} toolbar
   * @private
   */
  OpenPopupButton.prototype._createAndAddButton = function (toolbar) {
    var $button,
      $body = $('body'),
      openPopupButtonConf = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-popup-canvas hidden',
        iconClass: 'fa-cogs',
        tooltipText: this.drawer.t('Open options tools')
      };
    $button = toolbar.addButton(openPopupButtonConf);
    this.$button = $button;

    var buttonLeft = (this.latestState && this.latestState.left) || 0,
      buttonTop = (this.latestState && this.latestState.top) || 0;

    this.moveButton(buttonLeft, buttonTop);
    // set all mouse handlers:
    // handle mouse move while tool is active
    $body.off(MOUSE_MOVE).on(MOUSE_MOVE, this._onMouseMove.bind(this));

    // handle mouse down and mouse up
    $button.off(MOUSE_DOWN).on(MOUSE_DOWN, this._onMouseDown.bind(this));
    $body.off(MOUSE_UP).on(MOUSE_UP, this._onMouseUp.bind(this));
  };

  /**
   * React on mouse down
   * Set erasingNow to true.
   * Eraser path will start by current brush, independently.
   * @param {Event} event - mouse down event
   * @private
   */
  OpenPopupButton.prototype._onMouseDown = function (event) {
    // turn erasing on
    var self = this;
    this.moveNow = true;
    this.triggerClick = true;
    this.clickPosition = util.getEventPosition(event);

    this.refreshSizes();
    this.refreshPositionLimits();

    util.setTimeout(function () {
      self.triggerClick = false;
    }, 200);
  };


  /**
   * On mouse up - set erasingNow to false.
   * Eraser path will be created soon, and
   * main work on erasing will be  done in _onEraserPathCreated()
   * @param {Event} event
   * @private
   */
  OpenPopupButton.prototype._onMouseUp = function (event) {
    this.moveNow = false;
    this.dragNow = false;
    this.clickPosition = {};
    var isButton = event.target === this.$button.get(0) || this.$button.find(event.target).length;
    this.$button.removeClass('dragging');
    if (isButton && this.triggerClick) {
      this.drawer.trigger(this.drawer.EVENT_OVERCANVAS_POPUP_SHOW, [this.$button]);
    }
  };

  /**
   * Listens for mouse movement when eraser is active.
   *
   * @param {Event} event
   * @private
   */
  OpenPopupButton.prototype._onMouseMove = function (event) {
    if (this.moveNow) {
      var eventPosition = util.getEventPosition(event),
        sameLeft = this.clickPosition && (this.clickPosition.left === eventPosition.left),
        sameTop = this.clickPosition && (this.clickPosition.top === eventPosition.top),
        cursorIsMoved = !sameLeft || !sameTop;
      if (cursorIsMoved) {
        if (!this.dragNow) {
          this.$button.addClass('dragging');
          this.dragNow = true;
        }
        this.latestSizes = this.latestSizes || this.refreshSizes();
        var eventPos = util.getEventPosition(event),
          latestSizes = this.latestSizes,
          canvasSize = latestSizes.canvas;

        var left = eventPos.left - canvasSize.left - latestSizes.button.width / 2 - latestSizes.scroll.left;
        var top = eventPos.top - canvasSize.top - latestSizes.button.height / 2 - latestSizes.scroll.top;

        this.triggerClick = false;
        this.moveButton(left, top);

        event.preventDefault();
        event.stopPropagation();
      }
    }
  };


  /**
   * Refresh max sizes of button offset
   */
  OpenPopupButton.prototype.refreshSizes = function () {
    var result = {};

    var fCanvas = this.drawer.fCanvas,
      canvasSizeBox = fCanvas.upperCanvasEl.getBoundingClientRect(),
      buttonSizeBox = this.$button[0].getBoundingClientRect();
    result.canvas = canvasSizeBox;
    result.button = buttonSizeBox;
    result.scroll = util.getScrollTopFromElement(this.$button);
    this.latestSizes = result;
    return result;
  };

  /**
   * Refresh max sizes of button offset
   */
  OpenPopupButton.prototype.refreshPositionLimits = function () {
    var buttonSize = this.$button.get(0).getBoundingClientRect(),
      borderSize = 2;
    this.positionLimit = {
      left: this.drawer.width - buttonSize.width - borderSize,
      top: this.drawer.height - buttonSize.height - borderSize,
    };
  };

  /**
   * Show popup button
   */
  OpenPopupButton.prototype.showButton = function () {
    this.$button.removeClass('hidden');
  };

  /**
   * Hide popup button
   * @param {fabric.Event} [fEvent] - fabric event
   * @param {Boolean} [force] - force to hide button ignoring all options
   */
  OpenPopupButton.prototype.hideButton = function (fEvent, force) {
    var alwaysVisible = this.drawer.options.toolbars.popupButtonAlwaysVisible;
    if (force || !alwaysVisible) {
      this.$button.addClass('hidden');
    }
  };

  pluginsNamespace.OpenPopupButton = OpenPopupButton;
})(jQuery, DrawerJs.plugins, DrawerJs.util);
(function ($, pluginsNamespace, util) {
  /**
   * Provides ability to use popup over canvas
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var OvercanvasPopup = function ResizeConstructor(drawerInstance) {
    this.drawerInstance = drawerInstance;
    this.LOGTAG = this.name;

    this._attachDrawerEvents();
  };

  /**
   * Instance of Drawer
   * @type {Drawer}
   */
  OvercanvasPopup.prototype.drawerInstance = null;

  /**
   * Namespace for events
   * @const
   * @type {String}
   */
  OvercanvasPopup.prototype.namespace = 'OvercanvasPopup';

  /**
   * Name of plugin
   * @const
   * @type {String}
   */
  OvercanvasPopup.prototype.name = 'DrawerPluginOvercanvasPopup';

  /**
   * Additional class of popup-wrapper element
   * @type {String}
   */
  OvercanvasPopup.prototype.popupClass = 'popup-wrapper';
  /**
   * Default position of popup
   * @type {string}
   */
  OvercanvasPopup.prototype.defaultPosition = 'bottom';
  /**
   * Array of available positions. Note that order is necessary.
   * @type {String[]}
   */
  OvercanvasPopup.prototype.positions = ['bottom', 'left', 'right', 'top'];

  /**
   * Toolbar behavior
   * @type {string}
   */
  OvercanvasPopup.prototype.toolbarBehavior = 'overlay';

  /**
   * Attach drawer events
   * @private
   */
  OvercanvasPopup.prototype._attachDrawerEvents = function () {
    this.drawerInstance.on(this.drawerInstance.BEFORE_CREATE_TOOLBARS, this._onBeforeCreateToolbars.bind(this));
    this.drawerInstance.on(this.drawerInstance.EVENT_OVERCANVAS_POPUP_SHOW, this.showPopup.bind(this));
    this.drawerInstance.on(this.drawerInstance.EVENT_OVERCANVAS_POPUP_HIDE, this.hidePopup.bind(this));
  };

  /**
   * React on event - before toolbars create
   * @private
   */
  OvercanvasPopup.prototype._onBeforeCreateToolbars = function () {
    this._createHelperElements();
    this._attachHelperEvents();
  };

  /**
   * Add event handlers for popup and helpers elements
   * @private
   */
  OvercanvasPopup.prototype._attachHelperEvents = function () {
    var $closeBtn = this.$popup.find('.popup-close-btn'),
      $overlay = this.$popup.find('.popup-overlay'),
      $body = $('body');
    util.bindClick($closeBtn, this.namespace, this._triggerPopupHide.bind(this));
    util.bindClick($overlay, this.namespace, this._triggerPopupHide.bind(this));

    util.bindClick($body, this.namespace, this._onBodyClick.bind(this));
  };

  /**
   * Throw "hide overcanvas popup" event
   * @private
   */
  OvercanvasPopup.prototype._triggerPopupHide = function () {
    this.drawerInstance.trigger(this.drawerInstance.EVENT_OVERCANVAS_POPUP_HIDE);
  };

  /**
   * Throw "show overcanvas popup" event
   * @private
   */
  OvercanvasPopup.prototype._triggerPopupShow = function () {
    this.drawerInstance.trigger(this.drawerInstance.EVENT_OVERCANVAS_POPUP_SHOW);
  };

  /**
   * Close popup on click outside drawer
   * @param event
   * @private
   */
  OvercanvasPopup.prototype._onBodyClick = function (event) {
    if (this.popupIsVisible) {
      var $buttonExists = this.$button && this.$button.length,
        isButton = $buttonExists && (event.target === this.$button.get(0) || this.$button.find(event.target).length),
        isPopup = event.target === this.$popup.get(0) || this.$popup.find(event.target).length;
      if (!isButton && !isPopup) {
        this.drawerInstance.trigger(this.drawerInstance.EVENT_OVERCANVAS_POPUP_HIDE);
      }
    }
  };
  /**
   * Create helper elements
   * @private
   */
  OvercanvasPopup.prototype._createHelperElements = function () {
    this._removeHelperElements();

    var popupWrapperHtml = '' +
      '<div class="' + this.popupClass + ' hidden">' +
      '<div class="popup-content-wrapper">' +
      '<div class="popup-arrow"></div>' +
      '<div class="popup-content"></div>' +
      '</div>' +
      '<div class="popup-overlay"></div>' +
      '<div class="popup-close-btn">' +
      '<div class="fa fa-close"></div>' +
      '</div>' +
      '</div>';

    var $popupWrapper = $(popupWrapperHtml),
      container = this.drawerInstance.$canvasEditContainer;

    container.append($popupWrapper);

    this.drawerInstance.$popupWrapper = $popupWrapper;
    this.$popup = $popupWrapper;
    this.$arrow = $popupWrapper.find('.popup-arrow');
    this.$popupContentWrapper = $popupWrapper.find('.popup-content-wrapper');
  };

  /**
   * Remove helper elements
   * @private
   */
  OvercanvasPopup.prototype._removeHelperElements = function () {
    if (this.drawerInstance.$popupWrapper && this.drawerInstance.$popupWrapper.length) {
      this.drawerInstance.$popupWrapper.remove();
    }
    var $popupElement = this.drawerInstance.$canvasEditContainer.find('.' + this.popupClass);
    if ($popupElement && $popupElement.length) {
      $popupElement.remove();
    }
    this.drawerInstance.$popupWrapper = undefined;
    this.$popup = undefined;
  };

  /**
   * Refresh size values
   * @param {jQuery} $trigger - Trigger element
   * @returns {Object|undefined}
   * @private
   */
  OvercanvasPopup.prototype._getAvailableSpace = function ($trigger) {
    var result;
    if ($trigger && $trigger.length) {
      var $canvas = this.drawerInstance.$canvasEditContainer,
        canvasSizes = $canvas.get(0).getBoundingClientRect(),
        triggerSizes = $trigger.get(0).getBoundingClientRect(),
        popupSizes = this.$popupContentWrapper.get(0).getBoundingClientRect(),
        // arrowSize = this.$arrow.width(),
        arrowSize = 10,
        triggerOffsetX = triggerSizes.left - canvasSizes.left,
        triggerOffsetY = triggerSizes.top - canvasSizes.top;
      result = {};

      result.top = triggerOffsetY;
      result.left = triggerOffsetX;
      result.right = canvasSizes.width - triggerOffsetX - triggerSizes.width;
      result.bottom = canvasSizes.height - triggerOffsetY - triggerSizes.height;

      result.centerX = triggerOffsetX + triggerSizes.width / 2;
      result.centerY = triggerOffsetY + triggerSizes.height / 2;

      result.popup = {
        arrowSize: arrowSize,
        top: popupSizes.height,
        bottom: popupSizes.height,
        left: popupSizes.width,
        right: popupSizes.width
      };

      result.popupSizes = popupSizes;
      result.canvasSizes = canvasSizes;
      result.triggerSizes = triggerSizes;
    }
    this.sizes = result;
    return result;
  };

  /**
   * Move popup to position
   * @param {String} position - Position of popup - top/bottom/left/right
   * @private
   */
  OvercanvasPopup.prototype._movePopup = function (position) {
    var sizes = this.sizes,
      isVerticalAlign,
      styles = {},
      offsetLeft,
      offsetTop,
      arrowOffsetLeft = sizes.popupSizes.width / 2,
      arrowOffsetTop = sizes.popupSizes.height / 2,
      negativeDiff;

    switch (position) {
      case 'left':
        offsetLeft = sizes.left - sizes.popupSizes.width - sizes.popup.arrowSize;
        offsetTop = sizes.centerY - sizes.popupSizes.height / 2;
        break;
      case 'right':
        offsetLeft = sizes.canvasSizes.width - sizes.right + sizes.popup.arrowSize;
        offsetTop = sizes.centerY - sizes.popupSizes.height / 2;
        break;
      case 'top':
        isVerticalAlign = true;
        offsetLeft = sizes.centerX - sizes.popupSizes.width / 2;
        offsetTop = sizes.top - sizes.popupSizes.height - sizes.popup.arrowSize;
        break;
      case 'bottom':
        isVerticalAlign = true;
        offsetLeft = sizes.centerX - sizes.popupSizes.width / 2;
        offsetTop = sizes.canvasSizes.height - sizes.bottom + sizes.popup.arrowSize;
        break;
    }

    if (isVerticalAlign) {
      styles.top = offsetTop;
      negativeDiff = (offsetLeft + sizes.popupSizes.width) - sizes.canvasSizes.width;
      if (negativeDiff > 0) {
        arrowOffsetLeft += negativeDiff;
        styles.right = 0;
      } else {
        if (offsetLeft < 0) {
          arrowOffsetLeft += offsetLeft;
          offsetLeft = 0;
        }
        styles.left = offsetLeft;
      }
    } else {
      styles.left = offsetLeft;
      negativeDiff = (offsetTop + sizes.popupSizes.height) - sizes.canvasSizes.height;
      if (negativeDiff > 0) {
        arrowOffsetTop += negativeDiff;
        styles.bottom = 0;
      } else {
        if (offsetTop < 0) {
          arrowOffsetTop += offsetTop;
          offsetTop = 0;
        }
        styles.top = offsetTop;
      }
    }

    this.$popupContentWrapper.attr('data-position', position);
    this.$popupContentWrapper.removeAttr('style');
    this.$popupContentWrapper.css(styles);
    this._moveArrow(arrowOffsetLeft, arrowOffsetTop);
  };


  /**
   * Move helper element - arrow
   * @param {Number} left - Absolute position in px
   * @param {Number} top - Absolute position in px
   * @private
   */
  OvercanvasPopup.prototype._moveArrow = function (left, top) {
    this.$arrow.css({
      left: left,
      top: top
    });
  };

  /**
   * Adjust position of popup according of trigger element
   * @param {jQuery} $element
   */
  OvercanvasPopup.prototype._adjustPosition = function ($element) {
    var sizes = this._getAvailableSpace($element),
      positionFound;
    this.positions.forEach(function (position, i) {
      var positionExists = sizes[position] && sizes.popup[position];
      if (!positionFound && positionExists) {
        var neededSpace = sizes.popup[position] + sizes.popup.arrowSize,
          spaceIsEnough = (sizes[position] - neededSpace) > 0;
        positionFound = spaceIsEnough && position;
      }
    });
    if (positionFound) {
      this._movePopup(positionFound);
    }
  };

  /**
   * Show popup
   * @param {fabric.Event} [fEvent]
   * @param {jQuery} $trigger
   */
  OvercanvasPopup.prototype.showPopup = function (fEvent, $trigger) {
    this.popupIsVisible = true;
    this.$popup.removeClass('hidden');
    this._adjustPosition($trigger);
    this.$popupContentWrapper.removeClass('popup-transparent');
    this.drawerInstance.trigger(this.drawerInstance.EVENT_TOOLBAR_CHANGE_STATE, [{
      excludeElements: $().add(this.$popup).add($trigger),
      turnOn: true,
      state: this.toolbarBehavior
    }]);
  };

  /**
   * Hide popup
   */
  OvercanvasPopup.prototype.hidePopup = function () {
    this.drawerInstance.trigger(this.drawerInstance.EVENT_TOOLBAR_CLEAR_STATE);
    this.popupIsVisible = false;
    this.$popup.addClass('hidden');
    this.$popupContentWrapper.addClass('popup-transparent');
  };

  pluginsNamespace.OvercanvasPopup = OvercanvasPopup;
}(jQuery, DrawerJs.plugins, DrawerJs.util));
(function ($, pluginsNamespace, util) {
  /**
   * Provides a control for canvas resizing.
   * Supports both mouse/touch.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var ResizeTool = function ResizeConstructor(drawerInstance) {
    var self = this;

    this.drawerInstance = drawerInstance;
    this.name = 'DrawerPluginResize';
    this.LOGTAG = this.name;

    this.MOUSEDOWN = util.mouseDown('drawerPluginResize');
    this.MOUSEUP = util.mouseUp('drawerPluginResize');
    this.MOUSEMOVE = util.mouseMove('drawerPluginResize');

    // on toolbar create
    this._bindedOnToolbarCreated = this._onToolbarCreated.bind(this);
    drawerInstance.on(this.drawerInstance.EVENT_CONFIG_TOOLBAR_CREATED, this._bindedOnToolbarCreated);

    drawerInstance.on(this.drawerInstance.EVENT_RESIZER_HIDE, this.resizerHide.bind(this));
    drawerInstance.on(this.drawerInstance.EVENT_RESIZER_SHOW, this.resizerShow.bind(this));
  };

  /**
   * On toolbar created - create tool button.
   */
  ResizeTool.prototype._onToolbarCreated = function () {
    this.createControls();
  };


  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listeners of toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  ResizeTool.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    if (this.deleteControls) {
      this.deleteControls();
    }

    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawerInstance.off(this.drawerInstance.EVENT_CONFIG_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };


  ResizeTool.prototype.createControls = function () {
    this.deleteControls();
    this.appendResizeControl();
  };


  ResizeTool.prototype.deleteControls = function () {
    if (this.$canvasResizer && this.$canvasResizer.length) {
      this.$canvasResizer.remove();
    }
  };



  ResizeTool.prototype.appendResizeControl = function () {
    var _this = this;

    var $document = $(window.document),
      $body = $('body'),
      $elemForEvent = $document; // cache body elem for performance

    var $editContainer = _this.drawerInstance.$canvasEditContainer;

    this.$canvasResizer = $('<span class="redactor-drawer-resizer">' +
      '<span class="resizer-box"></span></span>');

    if (this.drawerInstance.touchDevice) {
      this.$canvasResizer.find('.resizer-box').addClass('touch');
    }

    $editContainer.append(this.$canvasResizer);

    this.$canvasResizer.find('.resizer-box')
      .on(this.MOUSEDOWN, function (event) {

        var eventPos = util.getEventPosition(event);

        _this.drawerInstance.log(_this.LOGTAG, 'resize start');
        _this.resizeOriginalWidth = _this.drawerInstance.width;
        _this.resizeOriginalHeight = _this.drawerInstance.height;
        _this.resizeStartX = eventPos.left;
        _this.resizeStartY = eventPos.top;

        var $body = $('body');
        $body.addClass('drawer-resizing');

        _this.drawerInstance.trigger(_this.drawerInstance.EVENT_CANVAS_START_RESIZE);
        _this.drawerInstance.resizingNow = true;

        $elemForEvent.on(_this.MOUSEMOVE, function (moveEvent) {
          moveEvent.stopPropagation();
          moveEvent.preventDefault();

          var moveEventPos = util.getEventPosition(moveEvent);

          var xDiff = _this.resizeStartX - moveEventPos.left;
          var yDiff = _this.resizeStartY - moveEventPos.top;

          var resizeDrawerFunc = function resizeDrawerFunc() {
            _this.drawerInstance.setSize(
              _this.resizeOriginalWidth - xDiff,
              _this.resizeOriginalHeight - yDiff
            );

            _this.drawerInstance.trigger(_this.drawerInstance.EVENT_CANVAS_RESIZING);
          };

          util.requestAnimationFrame(resizeDrawerFunc);
          return false;
        });

        // register global mouseUp handler so no matter where user
        // will release a button we should receive that event
        // and finish resizing.
        $elemForEvent.on(_this.MOUSEUP, function (upEvent) {
          $body.removeClass('drawer-resizing');

          $elemForEvent.off(_this.MOUSEMOVE);
          $elemForEvent.off(_this.MOUSEUP); // clean up this handler
          _this.drawerInstance.log(_this.LOGTAG, 'resize finished by mouseup');
          _this.resizeFinished();
        });

        // Also it's good to intercept mouse leaving from editor area
        // $body.on('mouseleave.canvasResizer', function () {
        //     $body.off(_this.MOUSEMOVE);
        //     $body.off('mouseleave.canvasResizer');

        //     _this.drawerInstance.log(_this.LOGTAG,
        //       'resize finished leaving redactor\' area.');

        //     _this.resizeFinished();
        // });

      });
  };

  /**
   * Hide resize elements
   */
  ResizeTool.prototype.resizerHide = function () {
    if (this.$canvasResizer && this.$canvasResizer.length) {
      this.$canvasResizer.addClass('hidden');
    }
  };

  /**
   * Show resize elements
   */
  ResizeTool.prototype.resizerShow = function () {
    if (this.$canvasResizer && this.$canvasResizer.length) {
      this.$canvasResizer.removeClass('hidden');
    }
  };


  ResizeTool.prototype.resizeFinished = function () {
    this.drawerInstance.onCanvasModified();
    this.drawerInstance.resizingNow = false;

    console.log('[resize]', 'EVENT_CANVAS_STOP_RESIZE');
    this.drawerInstance.trigger(this.drawerInstance.EVENT_CANVAS_STOP_RESIZE);
  };

  pluginsNamespace.Resize = ResizeTool;
}(jQuery, DrawerJs.plugins, DrawerJs.util));
(function (DrawerApi) {

  /**
   * Show context menu.
   */
  DrawerApi.prototype.contextMenuShow = function () {
    this.drawer.api.checkIsActive();
    var tool = this.drawer.getPluginInstance('ShapeContextMenu');
    tool.showContextMenu();
  };

  /**
   * Show context menu.
   */
  DrawerApi.prototype.contextMenuHide = function () {
    this.drawer.api.checkIsActive();
    var tool = this.drawer.getPluginInstance('ShapeContextMenu');
    tool.hideContextMenu();
  };


  /**
   * Set Context menu left top origin.
   * @param {number} left
   * @param {number} top
   * @param {boolean} doFitInViewport if true - inca case menu is out of viewport, it's coordinates will be adjusted
   */
  DrawerApi.prototype.contextMenuSetPosition = function (left, top, doFitInViewport) {
    this.drawer.api.checkIsActive();
    var tool = this.drawer.getPluginInstance('ShapeContextMenu');

    if (doFitInViewport) {
      var newCoords = tool._calcCoordsToFitViewport(left, top);
      left = newCoords.left;
      top = newCoords.top;
    }

    tool.setMenuPosition(left, top);
  };


})(DrawerJs.DrawerApi);

(function ($, pluginsNamespace, util) {
  var MOUSE_DOWN = util.mouseDown('ShapeContextMenu');
  /**
   *
   * Provides context menu for moving shapes to background-foreground and
   * remove them.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {Object} [options.position.mouse=cursor]
   * Defines placement of context menu when working on non-touch screen.
   * <br><br>
   * Valid values are: <code>cursor</code>, <code>shapeRightBottom</code>,.
   *
   * @param {Object} [options.position.touch=shapeRightBottom]
   * Defines placement of context menu when working on touch screen.
   * <br><br>
   *
   * @param {Function} [options.customFitViewportMethod]
   * Custom function to calc coords to fit menu in viewport.
   * Arguments : (left, top)
   * Returns :  Object with keys {left, top}
   * <br><br>
   *
   * @param {Function} [options.customMenuRenderer]
   * Custom function to render context menu.
   * Arguments : ()
   * Returns :  Object with keys {left, top}
   * <br><br>
   *
   * Valid values are: <code>cursor</code>, <code>shapeRightBottom</code>.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var ShapeContextMenu = function ShapeContextMenuConstructor(drawerInstance, options) {
    var _this = this;
    _this.drawerInstance = drawerInstance;

    this.options = $.extend(true, {}, this._defaultOptions || {}, options || {});
    this.left = this.top = 0;

    this._bindedOnContextMenu = this._onContextMenu.bind(this);

    // using here EVENT_OPTIONS_TOOLBAR_CREATED just to make tool responsive to tools reload,
    // tool by itself does not belong to any toolbar
    this._bindedSetHandlers = this._setHandlers.bind(this);
    drawerInstance.on(drawerInstance.EVENT_OPTIONS_TOOLBAR_CREATED, this._bindedSetHandlers);

    drawerInstance.on(drawerInstance.EVENT_EDIT_STOP, this._onEditStop.bind(this));
    drawerInstance.on(drawerInstance.EVENT_OBJECT_MOVING, this._onObjectMoved.bind(this));
  };


  ShapeContextMenu.prototype.eventsNamespace = 'shapeContextMenu';

  ShapeContextMenu.prototype._defaultOptions = {
    position: {
      touch: 'shapeRightBottom', // context menu will be placed at shape's right bottom corner
      mouse: 'cursor' // context menu will be placed in the position of click
    }
  };



  ShapeContextMenu.prototype._setHandlers = function () {
    if (this.options.customMenuRenderer) {
      this.drawerInstance.on(this.drawerInstance.EVENT_CONTEXTMENU + '.' + this.eventsNamespace, this.options.customMenuRenderer);
    }
    else {
      this.drawerInstance.on(this.drawerInstance.EVENT_CONTEXTMENU + '.' + this.eventsNamespace, this._bindedOnContextMenu);

      var self = this;
      util.bindClick($('body'), 'shapeContextMenu', function () {
        self.hideContextMenu();
      });
    }
    this.drawerInstance.$canvasEditContainer.off(MOUSE_DOWN).on(MOUSE_DOWN, this._onMouseDown.bind(this));
  };


  ShapeContextMenu.prototype._unsetHandlers = function (doDeleteToolbarCreationListeners) {
    if (this.options.customMenuRenderer) {
      this.drawerInstance.off(this.drawerInstance.EVENT_CONTEXTMENU + '.' + this.eventsNamespace);
    }
    else {
      util.unbindClick($('body'), 'shapeContextMenu');
      this.drawerInstance.off(this.drawerInstance.EVENT_CONTEXTMENU + '.' + this.eventsNamespace);

      if (doDeleteToolbarCreationListeners) {
        this.drawerInstance.off(this.drawerInstance.EVENT_OPTIONS_TOOLBAR_CREATED, this._bindedSetHandlers);
      }
    }
  };


  ShapeContextMenu.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    this._unsetHandlers(doDeleteToolbarCreationListeners);
  };

  ShapeContextMenu.prototype._onObjectMoved = function () {
    this.objectWasMoved = true;
  };

  ShapeContextMenu.prototype._onEditStop = function () {
    this._unsetHandlers();
  };

  ShapeContextMenu.prototype._onMouseDown = function () {
    this.objectWasMoved = false;
  };

  ShapeContextMenu.prototype._onContextMenu = function (event, originalEvent) {
    var drawingInProgress = this.drawerInstance.drawingInProgress,
      isBrushDrawing = this.drawerInstance.isBrushDrawing,
      objectWasMoved = this.objectWasMoved,
      ignoreContextMenu = drawingInProgress || isBrushDrawing || objectWasMoved;
    if (!ignoreContextMenu) {
      if (originalEvent.type.indexOf('touch') > -1) {
        originalEvent = originalEvent.originalEvent;
      }
      this.handleContextMenuEvent(originalEvent);
    }
  };

  /**
   * Handles context menu event: finds object by click coordinates, selects
   * that object and invokes {@link ShapeContextMenu.showContextMenu()}
   *
   * @param event mouse right click event or touch.originalEvent
   */
  ShapeContextMenu.prototype.handleContextMenuEvent = function (event) {
    var _this = this,
      targetIsCanvas = $(event.target).hasClass('upper-canvas'),
      canvasTarget = targetIsCanvas && _this.drawerInstance.fCanvas.findTarget(event),
      canvasTargetIsMoving = canvasTarget && canvasTarget.isMoving,
      targetCorner = canvasTarget && canvasTarget.__corner,
      needToShowContextMenu = canvasTarget && !canvasTargetIsMoving && !targetCorner;
    if (needToShowContextMenu) {
      _this.drawerInstance.fCanvas.setActiveObject(canvasTarget);
      _this.showContextMenu(canvasTarget, event);
    }
  };

  /**
   * Shows context menu for specified fabricjs object.
   *
   * @param fabricItem
   * @param event
   */
  ShapeContextMenu.prototype.showContextMenu = function (fabricItem, event) {
    var _this = this;
    _this.hideContextMenu();

    var eventsNS = _this.eventsNamespace;

    _this.$contextMenu = $(
      '<ul class="editable-canvas-shape-context-menu"' +
      '></ul>'
    );

    var $bringForward = $('<li><a>' +
      _this.drawerInstance.t('Bring forward') +
      '</a></li>');
    util.bindClick($bringForward.find('a'), eventsNS, this._bringObjectForward.bind(this, fabricItem));
    _this.$contextMenu.append($bringForward);

    var $sendBackwards = $('<li><a>' +
      _this.drawerInstance.t('Send backwards') +
      '</a></li>');
    util.bindClick($sendBackwards.find('a'), eventsNS, this._sendObjectBackwards.bind(this, fabricItem));
    _this.$contextMenu.append($sendBackwards);

    var $bringToFront = $('<li><a>' +
      _this.drawerInstance.t('Bring to front') +
      '</a></li>');
    util.bindClick($bringToFront.find('a'), eventsNS, this._bringObjectToFront.bind(this, fabricItem));

    _this.$contextMenu.append($bringToFront);

    var $sendToBack = $('<li><a>' +
      _this.drawerInstance.t('Send to back') +
      '</a></li>');
    util.bindClick($sendToBack.find('a'), eventsNS, this._sendObjectToBack.bind(this, fabricItem));

    _this.$contextMenu.append($sendToBack);

    var $duplicate = $('<li><a>' +
      _this.drawerInstance.t('Duplicate') +
      '</a></li>');
    util.bindClick($duplicate.find('a'), eventsNS, this._duplicateObject.bind(this, fabricItem));
    _this.$contextMenu.append($duplicate);

    var $remove = $('<li><a>' +
      _this.drawerInstance.t('Remove') +
      '</a></li>');
    util.bindClick($remove.find('a'), eventsNS, this._removeObject.bind(this, fabricItem));
    _this.$contextMenu.append($remove);

    var paddings = 20;
    if (_this.drawerInstance.touchDevice) {
      paddings = _this.drawerInstance.options.toolbarSizeTouch;
    } else {
      paddings = _this.drawerInstance.options.toolbarSize;
    }

    paddings = paddings / 3;

    _this.$contextMenu.find('li > a').each(function (k, v) {
      $(v).css({
        'padding-top': paddings + 'px',
        'padding-bottom': paddings + 'px'
      });
    });

    _this.drawerInstance.$canvasEditContainer.append(_this.$contextMenu);

    this._positionMenu(fabricItem, util.getEventPosition(event));
  };

  /**
   * Hides context menu.
   */
  ShapeContextMenu.prototype.hideContextMenu = function () {
    if (this.$contextMenu) {
      this.$contextMenu.remove();
    }
  };


  /**
   * Set Menu left and top coords.
   * @param {number} left
   * @param {number} top
   */
  ShapeContextMenu.prototype.setMenuPosition = function (left, top) {
    this.left = Number(left);
    this.top = Number(top);
    this.$contextMenu.css('left', left + 'px');
    this.$contextMenu.css('top', top + 'px');
  };


  /**
   * Get menu origin.
   * @return {Object} object with keys {left, top}
   */
  ShapeContextMenu.prototype.getMenuPosition = function () {
    return { left: this.left, top: this.top };
  };


  /**
   * Calcs menu position based on plugin options. click coords and object coords.
   * Then adjusts menu position to fit viewport.
   *
   * @param  {fabric.Object} fabricItem object for which context menu is called
   * @param  {Coords} clickCoords coords of mouse click
   */
  ShapeContextMenu.prototype._positionMenu = function (fabricItem, clickCoords) {
    // calc menu starting point based  on options
    var canvasOffset = $(this.drawerInstance.fCanvas.upperCanvasEl).offset();
    var left = 0;
    var top = 0;

    var optionsType = this.drawerInstance.touchDevice ? 'touch' : 'mouse';
    var positionOption = this.options.position[optionsType];


    if (positionOption == 'shapeRightBottom') {
      left = fabricItem.left + fabricItem.width;
      top = fabricItem.top + fabricItem.height;
    } else if (positionOption == 'cursor') {
      left = clickCoords.left - canvasOffset.left + 10;
      top = clickCoords.top - canvasOffset.top + 10;
    }

    var adjustedCoords = {};
    if (this.options.customFitViewportMethod) {
      adjustedCoords = this.options.customFitViewportMethod(left, top);
    } else {
      adjustedCoords = this._calcCoordsToFitViewport(left, top);
    }

    left = adjustedCoords.left;
    top = adjustedCoords.top;

    this.setMenuPosition(left, top);
  };


  /**
   * Calcualtes new coords for conetx menu to fit in viewport
   * @param  {number} left current menu origin left
   * @param  {number} top  current menu origin coord
   * @return {Object}      returns Object with keys {left, top}
   */
  ShapeContextMenu.prototype._calcCoordsToFitViewport = function (left, top) {
    left = Number(left);
    top = Number(top);
    // check if bottom edge is not outside viewport
    var menuHeight = this.$contextMenu.height();
    var canvasHeight = this.drawerInstance.fCanvas.height;
    var bottom = top + menuHeight;
    if (bottom > canvasHeight) {
      top = canvasHeight - menuHeight - 10;
    }

    var menuWidth = this.$contextMenu.width();
    var canvasWidth = this.drawerInstance.fCanvas.width;
    var right = left + menuWidth;
    if (right > canvasWidth) {
      left = canvasWidth - menuWidth - 10;
    }

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


  ShapeContextMenu.prototype._bringObjectForward = function (fabricItem) {
    this.drawerInstance.api.bringObjectForward(fabricItem);
    this.hideContextMenu();
    return false;
  };

  ShapeContextMenu.prototype._sendObjectBackwards = function (fabricItem) {
    this.drawerInstance.api.sendObjectBackwards(fabricItem);
    this.hideContextMenu();
    return false;
  };


  ShapeContextMenu.prototype._bringObjectToFront = function (fabricItem) {
    this.drawerInstance.api.bringObjectToFront(fabricItem);
    this.hideContextMenu();
    return false;
  };


  ShapeContextMenu.prototype._sendObjectToBack = function (fabricItem) {
    this.drawerInstance.api.sendObjectToBack(fabricItem);
    this.hideContextMenu();
    return false;
  };


  ShapeContextMenu.prototype._removeObject = function (fabricItem) {
    this.drawerInstance.api.removeObject(fabricItem);
    this.hideContextMenu();
    return false;
  };


  ShapeContextMenu.prototype._duplicateObject = function (fabricItem) {
    this.drawerInstance.api.duplicateObject(fabricItem);
    this.hideContextMenu();
    return false;
  };


  pluginsNamespace.ShapeContextMenu = ShapeContextMenu;
}(jQuery, DrawerJs.plugins, DrawerJs.util));
(function ($, pluginsNamespace, util) {
  "use strict";

  var emptyFunc = function () { };

  var MOUSE_UP = util.mouseUp('ToggleVisibilityButton');
  var MOUSE_DOWN = util.mouseDown('ToggleVisibilityButton');
  var MOUSE_MOVE = util.mouseMove('ToggleVisibilityButton');

  /**
   * Provides a button toggle toolbars visibility
   *
   * @param {Drawer} drawer - Instance of {@link Drawer}
   * @param {Object} options
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var ToggleVisibilityButton = function ToggleVisibilityButtonConstructor(drawer, options) {
    /**
     * @type {Drawer}
     */
    this.drawer = drawer;
    this._setupOptions(options);
    this.drawer.on(this.drawer.EVENT_FLOATING_TOOLBAR_CREATED, this._onToolbarCreated.bind(this));
  };

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  ToggleVisibilityButton.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  /**
   * React on overcanvas mode
   * @private
   */
  ToggleVisibilityButton.prototype._onOverCanvasMode = function () {
    this.showNextItem();
    if (!this.positionInitialized) {
      this._setInitialPosition();
    }
  };

  /**
   * On toolbar created - create tool button.
   * @private
   */
  ToggleVisibilityButton.prototype._onToolbarCreated = function (ev, toolbar) {
    this.toolbar = toolbar;

    var needToInitButton = this.checkConfigForButton();
    if (needToInitButton) {
      this.createControls(toolbar);
      this._onOverCanvasMode();
    }
  };

  /**
   *
   * @param toolbar
   * @returns {Boolean}
   * @private
   */
  ToggleVisibilityButton.prototype._checkToolbarForButton = function (toolbar) {
    var result = false;
    if (toolbar) {
      var haveButton = toolbar.options.toggleVisibilityButton,
        isPopup = toolbar.options.compactType === 'popup';
      result = haveButton && !isPopup;
    }
    return result;
  };

  /**
   * Check current config of drawer for toolbars that need button
   * @returns {Boolean}
   */
  ToggleVisibilityButton.prototype.checkConfigForButton = function () {
    var drawingToolsToolbar = this.drawer.toolbars.drawingToolsToolbar,
      toolOptionsToolbar = this.drawer.toolbars.toolOptionsToolbar,
      settingsToolbar = this.drawer.toolbars.settingsToolbar,

      drawingToolsHave = this._checkToolbarForButton(drawingToolsToolbar),
      toolOptionsHave = this._checkToolbarForButton(toolOptionsToolbar),
      settingsHave = this._checkToolbarForButton(settingsToolbar),
      needToInit = drawingToolsHave || toolOptionsHave || settingsHave,
      toolbarsForToggle = [];
    if (drawingToolsHave) {
      toolbarsForToggle.push(drawingToolsToolbar);
    }
    if (toolOptionsHave) {
      toolbarsForToggle.push(toolOptionsToolbar);
    }
    if (settingsHave) {
      toolbarsForToggle.push(settingsToolbar);
    }
    this.toolbarsForToggle = toolbarsForToggle;
    return needToInit;
  };

  /**
   * @param toolbar
   * @private
   */
  ToggleVisibilityButton.prototype.createControls = function (toolbar) {
    this._createAndAddButton(toolbar);
    this.showButton();
  };

  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listeners of toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  ToggleVisibilityButton.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    if (this.deleteControls) {
      this.deleteControls();
    }

    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawer.off(this.drawer.EVENT_FLOATING_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };


  /**
   * Move button over canvas
   * @param {Number} [left] - new offset of button
   * @param {Number} [top] - new offset of button
   */
  ToggleVisibilityButton.prototype.moveButton = function (left, top) {
    if (!this.positionLimit) {
      this.refreshPositionLimits();
    }
    left = left > this.positionLimit.left ? this.positionLimit.left : left;
    top = top > this.positionLimit.top ? this.positionLimit.top : top;

    left = left < 0 ? 0 : left;
    top = top < 0 ? 0 : top;


    this.latestState = {
      left: left || 0,
      top: top || 0
    };
    this.$button.css({
      left: left + 'px',
      top: top + 'px'
    });
  };

  /**
   * Restore initial position/state of button
   * @private
   */
  ToggleVisibilityButton.prototype._setInitialPosition = function () {
    var offsetLeft = 0,
      offsetTop = 0,
      insidePlaceholders = this.drawer.toolbars.toolbarPlaceholders.inside,
      $insideTopPlaceholderEl = insidePlaceholders && insidePlaceholders.top && insidePlaceholders.top.$element,
      $insideLeftPlaceholderEl = insidePlaceholders && insidePlaceholders.left && insidePlaceholders.left.$element;
    if ($insideTopPlaceholderEl && $insideTopPlaceholderEl.length) {
      offsetTop += $insideTopPlaceholderEl.height();
    }
    if ($insideLeftPlaceholderEl && $insideLeftPlaceholderEl.length) {
      offsetLeft += $insideLeftPlaceholderEl.width();
    }
    this.positionInitialized = true;
    this.moveButton(offsetLeft, offsetTop);
  };


  /**
   * Creates and adds button to toolbar.
   * @param  {DrawerToolbar} toolbar
   * @private
   */
  ToggleVisibilityButton.prototype._createAndAddButton = function (toolbar) {
    var $button,
      $body = $('body'),
      toggleVisibilityButtonConf = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-toggle-canvas hidden',
        iconClass: 'fa-eye',
        tooltipText: this.drawer.t('Toggle toolbar vision')
      };
    $button = toolbar.addButton(toggleVisibilityButtonConf);

    this.$button = $button;

    var buttonLeft = (this.latestState && this.latestState.left) || 0,
      buttonTop = (this.latestState && this.latestState.top) || 0;
    this.moveButton(buttonLeft, buttonTop);
    // set all mouse handlers:
    // handle mouse move while tool is active
    $body.off(MOUSE_MOVE).on(MOUSE_MOVE, this._onMouseMove.bind(this));

    // handle mouse down and mouse up
    $button.off(MOUSE_DOWN).on(MOUSE_DOWN, this._onMouseDown.bind(this));
    $body.off(MOUSE_UP).on(MOUSE_UP, this._onMouseUp.bind(this));

    // util.bindLongPress($button, 'move-button');

  };



  /**
   * React on mouse down
   * @param {Event} event
   * @private
   */
  ToggleVisibilityButton.prototype._onMouseDown = function (event) {
    // turn erasing on
    var self = this;
    this.moveNow = true;
    this.triggerClick = true;

    this.refreshSizes();
    this.refreshPositionLimits();

    util.setTimeout(function () {
      self.triggerClick = false;
    }, 200);
  };


  /**
   * React on mouse up
   * @param {Event} event
   * @private
   */
  ToggleVisibilityButton.prototype._onMouseUp = function (event) {
    var self = this,
      isButton = event.target === this.$button.get(0) || this.$button.find(event.target).length;
    this.moveNow = false;
    this.dragNow = false;
    this.$button.removeClass('dragging');
    if (isButton && this.triggerClick && !this.touchRightNow) {
      this.triggerClick = false;
      this.touchRightNow = true;
      util.setTimeout(function () {
        self.touchRightNow = false;
      }, 400);
      this.showNextItem();
    }
  };

  /**
   * Listens for mouse movement
   * @param {Event} event
   * @private
   */
  ToggleVisibilityButton.prototype._onMouseMove = function (event) {
    if (this.moveNow) {

      if (!this.dragNow) {
        this.$button.addClass('dragging');
        this.dragNow = true;
      }
      if (!this.latestSizes) {
        this.refreshSizes();
      }
      var eventPos = util.getEventPosition(event),
        latestSizes = this.latestSizes,
        canvasSize = latestSizes.canvas;

      var left = eventPos.left - canvasSize.left - latestSizes.button.width / 2 - latestSizes.scroll.left;
      var top = eventPos.top - canvasSize.top - latestSizes.button.height / 2 - latestSizes.scroll.top;

      this.triggerClick = false;
      this.moveButton(left, top);

      event.preventDefault();
      event.stopPropagation();
    }
  };

  /**
   * Refresh max sizes of button offset
   */
  ToggleVisibilityButton.prototype.refreshSizes = function () {
    var result = {};

    var fCanvas = this.drawer.fCanvas,
      canvasSizeBox = fCanvas.upperCanvasEl.getBoundingClientRect(),
      buttonSizeBox = this.$button[0].getBoundingClientRect();
    result.canvas = canvasSizeBox;
    result.button = buttonSizeBox;
    result.scroll = util.getScrollTopFromElement(this.$button);
    this.latestSizes = result;
    return result;
  };

  /**
   *
   */
  ToggleVisibilityButton.prototype.refreshPositionLimits = function () {
    var buttonSize = this.$button.get(0).getBoundingClientRect(),
      borderSize = 2;
    this.positionLimit = {
      left: this.drawer.width - buttonSize.width - borderSize,
      top: this.drawer.height - buttonSize.height - borderSize,
    };
  };

  /**
   * Show button
   */
  ToggleVisibilityButton.prototype.showButton = function () {
    this.$button.removeClass('hidden');
  };

  /**
   * Hide button
   */
  ToggleVisibilityButton.prototype.hideButton = function () {
    this.$button.addClass('hidden');
  };

  /**
   * Toggle current visible toolbar
   */
  ToggleVisibilityButton.prototype.showNextItem = function () {
    var self = this,
      haveVisible,
      nextToolbar,
      firstToolbar = this.toolbarsForToggle[0];
    this.toolbarsForToggle.forEach(function (toolbar, i) {
      if (!toolbar.invisible) {
        var indexOfNextToolbar = i + 1;
        haveVisible = true;
        nextToolbar = self.toolbarsForToggle[indexOfNextToolbar];
      }
      toolbar.hideToolbar();
    });
    if (haveVisible) {
      if (nextToolbar) {
        nextToolbar.showToolbar();
      }
    } else {
      firstToolbar.showToolbar();
    }
  };

  pluginsNamespace.ToggleVisibilityButton = ToggleVisibilityButton;
})(jQuery, DrawerJs.plugins, DrawerJs.util);
(function (global, $, pluginsNamespace, DrawerApi, util) {
  'use strict';
  var fabric = global.fabric || (global.fabric = {});

  var MOUSE_DOWN = util.mouseDown('Zoom');
  var MOUSE_MOVE = util.mouseMove('Zoom');
  var MOUSE_UP = util.mouseUp('Zoom');
  var isWebkit = util.checkBrowser('webkit'),
    cursorPrefix = isWebkit ? '-webkit-' : '';

  /**
   * Provides ability to change zoom
   *
   * @param {DrawerJs.Drawer} drawer
   * @param {DrawerJs.plugins.Zoom.defaultOptions} [options]
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var Zoom = function ZoomConstructor(drawer, options) {
    /**
     * @type {Drawer}
     */
    this.drawer = drawer;
    this._setupOptions(options);
    this._init();
  };

  /**
   * @typedef {Object} defaultOptions
   * @memberOf DrawerJs.plugins.Zoom
   * @property {Boolean} [enabled=true] - Zoom is enabled
   * @property {Boolean} [enableWhenNoActiveTool=true] - Allow to move canvas when no object is selected and is no active tool.
   * @property {Boolean} [enableButton=true] - Add "move canvas" button to settings toolbar
   * @property {Boolean} [showZoomTooltip=true] - Show tooltip with current zoom value on any change of zoom props
   * @property {Boolean} [enableMove=true] - Enable move of canvas
   * @property {Boolean} [useWheelEvents=true] - Attach wheel events
   * @property {Boolean} [moveCanvasOnWheel=true] - Change center of viewport on wheel events depending on mouse position.
   * @property {Number} [moveCanvasOnWheelStep=0] - Attach wheel events. Valid range of value is from 0(center of
   * viewport will not changed) to 1(center of viewport will move to mouse position). Most comfortable value is 0.05-0.15
   * @property {Number} [zoomStep=1.05] - Step of each zoom change. (1.05 = 105%)
   * @property {Number} [defaultZoom=1] - Default zoom value
   * @property {Number} [minZoom=1] - Min zoom value
   * @property {Number} [maxZoom=32] - Max zoom value
   *
   */

  /**
   * @type {DrawerJs.plugins.Zoom.defaultOptions}
   * @private
   */
  Zoom.prototype._defaultOptions = {
    enabled: true,

    enableWhenNoActiveTool: true,
    enableButton: true,

    showZoomTooltip: true,
    enableMove: true,
    useWheelEvents: true,

    moveCanvasOnWheel: true,
    moveCanvasOnWheelStep: 0,

    zoomStep: 1.05,
    defaultZoom: 1,
    maxZoom: 32,
    minZoom: 1,
    buttonOrder: 11
  };

  /**
   * Init zoom plugin
   * @private
   */
  Zoom.prototype._init = function () {
    this.viewport = new pluginsNamespace.ZoomViewport(this.drawer);

    this.enabled = this.options.enabled;
    this.zoomIsOn = true;
    this.currentZoom = 1;

    this._updateProto();
    if (this.enabled) {
      this._attachDrawerEvents();
      this._updateDrawerValues();
    }
  };

  /**
   * Setup data
   * @param {Object} [options] - options to save
   * @param {String} [pluginName] - name of plugin
   * @param {Boolean} [doNotSave] - set true to not save result as this.options
   * @returns {Object} config of plugin
   */
  Zoom.prototype._setupOptions = function (options, pluginName, doNotSave) {
    pluginName = pluginName || this.name;
    var drawer = this.drawerInstance || this.drawer,
      optionsFromDrawer = drawer && drawer.getPluginConfig(pluginName),
      result = $.extend(true,
        {},
        this._defaultOptions || {},
        optionsFromDrawer || {},
        options || {}
      );

    if (!doNotSave) {
      this.options = result;
    }
    return result;
  };

  /**
   * Update prototype of fabric object/canvas
   * @private
   */
  Zoom.prototype._updateProto = function () {
    this.drawer.on(this.drawer.EVENT_CANVAS_READY, this._updateCanvasProto.bind(this));
    this.drawer.on(this.drawer.EVENT_CANVAS_READY, this._updateObjectProto.bind(this));
  };

  /**
   * Attach drawer events
   * @private
   */
  Zoom.prototype._attachDrawerEvents = function () {
    this.drawer.on(this.drawer.EVENT_CONFIG_TOOLBAR_CREATED, this._onToolbarCreated.bind(this));
    this.drawer.on(this.drawer.EVENT_RESTORE_DEFAULT_ZOOM, this.restoreDefaultZoom.bind(this));

    this.drawer.on(this.drawer.EVENT_DO_ACTIVATE_TOOL, this._onActivateTool.bind(this));
    this.drawer.on(this.drawer.EVENT_DO_DEACTIVATE_TOOL, this._ctxUpperRestore.bind(this));

    this.drawer.on(this.drawer.EVENT_BEFORE_RENDER, this._ctxSet.bind(this));
    this.drawer.on(this.drawer.EVENT_AFTER_RENDER, this._ctxRestore.bind(this));

    this.drawer.on(this.drawer.EVENT_ZOOM_SET, this._ctxSet.bind(this));
    this.drawer.on(this.drawer.EVENT_ZOOM_UNSET, this._ctxUnset.bind(this));
    this.drawer.on(this.drawer.EVENT_ZOOM_RESTORE, this._ctxRestore.bind(this));

    this.drawer.on(this.drawer.EVENT_ZOOM_UPPER_SET, this._ctxUpperSet.bind(this));
    this.drawer.on(this.drawer.EVENT_ZOOM_UPPER_UNSET, this._ctxUpperUnset.bind(this));
    this.drawer.on(this.drawer.EVENT_ZOOM_UPPER_RESTORE, this._ctxUpperRestore.bind(this));

    this.drawer.on(this.drawer.EVENT_CANVAS_START_RESIZE, this._ctxSet.bind(this));
    this.drawer.on(this.drawer.EVENT_CANVAS_STOP_RESIZE, this._ctxRestore.bind(this));

    this.drawer.on(this.drawer.EVENT_SELECTION_CLEARED, this._refreshButton.bind(this));
    this.drawer.on(this.drawer.EVENT_OBJECT_SELECTED, this._refreshButton.bind(this));

    this.drawer.on(this.drawer.EVENT_TOOL_ACTIVATED, this._refreshButton.bind(this));
    this.drawer.on(this.drawer.EVENT_TOOL_DEACTIVATED, this._refreshButton.bind(this));
  };

  Zoom.prototype._refreshButton = function (fEvent) {
    this._refreshMoveButtonState();
  };

  Zoom.prototype._ctxUpperSet = function (fEvent) {
    var fCanvas = this.drawer.fCanvas,
      ctxUpper = fCanvas.contextTop;
    ctxUpper.save();
    ctxUpper.scale(this.viewport.zoom, this.viewport.zoom);
    ctxUpper.translate(this.viewport.position.x, this.viewport.position.y);
  };

  Zoom.prototype._ctxUpperUnset = function (fEvent) {
    var fCanvas = this.drawer.fCanvas,
      ctxUpper = fCanvas.contextTop;
    ctxUpper.save();
    ctxUpper.scale(1 / this.viewport.zoom, 1 / this.viewport.zoom);
    ctxUpper.translate(-this.viewport.position.x, -this.viewport.position.y);

  };

  Zoom.prototype._ctxUpperRestore = function (fEvent) {
    var fCanvas = this.drawer.fCanvas,
      ctxUpper = fCanvas.contextTop;
    ctxUpper.restore();
  };

  Zoom.prototype._ctxSet = function (fEvent) {
    var fCanvas = this.drawer.fCanvas,
      ctx = fCanvas.contextContainer;
    ctx.save();
    ctx.scale(this.viewport.zoom, this.viewport.zoom);
    ctx.translate(this.viewport.position.x, this.viewport.position.y);
  };

  Zoom.prototype._ctxUnset = function (fEvent) {
    var fCanvas = this.drawer.fCanvas,
      ctx = fCanvas.contextContainer;
    ctx.save();
    ctx.scale(1 / this.viewport.zoom, 1 / this.viewport.zoom);
    ctx.translate(-this.viewport.position.x, -this.viewport.position.y);
  };

  Zoom.prototype._ctxRestore = function (fEvent) {
    var fCanvas = this.drawer.fCanvas,
      ctx = fCanvas.contextContainer;
    ctx.restore();
  };
  /**
   * React on activate of tool
   * @param {fabric.Event} fEvent event obj
   * @param {BaseTool} tool tool object
   * @private
   */
  Zoom.prototype._onActivateTool = function (fEvent, tool) {
    if (!tool.doNotZoomOnActivate) {
      this._ctxUpperSet();
    }
  };

  /**
   * React on deactivate of tool
   * @param {fabric.Event} fEvent event obj
   * @param {BaseTool} tool tool object
   * @private
   */
  Zoom.prototype._onDeactivateTool = function (fEvent, tool) {
    if (!tool.doNotZoomOnActivate) {
      this._ctxUpperRestore();
    }
  };

  /**
   * On toolbar created - create tool button.
   * @param {fabric.Event} fEvent - event obj
   * @param {DrawerToolbar} toolbar - Drawer toolbar
   */
  Zoom.prototype._onToolbarCreated = function (fEvent, toolbar) {
    this.toolbar = toolbar;
    var needToInitButton = true;
    if (needToInitButton) {
      this.createControls(toolbar);
      this._createHelperElements();
      this._attachEvents();
    }
  };

  /**
   * Remove helper elements
   * @private
   */
  Zoom.prototype._removeHelperElements = function () {
    if (this.$zoomTooltip && this.$zoomTooltip.length) {
      this.$zoomTooltip.remove();
      delete this.$zoomTooltip;
    }
  };

  /**
   * Create helper elements
   * @private
   */
  Zoom.prototype._createHelperElements = function () {
    this._removeHelperElements();
    var zoomTooltipHtml = '<div class="zoom-tooltip transparent-tooltip"></div>',
      $zoomTooltip = $(zoomTooltipHtml),
      $canvasContainer = this.drawer.$canvasEditContainer;

    this.$zoomTooltip = $zoomTooltip;
    $canvasContainer.append($zoomTooltip);
  };

  /**
   * Attach events for helper elements
   * @private
   */
  Zoom.prototype._attachEvents = function () {
    var self = this;
    if (this.options.showZoomTooltip) {
      var hideFunc = function () {
        self.$zoomTooltip.addClass('transparent-tooltip');
      },
        debouncedTooltipHideFunc = util.debounce(hideFunc, 1000);

      this.drawer.on(this.drawer.EVENT_ZOOM_CHANGE, function (fEvent, zoomProps) {
        var zoomAsText = parseInt(zoomProps.zoom * 100, 10) + '%';
        self.$zoomTooltip.text(zoomAsText);
        self.$zoomTooltip.removeClass('transparent-tooltip');
        debouncedTooltipHideFunc();
      });
    }
    if (this.options.enableButton) {
      this.drawer.on(this.drawer.EVENT_ZOOM_CHANGE, function (fEvent, zoomProps) {
        var stateIsChanged = (zoomProps.prevValues.zoom === 1 && zoomProps.zoom !== 1) || (zoomProps.zoom === 1 && zoomProps.prevValues.zoom !== 1);
        if (stateIsChanged) {
          self._refreshMoveButtonState();
        }
      });
    }
  };


  /**
   * Create controls
   * @param {DrawerToolbar} toolbar - Drawer toolbar
   */
  Zoom.prototype.createControls = function (toolbar) {
    this._createAndAddButton(toolbar);
    this._attachWheelEvents();
    this._attachMoveEvents();
    this._attachTouchEvents();

  };


  /**
   * Creates and adds buttons to toolbar.
   * @param  {DrawerToolbar} toolbar
   */
  Zoom.prototype._createAndAddButton = function (toolbar) {
    var $zoomInButton,
      $zoomOutButton,
      $moveCanvasButton,
      zoomInButtonConf = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-zoom-in',
        iconClass: 'fa-search-plus',
        tooltipText: this.drawer.t('Zoom in'),
        clickHandler: this._onZoomIn.bind(this)
      },
      zoomOutButtonConf = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-zoom-out',
        iconClass: 'fa-search-minus',
        tooltipText: this.drawer.t('Zoom out'),
        clickHandler: this._onZoomOut.bind(this)
      },
      moveCanvasButtonConf = {
        buttonOrder: this.options.buttonOrder,
        additionalClass: 'btn-zoom-move',
        iconClass: 'fa-search',
        tooltipText: this.drawer.t('Move canvas'),
        clickHandler: this._onMoveButtonClick.bind(this)
      };
    $zoomInButton = toolbar.addButton(zoomInButtonConf);
    $zoomOutButton = toolbar.addButton(zoomOutButtonConf);

    if (this.options.enableMove && this.options.enableButton) {
      $moveCanvasButton = toolbar.addButton(moveCanvasButtonConf);
    }

    this.toolbar = toolbar;
    this.$zoomInButton = $zoomInButton;
    this.$zoomOutButton = $zoomOutButton;
    this.$moveCanvasButton = $moveCanvasButton;
  };

  /**
   * React on "Move canvas" button click
   * @param {Event} event
   * @private
   */
  Zoom.prototype._onMoveButtonClick = function (event) {
    var isDisabled = this.$moveCanvasButton.hasClass('disabled');
    if (!isDisabled) {
      this.moveButtonIsActive = !this.moveButtonIsActive;
      this._refreshMoveButtonState(true);
    }
  };

  /**
   * Collect data for zoom from event
   * @param {Event} event
   * @param {Boolean} [changeZoomCenter]
   * @param {Boolean} [zoomIn]
   * @returns {Object}
   * @private
   */
  Zoom.prototype._getZoomPopertiesFromEvent = function (event, changeZoomCenter, zoomIn) {
    var result = {},
      newZoomCenterX,
      newZoomCenterY,
      newZoomValue = this.currentZoom * (zoomIn ? this.options.zoomStep : 1 / this.options.zoomStep);
    if (changeZoomCenter) {
      var eventPosition = this.drawer.getRelativeEventPosition(event, true),
        zoomCenterXFromEvent = eventPosition.left / newZoomValue - this.viewport.position.x,
        zoomCenterYFromEvent = eventPosition.top / newZoomValue - this.viewport.position.y,
        deltaX = zoomCenterXFromEvent - this.zoomCenterX,
        deltaY = zoomCenterYFromEvent - this.zoomCenterY;

      newZoomCenterX = this.zoomCenterX + deltaX * this.options.moveCanvasOnWheelStep;
      newZoomCenterY = this.zoomCenterY + deltaY * this.options.moveCanvasOnWheelStep;
    } else {
      newZoomCenterX = this.zoomCenterX;
      newZoomCenterY = this.zoomCenterY;
    }
    result.zoom = newZoomValue;
    result.zoomCenterX = newZoomCenterX;
    result.zoomCenterY = newZoomCenterY;
    return result;
  };

  /**
   * Trigger zoom in event
   * @param {Event} event
   * @param {Boolean} [changeZoomCenter]
   * @private
   */
  Zoom.prototype._onZoomIn = function (event, changeZoomCenter) {
    if (this.zoomIsOn) {
      var zoomProps = this._getZoomPopertiesFromEvent(event, changeZoomCenter, true);
      this.setZoom(zoomProps.zoom, zoomProps.zoomCenterX, zoomProps.zoomCenterY, true);
    }
  };

  /**
   * Trigger zoom out event
   * @param {Event} event
   * @param {Boolean} [changeZoomCenter]
   * @private
   */
  Zoom.prototype._onZoomOut = function (event, changeZoomCenter) {
    if (this.zoomIsOn) {
      var zoomProps = this._getZoomPopertiesFromEvent(event, changeZoomCenter, false);
      this.setZoom(zoomProps.zoom, zoomProps.zoomCenterX, zoomProps.zoomCenterY, true);
    }
  };

  /**
   * Attach wheel event to canvas
   * @private
   */
  Zoom.prototype._attachWheelEvents = function () {
    if (this.options.useWheelEvents) {
      this.wheelEventsAttached = true;
      var canvasContainer = this.drawer.$canvasEditContainer.find('.canvas-container').get(0);
      util.addWheelListener(canvasContainer, this._onWheelEvent.bind(this), true); //@todo capture
    }
  };

  /**
   * Attach move events
   * @private
   */
  Zoom.prototype._attachMoveEvents = function () {
    if (this.options.enableMove) {
      var $document = $(window.document),
        $canvasContainer = this.drawer.$canvasEditContainer.find('.canvas-container');

      $canvasContainer.off(MOUSE_DOWN).on(MOUSE_DOWN, this._moveOnMouseDown.bind(this));
      $document.off(MOUSE_MOVE).on(MOUSE_MOVE, this._moveOnMouseMove.bind(this));
      $document.off(MOUSE_UP).on(MOUSE_UP, this._moveOnMouseUp.bind(this));
    }
  };

  /**
   * Move canvas - React on mouse down.
   * @param {Event} e
   * @private
   */
  Zoom.prototype._moveOnMouseDown = function (e) {
    var currActiveObj = this.drawer.fCanvas.getActiveObject(),
      selectedTool = this.drawer.activeDrawingTool,
      buttonIsActive = this.moveButtonIsActive,

      buttonPermission = this.options.enableButton && buttonIsActive,
      noActiveToolPermission = this.options.enableWhenNoActiveTool && (!this.options.enableButton || buttonPermission),
      preventMove = this.currentZoom === 1 || currActiveObj || selectedTool,
      initMove = !preventMove && (buttonPermission || noActiveToolPermission);

    if (initMove) {
      var currPoint = this.drawer.getRelativeEventPosition(e);

      this.drawer.movingNow = true;
      this.isMoving = true;
      this.moveStartPoint = currPoint;
      this.moveLastPoint = currPoint;

      var $body = $('body');
      this.drawer.fCanvas.defaultCursor = cursorPrefix + 'grabbing';
      this.drawer.fCanvas.setCursor(cursorPrefix + 'grabbing');
      $body.addClass('drawer-dragging drawer-zoom-moving');

      e.preventDefault();
      e.stopPropagation();
      return false;
    }
  };

  /**
   *
   * @private
   */
  Zoom.prototype._refreshMoveButtonState = function (doNotSaveState) {
    this.savedDefaultCursor = this.savedDefaultCursor || this.drawer.fCanvas.defaultCursor;
    if (this.options.enableButton) {
      if (this.currentZoom === 1) {
        this.$moveCanvasButton.addClass('disabled');
        this.drawer.fCanvas.defaultCursor = this.savedDefaultCursor;
        this.drawer.fCanvas.setCursor(this.savedDefaultCursor);
      } else {
        if (!doNotSaveState) {
          var enableToMove = this._checkMovePermission(!doNotSaveState);
          this.moveButtonIsActive = enableToMove;
        }

        this.$moveCanvasButton.removeClass('disabled');
        if (this.moveButtonIsActive) {
          this._prepareDrawerForZoom();
          this.drawer.fCanvas.defaultCursor = cursorPrefix + 'grab';
          this.drawer.fCanvas.setCursor(cursorPrefix + 'grab');
          $('body').addClass('drawer-zoom-move-available');
          this.toolbar.setActiveButton('btn-zoom-move');
        } else {
          this.drawer.fCanvas.defaultCursor = this.savedDefaultCursor;
          this.drawer.fCanvas.setCursor(this.savedDefaultCursor);
          $('body').removeClass('drawer-zoom-move-available');
          this.toolbar.clearActiveButton();
        }
      }
    } else {
      this.drawer.fCanvas.defaultCursor = cursorPrefix + 'grab';
      this.drawer.fCanvas.setCursor(cursorPrefix + 'grab');
      $('body').addClass('drawer-zoom-move-available');
    }
  };

  Zoom.prototype._prepareDrawerForZoom = function () {
    this.drawer.trigger(this.drawer.EVENT_DO_DEACTIVATE_ALL_TOOLS);
    this.drawer.fCanvas._clearSelection();
    this.drawer.fCanvas.renderAll();
  };



  /**
   *
   * @private
   */
  Zoom.prototype._checkMovePermission = function (withButtonState) {
    var currActiveObj = this.drawer.fCanvas.getActiveObject(),
      selectedTool = this.drawer.activeDrawingTool,
      buttonStateIsActive = withButtonState && this.moveButtonIsActive,
      result = !currActiveObj && !selectedTool && (this.options.enableWhenNoActiveTool || buttonStateIsActive);
    return result;
  };

  /**
   * Move canvas - React on mouse move.
   * @param {Event} e
   * @private
   */
  Zoom.prototype._moveOnMouseMove = function (e) {
    if (this.isMoving && this.currentZoom !== 1) {
      var self = this,
        moveEventPos = this.drawer.getRelativeEventPosition(e),
        moveLastPoint = this.moveLastPoint,
        deltaX = (moveLastPoint.left - moveEventPos.left),
        deltaY = (moveLastPoint.top - moveEventPos.top),
        newCenterX = this.zoomCenterX + deltaX,
        newCenterY = this.zoomCenterY + deltaY,
        moveDrawerFunc = function moveDrawerFunc() {
          self.setZoom(self.currentZoom, newCenterX, newCenterY, true);
        };

      this.moveLastPoint = moveEventPos;
      util.requestAnimationFrame(moveDrawerFunc);

      e.preventDefault();
      e.stopPropagation();
      return false;
    }
  };

  /**
   * Move canvas - React on mouse up.
   * @param {Event} e
   * @private
   */
  Zoom.prototype._moveOnMouseUp = function (e) {
    if (this.isMoving) {
      var $body = $('body'),
        self = this;

      util.setTimeout(function () {
        self.drawer.movingNow = false;
      }, 0);
      this.isMoving = false;
      this.drawer.fCanvas.defaultCursor = cursorPrefix + 'grab';
      this.drawer.fCanvas.setCursor(cursorPrefix + 'grab');
      $body.removeClass('drawer-dragging drawer-zoom-moving');

      e.preventDefault();
      e.stopPropagation();
      return false;
    }
  };

  /**
   * Attach wheel event to canvas
   * @private
   */
  Zoom.prototype._attachTouchEvents = function () {
    var self = this,
      $canvasContainer = this.drawer.$canvasEditContainer,
      startDiff,
      lastDiff;

    function getDistance(p1, p2) {
      var result;
      result = Math.sqrt(Math.pow(p1.left - p2.left, 2) + Math.pow(p1.top - p2.top, 2));
      return result;
    }

    $canvasContainer.off('touchstart.drawerZoom').on('touchstart.drawerZoom', function (event) {
      var touches = event.touches ? event.touches : event.originalEvent.touches;
      if (self.zoomIsOn && touches && touches.length > 1) {
        var pointPos1 = getEventPosition(event, 0),
          pointPos2 = getEventPosition(event, 1);
        if (!pointPos1.left || !pointPos1.top) {
          pointPos1 = getEventPosition(window.event, 0);
          pointPos2 = getEventPosition(window.event, 1);
        }
        startDiff = getDistance(pointPos1, pointPos2);
        lastDiff = startDiff;
        self._prepareDrawerForZoom();
      }
    });
    $canvasContainer.off('touchmove.drawerZoom').on('touchmove.drawerZoom', function (event) {
      var touches = event.touches ? event.touches : event.originalEvent.touches,
        zoominit = self.zoomIsOn && touches && touches.length > 1;
      if (zoominit) {
        var pointPos1 = util.getEventPosition(event, 0),
          pointPos2 = util.getEventPosition(event, 1);
        if (!pointPos1.left || !pointPos1.top) {
          pointPos1 = util.getEventPosition(window.event, 0);
          pointPos2 = util.getEventPosition(window.event, 1);
        }
        var currDiff = getDistance(pointPos1, pointPos2),
          isZoomIn = lastDiff < currDiff,
          zoom = self.currentZoom * (isZoomIn ? self.options.zoomStep : 1 / self.options.zoomStep),
          scrollOffset = util.getScrollOffset($canvasContainer),
          canvasContainer = $canvasContainer.get(0),
          canvasContainerSizes = canvasContainer.getBoundingClientRect(),
          touchCenterX = pointPos1.left - (pointPos1.left - pointPos2.left) / 2,
          touchCenterY = pointPos1.top - (pointPos1.top - pointPos2.top) / 2,
          zoomCenterX = touchCenterX - canvasContainerSizes.left - scrollOffset.left,
          zoomCenterY = touchCenterY - canvasContainerSizes.top - scrollOffset.top;

        self.setZoom(zoom, zoomCenterX, zoomCenterY, true);
        lastDiff = currDiff;
        event.stopPropagation();
        event.preventDefault();
      }
    });
  };

  /**
   * React on wheel event
   * @param {Event} e
   * @private
   */
  Zoom.prototype._onWheelEvent = function (e) {
    if (this.zoomIsOn) {
      var delta = e.deltaY || e.detail || e.wheelDelta;
      this._prepareDrawerForZoom();
      if (delta > 0) {
        this._onZoomOut(e, true);
      } else {
        this._onZoomIn(e, true);
      }
      e.stopPropagation();
      e.preventDefault();
    }
  };

  /**
   * Update zoom values of drawer/plugin
   * @param {Object} [data]
   * @private
   */
  Zoom.prototype._updateDrawerValues = function (data) {
    data = data || {};
    var width = this.drawer.width,
      height = this.drawer.height,
      zoomCenter = this.viewport.getZoomCenter();

    this.zoomCenterX = this.zoomCenterX !== undefined ? this.zoomCenterX : zoomCenter.x;
    this.zoomCenterY = this.zoomCenterY !== undefined ? this.zoomCenterY : zoomCenter.y;

    var zoomCenterX = data.zoomCenterX !== undefined ? data.zoomCenterX : this.zoomCenterX,
      zoomCenterY = data.zoomCenterY !== undefined ? data.zoomCenterY : this.zoomCenterY,
      currentZoom = data.zoom;

    if (typeof currentZoom !== 'number' || !isFinite(currentZoom)) {
      currentZoom = this.currentZoom;
      if (typeof currentZoom !== 'number' || !isFinite(currentZoom)) {
        currentZoom = this.options.defaultZoom;
      }
    }

    if (zoomCenterX > width) {
      zoomCenterX = width;
    }
    if (zoomCenterY > height) {
      zoomCenterY = width;
    }
    if (zoomCenterX < 0) {
      zoomCenterX = 0;
    }
    if (zoomCenterY < 0) {
      zoomCenterY = 0;
    }
    if (typeof this.options.minZoom === 'number') {
      currentZoom = Math.max(currentZoom, this.options.minZoom);
    }
    if (typeof this.options.maxZoom === 'number') {
      currentZoom = Math.min(currentZoom, this.options.maxZoom);
    }

    this.drawer.currentZoom = this.currentZoom = currentZoom;
    this.drawer.zoomCenterX = this.zoomCenterX = zoomCenterX;
    this.drawer.zoomCenterY = this.zoomCenterY = zoomCenterY;
  };

  /**
   * Set zoom
   * @param {Number} value - new zoom value
   * @param {Number} [zoomCenterX] - x coord of new canvas center
   * @param {Number} [zoomCenterY] - y coord of new canvas center
   * @param {Boolean} [strict] - Set zoom center without changes
   */
  Zoom.prototype.setZoom = function (value, zoomCenterX, zoomCenterY, strict) {
    var zoomValueIsValid = typeof value === 'number' && isFinite(value),
      strictMode = strict !== undefined ? strict : !this.options.moveCanvasOnWheel,
      prevValues,
      zoomResult;
    if (zoomValueIsValid) {
      prevValues = {
        zoom: this.currentZoom,
        zoomCenterX: this.zoomCenterX,
        zoomCenterY: this.zoomCenterY
      };
      this._updateDrawerValues({
        zoom: value,
        zoomCenterX: zoomCenterX,
        zoomCenterY: zoomCenterY
      });

      zoomResult = this.viewport.setViewport(this.currentZoom, this.zoomCenterX, this.zoomCenterY, strictMode);
      zoomResult.prevValues = prevValues;
      this._updateDrawerValues(zoomResult);
      this.drawer.trigger(this.drawer.EVENT_ZOOM_CHANGE, [zoomResult]);
    } else {
      console.info('invalid zoom value');
    }
  };

  /**
   * Update prototype of fabric tools
   * @priv
   */
  Zoom.prototype._updateObjectProto = function () {
    var self = this,
      _drawControl = fabric.Object.prototype._drawControl,
      drawControls = fabric.Object.prototype.drawControls,
      getPointByOrigin = fabric.Object.prototype.getPointByOrigin,
      _setCornerCoords = fabric.Object.prototype._setCornerCoords;


    /*
    fabric.Object.prototype._drawControl = function (control, ctx, methodName, left, top) {
      var viewport = self.viewport,
          zoom = viewport ? viewport.zoom : 1;
      ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY);
      return _drawControl.apply(this, [control, ctx, methodName, left, top]);
    };

    fabric.Object.prototype.drawControls = function (ctx) {
      var viewport = self.viewport,
          zoom = viewport ? viewport.zoom : 1,
          result;
      this.cornerSize = this.cornerSize / zoom;
      result = drawControls.apply(this, [ctx]);
      this.cornerSize = this.cornerSize * zoom;
      return result;
    };
    */

    fabric.Object.prototype._setCornerCoords = function () {
      var viewport = self.viewport,
        zoom = viewport ? viewport.zoom : 1,
        result;
      this.cornerSize = this.cornerSize / zoom;
      result = _setCornerCoords.apply(this, arguments);
      this.cornerSize = this.cornerSize * zoom;
      return result;
    };

    fabric.Object.prototype.getPointByOrigin = function () {
      var viewport = self.viewport,
        zoom = viewport ? viewport.zoom : 1,
        result;
      this.cornerSize = this.cornerSize / zoom;
      result = getPointByOrigin.apply(this, arguments);
      this.cornerSize = this.cornerSize * zoom;
      return result;
    };

  };

  /**
   * Override native fabric functions
   * @private
   */
  Zoom.prototype._updateCanvasProto = function () {
    var self = this,
      fCanvas = this.drawer.fCanvas,
      getPointer = fCanvas.getPointer,
      _drawSelection = fCanvas._drawSelection;

    fCanvas.getPointer = function (e, ignoreZoom, upperCanvasEl) {
      var pointer = getPointer.apply(this, arguments);
      if (!isFinite(pointer.x) || !isFinite(pointer.y)) {
        var $canvasEditContainer = self.drawer.$canvasEditContainer,
          canvasEditContainerSizes = $canvasEditContainer.get(0).getBoundingClientRect(),
          eventPos = util.getEventPosition(window.event),
          offset = util.getScrollOffset(self.drawer.$canvasEditContainer),
          x = eventPos.left - offset.left - canvasEditContainerSizes.left,
          y = eventPos.top - offset.top - canvasEditContainerSizes.top;
        pointer = {
          x: x,
          y: y
        };
      }
      var offsetX = self.viewport.position.x,
        offsetY = self.viewport.position.y,
        newX = parseInt(pointer.x / self.viewport.zoom - offsetX, 10),
        newY = parseInt(pointer.y / self.viewport.zoom - offsetY, 10);
      pointer.oldX = pointer.x;
      pointer.oldY = pointer.y;
      pointer.newX = newX;
      pointer.newY = newY;
      pointer.x = newX;
      pointer.y = newY;
      return pointer;
    };

    fCanvas._drawSelection = function () {
      var ctx;
      ctx = this.contextTop;
      ctx.save();
      ctx.scale(this.viewport.zoom, this.viewport.zoom);
      ctx.translate(this.viewport.position.x, this.viewport.position.y);
      _drawSelection.apply(this, arguments);
      ctx.restore();
      return ctx;
    };
  };

  /**
   * Restore default state of zoom
   * @private
   */
  Zoom.prototype.restoreDefaultZoom = function () {
    var defaultZoomValue = this.options.defaultZoom,
      zoomCenter = this.viewport.getZoomCenter(),
      newZoomData;
    this.setZoom(defaultZoomValue, zoomCenter.x, zoomCenter.y, true);
    this.viewport.setToCenterOfCanvas(1);
    newZoomData = this.viewport.getData();
    this._updateDrawerValues(newZoomData);
  };


  /**
   * API methods
   */

  /**
   * Set zoom of current drawer instance
   * @param {Number} zoom - zoom level
   * @param {Number} [zoomCenterX] - x coord of new center point of canvas. From 0 to current canvas width
   * @param {Number} [zoomCenterY] - y coord of new center point of canvas. From 0 to current canvas height
   */
  DrawerApi.prototype.setZoom = function (zoom, zoomCenterX, zoomCenterY) {
    var zoomTool = this.drawer.getPluginInstance('Zoom');
    if (zoomTool) {
      zoomTool.setZoom(zoom, zoomCenterX, zoomCenterY, true);
    }
  };

  /**
   * Restore default state of zoom
   */
  DrawerApi.prototype.restoreDefaultZoom = function () {
    var zoomTool = this.drawer.getPluginInstance('Zoom');
    if (zoomTool) {
      zoomTool.restoreDefaultZoom();
    }
  };

  pluginsNamespace.Zoom = Zoom;
}(this, jQuery, DrawerJs.plugins, DrawerJs.DrawerApi, DrawerJs.util));
; (function (global, $, pluginsNamespace) {
  'use strict';

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

  /**
   * @param {DrawerJs.Drawer} drawer
   * @param {Object} [options]
   * @memberof DrawerJs.plugins
   * @constructor
   */
  var ZoomViewport = function ZoomViewportConstr(drawer, options) {
    this.drawer = drawer;
    this.i = 0;
    this.position = new fabric.Point(0, 0);

    this.zoom = 1;
    return this;
  };

  /**
   * Set zoom depending on last settings
   * @param {Number} newZoom - zoom value
   * @param {Number} x - x coordinate of zoom center point
   * @param {Number} y - y coordinate of zoom center point
   * @param {Boolean} [strict] - do not change given coords
   * @returns {Object}
   */
  ZoomViewport.prototype.setViewport = function (newZoom, x, y, strict) {
    this._adjustPosition(newZoom, x, y, strict);
    this._render();
    return this.getData();
  };

  /**
   * Get current viewport data
   * @returns {Object}
   */
  ZoomViewport.prototype.getData = function () {
    var data = {},
      zoomCenter = this.getZoomCenter();
    data.zoom = this.zoom;
    data.position = {
      x: this.position.x,
      y: this.position.y
    };
    data.zoomCenterX = zoomCenter.x;
    data.zoomCenterY = zoomCenter.y;
    return data;
  };

  /**
   * Get zoom center coords
   * @returns {Object}
   */
  ZoomViewport.prototype.getZoomCenter = function () {
    var result = {},
      width = this.drawer.width,
      height = this.drawer.height,
      availableX = width / this.zoom,
      availableY = height / this.zoom,
      zoomCenterX = availableX / 2 - this.position.x,
      zoomCenterY = availableY / 2 - this.position.y;

    result.x = zoomCenterX;
    result.y = zoomCenterY;
    return result;
  };

  /**
   *
   * @param {Number} zoom - zoom value
   * @param {Number} x - x offset of canvas
   * @param {Number} y - y offset of canvas
   * @returns {{x: (number), y: (number)}}
   * @private
   */
  ZoomViewport.prototype._validatePosition = function (zoom, x, y) {
    x = x || 0;
    y = y || 0;
    var width = this.drawer.width,
      height = this.drawer.height,
      availableX = width / zoom,
      availableY = height / zoom,
      maxPositionX = width - availableX,
      maxPositionY = height - availableY;
    if (x < -maxPositionX) {
      x = -maxPositionX;
    }
    if (y < -maxPositionY) {
      y = -maxPositionY;
    }
    if (x > 0) {
      x = 0;
    }
    if (y > 0) {
      y = 0;
    }
    return {
      x: x,
      y: y
    };
  };

  /**
   * @param {Number} [zoom]
   */
  ZoomViewport.prototype.setToCenterOfCanvas = function (zoom) {
    zoom = zoom || this.zoom;
    var halfWidth = this.drawer.width / 2,
      halfHeight = this.drawer.height / 2;

    this._adjustPosition(zoom, halfWidth, halfHeight, true);
    this._render();
  };

  /**
   * Render canvas
   * @private
   */
  ZoomViewport.prototype._render = function () {
    this.drawer.fCanvas.renderAll();
  };

  /**
   * Update position values
   * @param {Number} zoom - zoom value
   * @param {Number} x - x offset of canvas
   * @param {Number} y - y offset of canvas
   */
  ZoomViewport.prototype.setPosition = function (zoom, x, y) {
    zoom = zoom || this.zoom;
    var validatedPoint = this._validatePosition(zoom, x, y);
    this.position.x = validatedPoint.x;
    this.position.y = validatedPoint.y;
    this.zoom = zoom;
  };

  /**
   * Process coordinates of center point
   * @param {Number} newZoom - zoom value
   * @param {Number} x - x coordinate of zoom center point
   * @param {Number} y - y coordinate of zoom center point
   * @param {Boolean} [strict] - do not change given coords
   * @private
   */
  ZoomViewport.prototype._adjustPosition = function (newZoom, x, y, strict) {
    this.position.x = this.position.x || 0;
    this.position.y = this.position.y || 0;
    var width = this.drawer.width,
      height = this.drawer.height,
      k = newZoom / this.zoom,
      oldAvailableX = width / this.zoom,
      oldAvailableY = height / this.zoom,
      newAvailableX = width / newZoom,
      newAvailableY = height / newZoom,
      newX,
      newY;

    if (strict) {
      newX = newAvailableX / 2 - x;
      newY = newAvailableY / 2 - y;
    } else {
      if (x !== undefined && y !== undefined) {
        var deltaX = (x - width / 2) / this.zoom,
          deltaY = (y - width / 2) / this.zoom;
        newX = this.position.x - deltaX - ((oldAvailableX - newAvailableX) / 2);
        newY = this.position.y - deltaY - ((oldAvailableY - newAvailableY) / 2);
      } else {
        newX = width / 2 - k * (width / 2 - this.position.x);
        newY = height / 2 - k * (height / 2 - this.position.y);
      }
    }
    this.setPosition(newZoom, newX, newY);
    return this.position;
  };

  pluginsNamespace.ZoomViewport = ZoomViewport;
}(this, jQuery, DrawerJs.plugins));
(function ($, pluginsNamespace, BaseToolOptions) {
  'use strict';

  /**
   * Provides range control for selecting brush size in free drawing mode.
   *
   * @param {DrawerJs.Drawer} drawer
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var BrushSize = function BrushSizeConstructor(drawer) {
    // call super constructor
    BaseToolOptions.call(this, drawer);

    this.name = 'BrushSize';

    /**
     * Option name. On selecting tool/object, if this.toolName is in array of
     * object allowed options - tool will show controls
     * @type {String}
     */
    this.optionName = 'brushSize';

    /**
     * Size controls element
     * @type {Object}
     */
    this.$sizeControl = null;

    // set handlers
    drawer.on(drawer.EVENT_BRUSH_SIZE_CHANGED, this.updateValue.bind(this));
    drawer.on(drawer.EVENT_BRUSH_CHANGED, this.updateValue.bind(this));
  };


  BrushSize.prototype = Object.create(BaseToolOptions.prototype);
  BrushSize.prototype.constructor = BaseToolOptions;


  //////////////////////////////////////////////////////////////////////////////////////////
  /**
   * Creates controls. Is called from BaseToolOptions._onToolbarCreated
   * @param  {DrawerToolbar} toolbar
   */
  BrushSize.prototype.createControls = function (toolbar) {
    this.createSizeControl(toolbar);
  };

  /**
   * Deletes tool button.
   * If  doDeleteToolbarCreationListeners is true - removes listenin on toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  BrushSize.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    this.$sizeControl.remove();

    // stop listening toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawer.off(this.drawer.EVENT_OPTIONS_TOOLBAR_CREATED, this._bindedOnToolbarCreated);
    }
  };


  /**
   * Create controls.
   * @param  {DrawerToolbar} toolbar to add control to
   */
  BrushSize.prototype.createSizeControl = function (toolbar) {
    var _this = this;

    _this.$sizeControl = $(
      '<li style="display:none" ' +
      'class="editable-canvas-brushsize toolbar-item-range"' +
      '>' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-label">' +
      this.drawer.t('Size:') + ' ' +
      '</span>' +
      '<span class="toolbar-label toolbar-label-indicator editable-canvas-brushsize-indicator">' +
      '0px' +
      '</span>' +
      '</div>' +
      '<input class="editable-canvas-brushsize-input" ' +
      'type="range" name="drawer-size" min="1"' +
      'value="0" />' +
      '</li>');

    toolbar.addControl(_this.$sizeControl, this.options.buttonOrder);

    $(_this.$sizeControl).on('change', function () {
      var size = $(_this.$sizeControl).find('input').val();
      $(_this.$sizeControl).find('.editable-canvas-brushsize-indicator')
        .text(size + 'px');
      _this.drawer.setBrushSize(size);
    });
  };


  BrushSize.prototype.showControls = function () {
    this.updateValue();
    this.$sizeControl.show();
  };

  BrushSize.prototype.hideControls = function () {
    this.$sizeControl.hide();
  };


  /**
   * Update size control with current drawer brush size.
   */
  BrushSize.prototype.updateValue = function () {
    var size = this.drawer.getBrushSize();
    this.$sizeControl.find('input').val(size);
    this.$sizeControl.find('.editable-canvas-brushsize-indicator')
      .text(size + 'px');
  };

  pluginsNamespace.BrushSize = BrushSize;

}(jQuery, DrawerJs.plugins, DrawerJs.plugins.BaseToolOptions));

(function ($, pluginsNamespace, BaseToolOptions, util) {
  'use strict';

  /**
   * Provides color input
   * for changing shapes/brush color.
   *
   * @param {DrawerJs.Drawer} drawer
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {String[]} options.colors
   * Array of colors to be used.
   *
   * @param {number} options.colorsInRow
   * Number of colors for one row.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @extends {DrawerJs.plugins.BaseToolOptions}
   */
  var ColorTool = function ColorToolConstructor(drawer, options) {
    // call super c-tor
    BaseToolOptions.call(this, drawer);
    this._setupOptions(options);

    /**
     * Instance of ColorpickerControl
     * @type {DrawerJs.plugins.ColorpickerControl}
     */
    this.colorControl = new pluginsNamespace.ColorpickerControl(this.drawer, this.options);
    /**
     * Instance of OpacityControl
     * @type {DrawerJs.plugins.OpacityControl}
     */
    this.opacityControl = new pluginsNamespace.OpacityControl(this.drawer, this.options);
  };

  ColorTool.prototype = Object.create(BaseToolOptions.prototype);
  ColorTool.prototype.constructor = BaseToolOptions;

  ColorTool.prototype.optionName = 'color';

  ColorTool.prototype._defaultOptions = {
    showOpacityControl: false,
    alwaysVisible: true,
    colorText: 'Fill:'
  };
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

  /**
   * Creates controls
   * @param  {DrawerToolbar} toolbar
   */
  ColorTool.prototype.createControls = function (toolbar) {
    this.colorControl.createControl(toolbar, this._onColorSelected.bind(this));
    this.opacityControl.createControl(toolbar, this._onOpacityControlChanged.bind(this));
  };


  /**
   * This function is called everytime user clicks on color from color-dropdown
   * menu.
   *
   * @param {String} selectedColor Hash value of user selected color.
   */
  ColorTool.prototype._onColorSelected = function (selectedColor) {
    if (selectedColor == 'transparent') {
      var opacity = this.opacityControl.getOpacity();
      var colorWithAlfaRgba = this._hexToRgba(selectedColor, opacity);

      this.drawer.setColor(colorWithAlfaRgba);
    } else {
      this.drawer.setColor(selectedColor);
    }
  };


  /**
   * This function is called every time user clicks on color from color-dropdown
   * menu.
   *
   * @param {String} opacity Hash value of user selected color.
   */
  ColorTool.prototype._onOpacityControlChanged = function (opacity) {
    var currentColor = this.drawer.activeColor;
    var colorWithAlfaRgba = this._hexToRgba(currentColor, opacity);

    this.drawer.setColor(colorWithAlfaRgba);
  };


  /**
   * React on object selection - update controls
   * Is called from BaseOptionTool._onObjectSelected method
   *
   * @param  {fabric.Object} target
   */
  ColorTool.prototype.updateControlsFromObject = function (target) {
    var color = null;

    // get object color
    if (target.path) { // free drawing shape
      // @todo: rework in target.getColor()
      color = target.get('stroke');
      this.colorControl.disableTransparent();
    } else {
      color = target.get('fill');
      this.colorControl.enableTransparent();
    }

    // update color and opacity controls
    if (color) {
      this.updateControlsWithColor(color);
      this.drawer.activeColor = color;
    }
  };


  /**
   * Updates color and opacity controls with color
   * @param  {String} color
   */
  ColorTool.prototype.updateControlsWithColor = function (color) {
    // update color control
    this.colorControl.setColor(color);

    // update opacity control
    var fColor = new fabric.Color(color);
    var source = fColor._source;
    var opacity = source[3];
    this.opacityControl.setOpacity(opacity);
  };


  /**
   * Show color control and optionally - opacity control
   * @param  {Boolean} [withoutOpacity]
   */
  ColorTool.prototype.showControls = function (withoutOpacity) {
    withoutOpacity = withoutOpacity !== undefined ? withoutOpacity : !this.options.showOpacityControl;
    this.colorControl.showControls();
    if (withoutOpacity) {
      this.opacityControl.hideControls();
    } else {
      this.opacityControl.showControls();
    }
  };


  /**
   * Hides both controls
   */
  ColorTool.prototype.hideControls = function (force) {
    var alwaysVisible = this.drawer.options.toolbars.popupButtonAlwaysVisible || this.options.alwaysVisible;
    if (force || !alwaysVisible) {
      this.colorControl.hideControls();
      this.opacityControl.hideControls();
    }
  };


  /**
   * Adds opacity to color in hex form, returns rgba
   * @param  {String} colorHex color in hex format
   * @param  {Number} opacity  opacity 0..1
   * @return {String}          color in rgba format
   */
  ColorTool.prototype._hexToRgba = function (colorHex, opacity) {
    var colorWithAlfa = new fabric.Color(colorHex);
    colorWithAlfa._source[3] = opacity;

    return colorWithAlfa.toRgba();
  };


  pluginsNamespace.Color = ColorTool;

}(jQuery, DrawerJs.plugins, DrawerJs.plugins.BaseToolOptions, DrawerJs.util));

(function ($, pluginsNamespace, util) {
  'use strict';

  var optimalSizeOfDropdown = 375;

  /**
   * Creates color input for changing color; colorChangeHandler is called on color change.
   *
   * @param {DrawerJs.Drawer} drawer
   *
   * @param {Object} [options]
   * Configuration object.
   *
   * @param {String[]} [options.colors]
   * Array of colors to be used.
   *
   * @param {number} [options.colorsInRow]
   * Number of colors for one row.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var ColorpickerControl = function ColorpickerControlConstructor(drawer, options) {
    this.drawer = drawer;

    // init options
    options = options || {};
    this.options = $.extend(true, {}, this._defaultOptions || {}, options || {});

    this.hideOnEditMode = true;
    // more stuff
    this.assetsUrl = util.getDrawerFolderUrl() + 'src/';
    this.shouldDisplayTransparent = false;

    this.colors = this.options.colors;
    this.colorsInRow = this.options.colorsInRow;
  };


  ColorpickerControl.prototype._defaultOptions = {
    colors: [
      '#ffffff', '#000000', '#eeece1', '#1f497d', '#4f81bd', '#c0504d', '#9bbb59', '#8064a2', '#4bacc6', '#f79646', '#ffff00',
      '#f2f2f2', '#7f7f7f', '#ddd9c3', '#c6d9f0', '#dbe5f1', '#f2dcdb', '#ebf1dd', '#e5e0ec', '#dbeef3', '#fdeada', '#fff2ca',
      '#d8d8d8', '#595959', '#c4bd97', '#8db3e2', '#b8cce4', '#e5b9b7', '#d7e3bc', '#ccc1d9', '#b7dde8', '#fbd5b5', '#ffe694',
      '#bfbfbf', '#3f3f3f', '#938953', '#548dd4', '#95b3d7', '#d99694', '#c3d69b', '#b2a2c7', '#b7dde8', '#fac08f', '#f2c314',
      '#a5a5a5', '#262626', '#494429', '#17365d', '#366092', '#953734', '#76923c', '#5f497a', '#92cddc', '#e36c09', '#c09100',
      '#7f7f7f', '#0c0c0c', '#1d1b10', '#0f243e', '#244061', '#632423', '#4f6128', '#3f3151', '#31859b', '#974806', '#7f6000'
    ],
    colorsInRow: 11,
    minSwatchSize: 10,
    buttonOrder: 6
  };

  ColorpickerControl.prototype.TRANSPARENT = 'rgba(0, 0, 0, 0)';
  ColorpickerControl.prototype.defaultPosition = 'bottom';
  ColorpickerControl.prototype.positions = ['bottom', 'top', 'left', 'right'];

  /**
   * Removes tool
   */
  ColorpickerControl.prototype.remove = function () {
    if (this.$colorButton) {
      this.$colorButton.remove();
    }
  };


  /**
   * Hides controls
   */
  ColorpickerControl.prototype.hideControls = function () {
    if (this.$colorButton) {
      this.$colorButton.addClass('hidden');
    }
  };

  /**
   * Shows controls.
   * Before showing - updates controls size and position.
   */
  ColorpickerControl.prototype.showControls = function () {
    if (this.$colorButton) {
      this.$colorButton.removeClass('hidden');
    }
  };


  /**
   * Returns current selected color
   * @return {String} currently selected css color
   */
  ColorpickerControl.prototype.getColor = function () {
    return this.currentColor;
  };


  /**
   * Sets current color.
   * @param {String|fabric.Color} color valid css color or 'transparent'
   */
  ColorpickerControl.prototype.setColor = function (color) {
    if (color instanceof fabric.Color) {
      this.currentColor = color;
    } else {
      this.currentColor = new fabric.Color(color);
    }

    var background = color,
      isTransparent = color === '' || color == this.TRANSPARENT || color == 'transparent';
    if (isTransparent) {
      background = 'url(' + this.assetsUrl + 'transparent.png)';
      this.currentColor = this.TRANSPARENT;
    }

    this.$colorButton.$colorIndicator.css('background', background);
  };


  /**
   * Removes transparent color from list
   */
  ColorpickerControl.prototype.disableTransparent = function () {
    this.shouldDisplayTransparent = false;

    if (this.$colorButton) {
      this.$colorButton.find('.transparent').hide();
    }
  };

  /**
   * Adds transparent color to the list
   */
  ColorpickerControl.prototype.enableTransparent = function () {
    this.shouldDisplayTransparent = true;

    if (this.$colorButton) {
      this.$colorButton.find('.transparent').show();
    }
  };


  /**
   * This function is called every time user clicks on color from color-dropdown
   * menu.
   *
   * @param {String} selectedColor Hash value of user selected color.
   */
  ColorpickerControl.prototype.onColorSelected = function (selectedColor) {
    this.hideColorDropdown();
    this.setColor(selectedColor);

    if (this.colorChangeHandler) {
      this.colorChangeHandler(selectedColor);
    }
  };


  /**
   * Create and attach global click handlers
   * @private
   */
  ColorpickerControl.prototype._setGlobalClickHandler = function () {
    var self = this;
    $('html').click(function (e) {
      if (self.colorDropdownVisible && (e.target != self.$colorButton.$colorIndicator.get(0))) {
        self.hideColorDropdown();
      }
      return true;
    });
  };


  /**
   * Creates color button which shows colors Controls on click.
   *
   * @param {DrawerToolbar} toolbar to append this button to.
   * @param {Function} colorChangeHandler - Function that will be called when color is selected.
   */
  ColorpickerControl.prototype.createControl = function (toolbar, colorChangeHandler) {
    this.$colorButton = $(this._getControlHtml());

    this._collectDataFromToolbar(toolbar);

    // cache control components
    this.$colorButton.$colorIndicator = this.$colorButton.find('.color-indicator');
    this.$colorButton.$colorDropdown = this.$colorButton.find('.color-dropdown');

    this.$colorButton.$colorIndicator.css('background-color', this.drawer.activeColor);

    this.colorDropdownVisible = false;
    this.$colorButton.$colorIndicator.click(this._onButtonClick.bind(this));

    this.colorChangeHandler = colorChangeHandler;

    this._buildPicker(this.$colorButton.$colorDropdown);

    this._setGlobalClickHandler();

    toolbar.addControl(this.$colorButton, this.options.buttonOrder);

    return this.$colorButton;
  };

  /**
   * React on button click
   * @param {Event} event - mouse click event
   * @private
   */
  ColorpickerControl.prototype._onButtonClick = function (event) {
    var $trigger = $(event.currentTarget);
    this.$lastTrigger = $trigger;
    this.toggleColorDropdown($trigger);
  };

  ColorpickerControl.prototype._collectDataFromToolbar = function (toolbar) {
    if (toolbar) {
      var toolbarPosition = toolbar.options.position;
      this.isVertical = toolbarPosition === 'left' || toolbarPosition === 'right';
      this.$toolbar = toolbar.$toolbar;
      this.toolbar = toolbar;
    }
  };

  /**
   * Build some magic picker.
   * @param  {jQuery} $container - jQuery wrapper of container
   * @private
   */
  ColorpickerControl.prototype._buildPicker = function ($container) {
    // create swatch for each color
    for (var i = 0; i < this.colors.length; i++) {
      var color = this.colors[i];

      var $swatch = this._createSwatch(color);
      $container.append($swatch);
    }

    // create transparentSwatch
    var $transparentSwatch = this._createTransparentSwatch();
    $container.append($transparentSwatch);
  };


  /**
   * Calcs swatch size and updated Controls width if needed.
   * @private
   */
  ColorpickerControl.prototype._updateControlsSize = function () {
    // calc swatch size
    var swatchSize = this._calcSwatchSize();
    // swatch size in css is 1em, so set $container fontSize
    this.$colorButton.$colorDropdown.css('fontSize', swatchSize + 'px');
    // set control width
    var controlWidth = swatchSize * this.colorsInRow;
    this.$colorButton.$colorDropdown.css('width', controlWidth + 'px');
  };


  /**
   * Calc swatch size to fit current drawer width, respecting this.colorsInRow
   * @return {Number} size of swatch in px
   * @private
   */
  ColorpickerControl.prototype._calcSwatchSize = function () {
    var swatchSize = this.drawer.touchDevice ? this.drawer.options.toolbarSize
      : this.drawer.options.toolbarSizeTouch;
    swatchSize = swatchSize || this.options.minSwatchSize;

    // calc swatch width to fit in drawer canvas
    var widthToFit = Math.floor(this.drawer.width * 0.95 / this.colorsInRow);
    // calc swatch height to fit in drawer canvas, including toolOptionsToolbar height
    var heightWithToolbars = this.drawer.height + this.drawer.toolbars.toolOptionsToolbar.height();
    var heightToFit = Math.floor(heightWithToolbars / (this.colors.length / this.colorsInRow));

    var minSize = Math.min(widthToFit, heightToFit);
    // look if colors total area is bigger then available space
    if (swatchSize > minSize) {
      swatchSize = Math.max(minSize, this.options.minSwatchSize);
    }

    return swatchSize;
  };

  /**
   * Collect sizes of needed elements
   * @param {jQuery} $trigger - trigger element
   * @returns {Object}
   * @private
   */
  ColorpickerControl.prototype._getAvailableSpace = function ($trigger) {
    var result;
    if ($trigger && $trigger.length) {
      var $canvas = this.drawer.$canvasEditContainer,
        $contentWrapper = this.$colorButton.$colorDropdown.closest('.popup-content-wrapper'),
        $toolbarWrapper = this.$colorButton.$colorDropdown.closest('.toolbar-content-wrapper'),
        canvasSizes = $canvas.get(0).getBoundingClientRect(),
        triggerSizes = $trigger.get(0).getBoundingClientRect(),
        toolbarSizes = $toolbarWrapper.get(0).getBoundingClientRect(),
        popupSizes = $contentWrapper.get(0).getBoundingClientRect(),
        paletteSizes = this.$colorButton.$colorDropdown.get(0).getBoundingClientRect(),

        // arrowSize = 10,
        arrowSize = 0,
        triggerOffsetX = triggerSizes.left - canvasSizes.left,
        triggerOffsetY = triggerSizes.top - canvasSizes.top;
      result = {};

      result.top = triggerOffsetY;
      result.left = triggerOffsetX;
      result.right = canvasSizes.width - triggerOffsetX - triggerSizes.width;
      result.bottom = canvasSizes.height - triggerOffsetY - triggerSizes.height;

      result.centerX = triggerOffsetX + triggerSizes.width / 2;
      result.centerY = triggerOffsetY + triggerSizes.height / 2;

      result.palette = {
        arrowSize: arrowSize,
        top: paletteSizes.height,
        bottom: paletteSizes.height,
        left: paletteSizes.width,
        right: paletteSizes.width
      };

      result.popupSizes = popupSizes;
      result.toolbarSizes = toolbarSizes;
      result.paletteSizes = paletteSizes;
      result.canvasSizes = canvasSizes;
      result.triggerSizes = triggerSizes;
    }
    this.sizes = result;
    return result;
  };

  /**
   * Reset palette
   * @private
   */
  ColorpickerControl.prototype._resetPalette = function () {
    this.$colorButton.$colorDropdown.removeClass('palette-with-scroll');
    this.$colorButton.$colorDropdown.removeAttr('style');
    this.$colorButton.$colorDropdown.removeAttr('data-position');
  };

  /**
   * Set size for palette as for canvas
   * @private
   */
  ColorpickerControl.prototype._setFullSize = function () {
    var sizes = this.sizes,
      popupOffsetLeft = sizes.toolbarSizes.left - sizes.canvasSizes.left,
      popupOffsetTop = sizes.toolbarSizes.top - sizes.canvasSizes.top,
      styles = {};

    styles.top = -popupOffsetTop;
    styles.left = -popupOffsetLeft;
    styles.width = sizes.canvasSizes.width;
    styles.height = sizes.canvasSizes.height;

    this.$colorButton.$colorDropdown.css(styles);
  };

  /**
   * Adjust position for popup
   * @param {jQuery} $element - trigger element
   * @private
   */
  ColorpickerControl.prototype._adjustPosition = function ($element) {
    this._resetPalette();
    var sizes = this._getAvailableSpace($element),
      positionFound;
    this.positions.forEach(function (position, i) {
      var positionExists = sizes[position] && sizes.palette[position];
      if (!positionFound && positionExists) {
        var neededSpace = sizes.palette[position] + sizes.palette.arrowSize,
          spaceIsEnough = (sizes[position] - neededSpace) > 0;
        positionFound = spaceIsEnough && position;
      }
    });
    if (positionFound) {
      this._movePalette(positionFound);
    } else {
      this._setFullSize();
    }
  };

  /**
   * Crop unnecessary width/height
   * @private
   */
  ColorpickerControl.prototype._cropSizeOfPalette = function () {
    var paletteSizes = this.$colorButton.$colorDropdown.get(0).getBoundingClientRect(),
      canvasSizes = this.sizes.canvasSizes,
      styles = {},
      negativeDiffY_bottom = canvasSizes.height - (paletteSizes.height + paletteSizes.top - canvasSizes.top),
      negativeDiffY_top = paletteSizes.top - canvasSizes.top,
      negativeDiffX_right = canvasSizes.width - (paletteSizes.width + paletteSizes.left - canvasSizes.left),
      negativeDiffX_left = paletteSizes.left - canvasSizes.left,
      negativeDiffY = Math.min(negativeDiffY_bottom, negativeDiffY_top),
      negativeDiffX = Math.min(negativeDiffX_right, negativeDiffX_left),
      notEnoughSpace;

    if (negativeDiffY < 0) {
      notEnoughSpace = true;
      styles.height = paletteSizes.height + negativeDiffY;
    }
    if (negativeDiffX < 0) {
      notEnoughSpace = true;
      styles.width = paletteSizes.width + negativeDiffX;
    }
    if (notEnoughSpace) {
      this.$colorButton.$colorDropdown.addClass('palette-with-scroll');
    }

    this.$colorButton.$colorDropdown.css(styles);
  };

  /**
   * Move palette to chosen direction
   * @param {String} position
   * @private
   */
  ColorpickerControl.prototype._movePalette = function (position) {
    var sizes = this.sizes,
      popupOffsetLeft = this.sizes.toolbarSizes.left - this.sizes.canvasSizes.left,
      popupOffsetTop = this.sizes.toolbarSizes.top - this.sizes.canvasSizes.top,
      isVerticalAlign,
      styles = {},
      offsetLeft,
      offsetTop,
      arrowOffsetLeft = sizes.paletteSizes.width / 2,
      arrowOffsetTop = sizes.paletteSizes.height / 2,
      negativeDiff,
      smallerThanCanvas,
      notEnoughSpace;

    switch (position) {
      case 'left':
        offsetLeft = sizes.left - sizes.paletteSizes.width - sizes.palette.arrowSize;
        offsetTop = sizes.centerY - sizes.paletteSizes.height / 2;
        styles.right = sizes.toolbarSizes.width - (sizes.left - popupOffsetLeft);
        break;
      case 'right':
        offsetLeft = sizes.canvasSizes.width - sizes.right + sizes.palette.arrowSize;
        offsetTop = sizes.centerY - sizes.paletteSizes.height / 2;
        styles.left = sizes.left - popupOffsetLeft + sizes.triggerSizes.width;
        break;
      case 'top':
        isVerticalAlign = true;
        offsetLeft = sizes.centerX - sizes.paletteSizes.width / 2;
        offsetTop = sizes.top - sizes.paletteSizes.height - sizes.palette.arrowSize;
        styles.bottom = sizes.toolbarSizes.height - (sizes.top - popupOffsetTop);
        break;
      case 'bottom':
        isVerticalAlign = true;
        offsetLeft = sizes.centerX - sizes.paletteSizes.width / 2;
        offsetTop = sizes.canvasSizes.height - sizes.bottom + sizes.palette.arrowSize;
        styles.top = sizes.top - popupOffsetTop + sizes.triggerSizes.height;
        break;
    }

    if (isVerticalAlign) {
      negativeDiff = (offsetLeft + sizes.paletteSizes.width) - sizes.canvasSizes.width;
      smallerThanCanvas = (sizes.canvasSizes.width - sizes.paletteSizes.width) > 0;
      if (negativeDiff > 0 && offsetLeft > 0 && !smallerThanCanvas) {
        arrowOffsetLeft += negativeDiff;
        styles.right = 0 - popupOffsetLeft;
      } else {
        if (offsetLeft < 0) {
          arrowOffsetLeft += offsetLeft;
          offsetLeft = 0;
        }
        notEnoughSpace = true;
        styles.width = sizes.canvasSizes.width;
        styles.left = offsetLeft - popupOffsetLeft;
      }
    } else {
      negativeDiff = (offsetTop + sizes.paletteSizes.height) - sizes.canvasSizes.height;
      smallerThanCanvas = (sizes.canvasSizes.height - sizes.paletteSizes.height) > 0;
      if (negativeDiff > 0 && offsetTop > 0 && !smallerThanCanvas) {
        arrowOffsetTop += negativeDiff;
        styles.bottom = 0 - popupOffsetTop;
      } else {
        notEnoughSpace = true;
        if (offsetTop < 0) {
          arrowOffsetTop += offsetTop;
          offsetTop = 0;
        }
        styles.height = sizes.canvasSizes.width;
        styles.top = offsetTop - popupOffsetTop;
      }
    }

    if (notEnoughSpace) {
      this.$colorButton.$colorDropdown.addClass('palette-with-scroll');
    }
    this.$colorButton.$colorDropdown.attr('data-position', position);
    this.$colorButton.$colorDropdown.css(styles);
    this._cropSizeOfPalette();
    // this._moveArrow(arrowOffsetLeft, arrowOffsetTop);
  };


  /**
   * Adjusts color dropdown position to be inside drawer
   * @private
   */
  ColorpickerControl.prototype._adjustControlsPosition = function () {
    var notEnoughWidth = this.drawer.width < optimalSizeOfDropdown,
      notEnoughHeight = this.drawer.height < optimalSizeOfDropdown,
      smallerThanNormalSize = this.isVertical ? notEnoughHeight : notEnoughWidth,
      canvasContainerSizes = this.drawer.$canvasEditContainer.get(0).getBoundingClientRect(),
      colorButtonSizes = this.$colorButton.get(0).getBoundingClientRect(),
      minimumSizeDelta = 10,
      currDropdownSizes;


    this.$colorButton.$colorDropdown.removeAttr('style');
    currDropdownSizes = this.$colorButton.$colorDropdown.get(0).getBoundingClientRect();

    if (this.isVertical) {
      if (smallerThanNormalSize) {
        this.$colorButton.$colorDropdown.css('top', 0);
      } else {
        var topOffsetOfButton = colorButtonSizes.top - canvasContainerSizes.top,
          newTopValue = topOffsetOfButton + colorButtonSizes.height / 2 - currDropdownSizes.height / 2;
        this.$colorButton.$colorDropdown.css('top', newTopValue);
      }
    } else {
      if (smallerThanNormalSize) {
        this.$colorButton.$colorDropdown.css('left', 0);
      } else {
        var leftOffsetOfButton = colorButtonSizes.left - canvasContainerSizes.left,
          newLeftValue = leftOffsetOfButton + colorButtonSizes.width / 2 - currDropdownSizes.width / 2;
        this.$colorButton.$colorDropdown.css('left', newLeftValue);
      }
    }

    if (!smallerThanNormalSize) {
      currDropdownSizes = this.$colorButton.$colorDropdown.get(0).getBoundingClientRect();
      if (this.isVertical) {
        smallerThanNormalSize = (optimalSizeOfDropdown - currDropdownSizes.height) > minimumSizeDelta;

        var topOffsetIsValid = currDropdownSizes.top > canvasContainerSizes.top,
          bottomOffsetIsValid = (currDropdownSizes.top + currDropdownSizes.height) < (canvasContainerSizes.top + canvasContainerSizes.height),
          topIsCorrect = topOffsetIsValid,
          bottomIsCorrect = bottomOffsetIsValid && !smallerThanNormalSize;

        if (!topIsCorrect) {
          this.$colorButton.$colorDropdown.removeAttr('style');
          this.$colorButton.$colorDropdown.css('top', 0);
        }
        if (!bottomIsCorrect) {
          this.$colorButton.$colorDropdown.removeAttr('style');
          this.$colorButton.$colorDropdown.css({
            'bottom': 0,
            'top': 'auto'
          });
        }
      } else {
        smallerThanNormalSize = (optimalSizeOfDropdown - currDropdownSizes.width) > minimumSizeDelta;

        var leftOffsetIsValid = currDropdownSizes.left > canvasContainerSizes.left,
          rightOffsetIsValid = (currDropdownSizes.left + currDropdownSizes.width) < (canvasContainerSizes.left + canvasContainerSizes.width),
          leftIsCorrect = leftOffsetIsValid,
          rightIsCorrect = rightOffsetIsValid && !smallerThanNormalSize;

        if (!leftIsCorrect) {
          this.$colorButton.$colorDropdown.removeAttr('style');
          this.$colorButton.$colorDropdown.css('left', 0);
        }
        if (!rightIsCorrect) {
          this.$colorButton.$colorDropdown.removeAttr('style');
          this.$colorButton.$colorDropdown.css({
            'right': 0,
            'left': 'auto'
          });
        }
      }
    }
  };

  /**
   * Get html of color control
   * @returns {String}
   * @private
   */
  ColorpickerControl.prototype._getControlHtml = function () {
    var colorLabelText = this.options.colorText || "";
    return '<li class="colorpicker-control" ' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      '>' +
      '<span class="toolbar-label" ' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      'data-editable-canvas-cssrules="line-height">' +
      this.drawer.t(colorLabelText) + ' ' + '</span>' +
      '<span class="color-indicator" ' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      'data-editable-canvas-cssrules="width"></span>' +
      '<span class="color-dropdown control-hidden hidden" ' +
      'data-editable-canvas-sizeable="toolbar-button"></span>' +
      '</li>';

  };

  /**
   * Creates swatch - square control with given color
   *
   * @param  {String} color
   * @param  {String} _class css class
   * @return {jQuery}
   * @private
   */
  ColorpickerControl.prototype._createSwatch = function (color, _class) {
    var $swatch = $('<a href="#"></a>');
    $swatch.attr('rel', color)
      .addClass('color-swatch')
      .css({ 'background-color': color });

    if (_class) {
      $swatch.addClass(_class);
    }

    // bind click
    util.bindClick($swatch, 'colorpicker', this._swatchClickHandler.bind(this));

    return $swatch;
  };


  /**
   * Swatch click handler.
   * Calls onColorSelected()
   * Prevents default click on <a> behavior.
   *
   * @param  {jQuery.Event} e
   * @private
   */
  ColorpickerControl.prototype._swatchClickHandler = function (e) {
    e.preventDefault();
    e.stopPropagation();

    var clickedColorValue = $(e.target).attr('rel');
    this.onColorSelected(clickedColorValue);
    return false;
  };

  /**
   * Creates swatch for transparent  color
   * @return {jQuery}
   * @private
   */
  ColorpickerControl.prototype._createTransparentSwatch = function () {
    var $swatch = this._createSwatch(this.TRANSPARENT, 'transparent');
    $swatch.text(this.drawer.t('Transparent'));

    if (!this.shouldDisplayTransparent) {
      $swatch.hide();
    }

    return $swatch;
  };

  /**
   * Show color dropdown
   * @param {jQuery} [$trigger] - trigger element
   */
  ColorpickerControl.prototype.showColorDropdown = function ($trigger) {
    this.colorDropdownVisible = true;
    this._updateControlsSize();
    var insidePopup = this.$toolbar.closest('.popup-content-wrapper').length;
    this.$colorButton.$colorDropdown.removeClass('hidden');
    if (insidePopup) {
      this._adjustPosition($trigger);
    } else {
      this._adjustControlsPosition();
    }

    this.$colorButton.$colorDropdown.removeClass('control-hidden');
    if (this.toolbar.options.compactType === 'scrollable') {
      this.$cloneControl = $('<div class="colorpicker-control"></div>');
      this.$cloneDropdown = this.$colorButton.$colorDropdown.clone(true);
      this.$cloneControl.append(this.$cloneDropdown);
      this.$toolbar.closest('.toolbar-placeholder').append(this.$cloneControl);
    }
  };

  /**
   * Hide color dropdown
   */
  ColorpickerControl.prototype.hideColorDropdown = function () {
    this.colorDropdownVisible = false;
    this.$colorButton.$colorDropdown.addClass('hidden');
    this.$colorButton.$colorDropdown.addClass('control-hidden');
    if (this.$cloneControl) {
      this.$cloneControl.remove();
    }
  };

  /**
   * Toggle state of color dropdown
   * @param {jQuery} [$trigger]
   */
  ColorpickerControl.prototype.toggleColorDropdown = function ($trigger) {
    if (!this.colorDropdownVisible) {
      this.showColorDropdown($trigger);
    } else {
      this.hideColorDropdown();
    }
  };


  pluginsNamespace.ColorpickerControl = ColorpickerControl;

}(jQuery, DrawerJs.plugins, DrawerJs.util));

(function ($, pluginsNamespace, util) {
  'use strict';

  /**
   * Creates color input for changing color; colorChangeHandler is called on color change.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {callback} colorChangeHandler will be called  with new color as parameter
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var OpacityControl = function OpacityControlConstructor(drawer, options) {
    /**
     * @type {Drawer}
     */
    this.drawer = drawer;

    // init options
    this.options = $.extend(true, {}, this._defaultOptions || {}, options || {});

    this._opacity = this.options.defaultOpacity;
    this.drawer.on(this.drawer.AFTER_CREATE_TOOLBARS, this._moveControlToEnd.bind(this));
  };


  OpacityControl.prototype._defaultOptions = {
    defaultOpacity: 1
  };


  /**
   * Removes tool
   */
  OpacityControl.prototype.remove = function () {
    if (this.$opacityControl) {
      this.$opacityControl.remove();
    }
  };

  /**
   * Move opacity control to the end of toolbar for popup
   * @private
   */
  OpacityControl.prototype._moveControlToEnd = function () {
    var needToMove = this.toolbar.options.compactType === 'popup';
    if (needToMove) {
      var $toolbar = this.$opacityControl.parent();
      $toolbar.append(this.$opacityControl);
    }
  };

  /**
   * Hides controls
   */
  OpacityControl.prototype.hideControls = function () {
    if (this.$opacityControl) {
      this.$opacityControl.addClass('hidden');
    }
  };


  /**
   * Shows controls
   */
  OpacityControl.prototype.showControls = function () {
    if (this.$opacityControl) {
      this.$opacityControl.removeClass('hidden');
    }
  };


  /**
   * Returns current opacity
   * @return {Number} current opacity - number from 0 to 1
   */
  OpacityControl.prototype.getOpacity = function () {
    return this._opacity;
  };


  /**
   * Sets current opacity and updated tool control.
   * @param {Number} opacity number from 0 to 1
   */
  OpacityControl.prototype.setOpacity = function (opacity) {
    this._opacity = opacity;

    this.$opacityControl.find('input').val(opacity * 100);
    this.$opacityControl.find('.editable-canvas-opacity-indicator').text(opacity * 100 + '%');
  };


  /**
   * This function is called every time user clicks on color from color-dropdown
   * menu.
   *
   * @param {String} selectedColor Hash value of user selected color.
   */
  OpacityControl.prototype.onOpacityChange = function () {
    var $opacityInput = $(this.$opacityControl).find('input'),
      rawValue = parseInt($opacityInput.val(), 10),
      rawValueIsValid = typeof rawValue === 'number' && isFinite(rawValue),
      validatedValue = rawValueIsValid ? rawValue : this.options.defaultOpacity,
      formattedValue = parseInt(validatedValue, 10) + '%';
    this._opacity = validatedValue / 100;

    this.$opacityIndicator.text(formattedValue);

    if (this.opacityChangeCallback) {
      this.opacityChangeCallback(this._opacity);
    }
  };


  /**
   * Create controls.
   * @param  {DrawerToolbar} toolbar to add control to
   */
  OpacityControl.prototype.createControl = function (toolbar, opacityChangeCallback) {
    var self = this;
    this.toolbar = toolbar;
    this.opacityChangeCallback = opacityChangeCallback;

    this.$opacityControl = $(
      '<li class="editable-canvas-opacity toolbar-item-range">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-label editable-canvas-opacity-label">' +
      this.drawer.t('Fill opacity:') + ' ' +
      '</span>' +
      '<span class="toolbar-label toolbar-label-indicator editable-canvas-opacity-indicator">' +
      '100%' +
      '</span>' +
      '</div>' +
      '<input class="editable-canvas-opacity-input" ' +
      'type="range" name="drawer-size" min="0" max="100"' +
      'value="100" />' +
      '</li>');

    this.$opacityIndicator = this.$opacityControl.find('.editable-canvas-opacity-indicator');

    toolbar.addControl(this.$opacityControl, this.options.buttonOrder);

    $(this.$opacityControl).on('change', this.onOpacityChange.bind(this));

    return this.$opacityControl;
  };


  OpacityControl.prototype.showControls = function () {
    // this.updateControl();
    this.$opacityControl.show();
  };


  OpacityControl.prototype.hideControls = function () {
    this.$opacityControl.hide();
  };


  // OpacityControl.prototype.updateControl = function () {
  //   var opactity = .5;
  //   this.setBrushSize

  // };

  pluginsNamespace.OpacityControl = OpacityControl;

}(jQuery, DrawerJs.plugins, DrawerJs.util));

(function ($, pluginsNamespace) {
  /**
   * Provides html5 color input for changing shapes/freedrawing brush color.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var ColorpickerHtml5 = function ColorpickerHtml5Constructor(drawerInstance) {
    var _this = this;
    this.drawerInstance = drawerInstance;
    this.name = 'BrushSize';

    drawerInstance.on(drawerInstance.EVENT_OPTIONS_TOOLBAR_CREATED,
      function (e, toolbar) {
        _this.createColorControl(toolbar);
      });
  };

  /**
   * Create control and append it to toolbar
   * @param  {DrawerToolbar} toolbar to add control to
   */
  ColorpickerHtml5.prototype.createColorControl = function (toolbar) {
    var _this = this;

    var colorButton = $(
      '<li class="editable-canvas-plugin-color"' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      'data-editable-canvas-cssrules="height">' +
      '<span class="toolbar-label"' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      'data-editable-canvas-cssrules="height,line-height">Color: </span>' +
      '<input class="editable-canvas-colorpicker" type="color" ' +
      'value="' + this.drawerInstance.activeColor + '" ' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      'data-editable-canvas-cssrules="height,width"/>' +
      '</li>');

    toolbar.addControl(colorButton, this.options.buttonOrder);

    var colorChangeHandler = function (event) {
      if (!event) {
        return;
      }

      var color = event.target.value;

      _this.drawerInstance.setColor(color);
    };

    colorButton.find('input').change(colorChangeHandler);
  };

  pluginsNamespace.ColorpickerHtml5 = ColorpickerHtml5;

}(jQuery, DrawerJs.plugins));

(function ($, pluginsNamespace, BaseToolOptions, util) {
  'use strict';

  /**
   * Provides input for changing width of line/arrow.
   *
   * @param {DrawerJs.Drawer} drawer
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} [options] - Configuration object
   * @param {Number} [options.digitsAfterDecimalPoint=0] - The number of digits to appear after the decimal point;
   *
   * @constructor
   * @memberof DrawerJs.options
   * @extends {DrawerJs.plugins.BaseToolOptions}
   */
  var LineWidth = function LineWidthConstructor(drawer, options) {
    // call super c-tor
    BaseToolOptions.call(this, drawer);
    this._setupOptions(options);
  };

  LineWidth.prototype = Object.create(BaseToolOptions.prototype);
  LineWidth.prototype.constructor = BaseToolOptions;

  LineWidth.prototype.name = 'LineWidth';
  LineWidth.prototype.optionName = 'lineWidth';
  // LineWidth.prototype.useCombobox = true;
  // LineWidth.prototype.buttonMode = true;
  // LineWidth.prototype.preventHightlight = true;
  LineWidth.prototype.useCombobox = false;
  LineWidth.prototype.preventHightlight = false;
  LineWidth.prototype.buttonMode = false;

  LineWidth.prototype.buttonIconClass = 'fa-arrows-h';

  LineWidth.prototype._defaultOptions = {
    digitsAfterDecimalPoint: 0
  };

  LineWidth.prototype.onSelectionCleared = function (toolbar) {
    this.data = false;
  };

  LineWidth.prototype.createControls = function (toolbar) {
    this.createControl(toolbar);
    this._attachEvents();
  };

  /**
   * Get html of control
   * @returns {string} result - html of controls
   * @private
   */
  LineWidth.prototype._generateHtml = function () {
    var result,
      selectHtml;

    selectHtml = '' +
      '<select ' +
      'class="editable-canvas-line-width-input controls-value-item" ' +
      'name="drawer-size"' +
      'data-name="lineWidth"' +
      '>' +
      '</select>';

    var optionItemDefaultClasses = 'toolbar-item-wrapper toolbar-item-range editable-canvas-line-width hidden',
      optionItemAdditionalClasses = '' +
        (this.buttonMode ? ' toolbar-button-item ' : '') +
        (this.preventHightlight ? ' prevent-highlight ' : ''),
      optionItemClasses = optionItemDefaultClasses + optionItemAdditionalClasses;

    /*
     // For button mode
     result = '' +
     '<li class="' + optionItemClasses + '">' +
     '<div class="toolbar-item-description">' +
     '<span class="toolbar-item-label">' +
     this.drawer.t('Line width:') + ' ' +
     '</span>' +
     '<span class="toolbar-item-valueholder"></span>' +
     '<span class="toolbar-item-icon fa ' + this.buttonIconClass + '"></span>' +
     '</div>' +
     '<div class="toolbar-dropdown-block collapsed">' +
     selectHtml +
     '</div>' +
     '</li>';

     */

    result = '' +
      '<li class="' + optionItemClasses + '">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-label editable-canvas-line-width-label">' +
      this.drawer.t('Line width:') + ' ' +
      '</span>' +
      '<span class="toolbar-label toolbar-label-indicator editable-canvas-line-width-indicator"></span>' + '' +
      '</div>' +
      '<input ' +
      'class="editable-canvas-line-width-input controls-value-item" ' +
      'name="drawer-line-width"' +
      'data-name="lineWidth"' +
      'type="range"' +
      'min="1"' +
      'max="1000"' +
      '>' +
      '</li>';
    return result;
  };

  /**
   * Create/add controls
   * @param {DrawerToolbar} toolbar
   * @returns {jQuery}
   * @private
   */
  LineWidth.prototype.createControl = function (toolbar) {
    var toolControlHtml = this._generateHtml();
    this.$toolControl = $(toolControlHtml);
    this.$valueIndicator = this.$toolControl.find('.editable-canvas-line-width-indicator');
    toolbar.addControl(this.$toolControl, this.options.buttonOrder);
    return this.$toolControl;
  };

  /**
   * Attach events for control element
   * @private
   */
  LineWidth.prototype._attachEvents = function () {
    if (this.$toolControl) {
      this.$toolControl.on('input change toolbarOptionChange', this.onInputChange.bind(this));
    }
  };


  /**
   * Validate width value
   * @param rawValue
   * @returns {*}
   */
  LineWidth.prototype.validateValue = function (rawValue) {
    rawValue = parseInt(rawValue, 10);
    var result,
      rawValueIsValid = typeof rawValue === 'number' && isFinite(rawValue) && rawValue;
    if (rawValueIsValid) {
      var decimalRatio = Math.pow(10, this.options.digitsAfterDecimalPoint),
        formattedValue = parseInt(rawValue * decimalRatio, 10) / decimalRatio;
      result = formattedValue;
    }
    return result;
  };

  /**
   * Set line width of current active object
   * @param {number|string} value - Width of line/arrow in px
   */
  LineWidth.prototype.setLineWidth = function (value) {
    var fCanvas = this.drawer.fCanvas,
      target = fCanvas.getActiveObject();
    if (target) {
      if (!this.data) {
        this.collectDataFromObject(target);
      }

      var diffRatio = parseInt(value / this.data.initialWidth * 100, 10) / 100;

      target.x1 = this.data.x1 * diffRatio * target.scaleX;
      target.x2 = this.data.x2 * diffRatio * target.scaleX;
      target.y1 = this.data.y1 * diffRatio * target.scaleY;
      target.y2 = this.data.y2 * diffRatio * target.scaleY;

      target.set('x1', target.x1);
      target.set('x2', target.x2);
      target.set('y1', target.y1);
      target.set('y2', target.y2);

      target.set('top', this.data.top);
      target.set('left', this.data.left);

      fCanvas.renderAll();
    }
  };

  /**
   * This function is called every time user changes width via control
   * @private
   */
  LineWidth.prototype.onInputChange = function () {
    var $opacityInput = $(this.$toolControl).find('input'),
      rawValue = $opacityInput.val(),
      validatedValue = this.validateValue(rawValue);
    this.$valueIndicator.text(validatedValue + 'px');
    this.setLineWidth(validatedValue);
  };

  LineWidth.prototype.collectDataFromObject = function (target) {
    var result = {},
      decimalRatio = Math.pow(10, this.options.digitsAfterDecimalPoint),
      calcWidth = function (x, y) {
        var result,
          axisSum = Math.pow(x, 2) + Math.pow(y, 2),
          widthRaw = Math.sqrt(axisSum),
          lineWidth = parseInt(widthRaw * decimalRatio) / decimalRatio;
        result = lineWidth;
        return result;
      },
      xDelta = target.x2 - target.x1,
      yDelta = target.y2 - target.y1,
      xDiffScaled = (xDelta) * target.scaleX,
      yDiffScaled = (yDelta) * target.scaleY,
      xDiff = (xDelta),
      yDiff = (yDelta),
      angleRad = Math.atan((yDelta) / (xDelta)),
      angleDeg = Math.abs(fabric.util.radiansToDegrees(angleRad));

    result.lineWidth = calcWidth(xDiffScaled, yDiffScaled);
    result.initialWidth = calcWidth(xDiff, yDiff);
    result.angleRad = angleRad;
    result.angleDeg = angleDeg;

    result.top = target.top;
    result.left = target.left;
    result.x1 = target.x1;
    result.x2 = target.x2;
    result.y1 = target.y1;
    result.y2 = target.y2;

    this.data = result;
    return result;
  };

  LineWidth.prototype.updateControls = function (dataToFill) {
    dataToFill = dataToFill || this.data;
    var rawValue = dataToFill.lineWidth,
      validatedValue = this.validateValue(rawValue);
    this.$toolControl.find('input').val(validatedValue);
    this.$valueIndicator.text(validatedValue + 'px');
  };

  LineWidth.prototype.showControls = function () {
    this.$toolControl.removeClass('hidden');
  };

  LineWidth.prototype.hideControls = function (force) {
    this.$toolControl.addClass('hidden');
  };

  pluginsNamespace.LineWidth = LineWidth;
}(jQuery, DrawerJs.plugins, DrawerJs.plugins.BaseToolOptions, DrawerJs.util));
(function ($, pluginsNamespace, BaseToolOptions, util) {
  'use strict';

  /**
   * Provides opacity input for changing shapes/brush opacity.
   *
   * @param {DrawerJs.Drawer} drawer
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options - Configuration object
   * @param {Number} options.defaultValue - Default opacity value
   *
   *
   * @constructor
   * @memberof DrawerJs.options
   * @extends {DrawerJs.plugins.BaseToolOptions}
   */
  var OpacityOption = function OpacityOptionConstructor(drawer, options) {
    // call super c-tor
    BaseToolOptions.call(this, drawer);
    this._setupOptions(options);
  };

  OpacityOption.prototype = Object.create(BaseToolOptions.prototype);
  OpacityOption.prototype.constructor = BaseToolOptions;

  OpacityOption.prototype.optionName = 'opacity';


  OpacityOption.prototype._defaultOptions = {
    defaultValue: 1,
    alwaysVisible: true
  };

  OpacityOption.prototype.createControls = function (toolbar) {
    this.createControl(toolbar, this.setOpacity.bind(this));
  };

  /**
   * Create controls.
   * @param {DrawerToolbar} toolbar
   * @param {Function} [opacityChangeCallback]
   * @returns {jQuery}
   */
  OpacityOption.prototype.createControl = function (toolbar, opacityChangeCallback) {
    this.toolbar = toolbar;
    this.opacityChangeCallback = opacityChangeCallback;

    this.$opacityControl = $(
      '<li class="editable-canvas-opacity-option toolbar-item-range">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-label editable-canvas-opacity-option-label">' +
      this.drawer.t('Opacity :') + ' ' +
      '</span>' +
      '<span class="toolbar-label toolbar-label-indicator editable-canvas-opacity-option-indicator">' +
      '100%' +
      '</span>' +
      '</div>' +
      '<input class="editable-canvas-opacity-option-input" ' +
      'type="range" name="drawer-size" min="0" max="100"' +
      'value="100" />' +
      '</li>');

    this.$opacityIndicator = this.$opacityControl.find('.editable-canvas-opacity-option-indicator');
    toolbar.addControl(this.$opacityControl, this.options.buttonOrder);
    this.$opacityControl.on('change', this._onOpacityChange.bind(this));
    return this.$opacityControl;
  };


  /**
   * Set opacity value
   * @private
   */
  OpacityOption.prototype.setOpacity = function (value) {
    var fCanvas = this.drawer.fCanvas,
      activeFabricObject = fCanvas.getActiveObject();

    this.drawer.setOpacity(value);
    if (activeFabricObject) {
      activeFabricObject.setOpacity(value);
      fCanvas.renderAll();
    }
  };

  /**
   * This function is called every time user changes opacity via control
   * @private
   */
  OpacityOption.prototype._onOpacityChange = function () {
    var $opacityInput = $(this.$opacityControl).find('input'),
      rawValue = parseInt($opacityInput.val(), 10),
      rawValueIsValid = typeof rawValue === 'number' && isFinite(rawValue),
      validatedValue = rawValueIsValid ? rawValue : this.options.defaultOpacity,
      formattedValue = parseInt(validatedValue, 10) + '%';
    this._opacity = validatedValue / 100;

    this.$opacityIndicator.text(formattedValue);

    if (this.opacityChangeCallback) {
      this.opacityChangeCallback(this._opacity);
    }
  };

  OpacityOption.prototype.collectDataFromObject = function (target) {
    var result = {};
    result.opacity = target.get('opacity');
    this.data = result;
    return result;
  };

  OpacityOption.prototype.updateControls = function (dataToFill) {
    dataToFill = dataToFill || this.data;

    var rawValue = dataToFill.opacity,
      rawValueIsValid = typeof rawValue === 'number' && isFinite(rawValue),
      validatedValue = rawValueIsValid ? rawValue : this.options.defaultOpacity,
      formattedValue = parseInt(validatedValue * 100, 10) + '%';
    this.drawer.activeOpacity = validatedValue;
    this.$opacityControl.find('input').val(validatedValue * 100);
    this.$opacityControl.find('.editable-canvas-opacity-option-indicator').text(formattedValue);
  };

  OpacityOption.prototype.showControls = function () {
    this.$opacityControl.removeClass('hidden');
  };

  OpacityOption.prototype.hideControls = function (force) {
    var alwaysVisible = this.drawer.options.toolbars.popupButtonAlwaysVisible || this.options.alwaysVisible;
    if (force || !alwaysVisible) {
      this.$opacityControl.addClass('hidden');
    }
  };

  pluginsNamespace.OpacityOption = OpacityOption;

}(jQuery, DrawerJs.plugins, DrawerJs.plugins.BaseToolOptions, DrawerJs.util));

(function ($, pluginsNamespace, BaseToolOptions, util) {
  'use strict';

  /**
   * Provides input for changing width of line/arrow.
   *
   * @param {DrawerJs.Drawer} drawer
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} [options] - Configuration object
   * @param {Number} [options.digitsAfterDecimalPoint=0] - The number of digits to appear after the decimal point;
   *
   * @constructor
   * @memberof DrawerJs.options
   * @extends {DrawerJs.plugins.BaseToolOptions}
   */
  var StrokeWidth = function StrokeWidthConstructor(drawer, options) {
    // call super c-tor
    BaseToolOptions.call(this, drawer);
    this._setupOptions(options);
  };

  StrokeWidth.prototype = Object.create(BaseToolOptions.prototype);
  StrokeWidth.prototype.constructor = BaseToolOptions;

  StrokeWidth.prototype.name = 'StrokeWidth';
  StrokeWidth.prototype.optionName = 'strokeWidth';
  // StrokeWidth.prototype.useCombobox = true;
  // StrokeWidth.prototype.buttonMode = true;
  // StrokeWidth.prototype.preventHightlight = true;
  StrokeWidth.prototype.useCombobox = false;
  StrokeWidth.prototype.preventHightlight = false;
  StrokeWidth.prototype.buttonMode = false;

  StrokeWidth.prototype.buttonIconClass = 'fa-arrows-h';

  StrokeWidth.prototype._defaultOptions = {
    digitsAfterDecimalPoint: 0
  };

  StrokeWidth.prototype.onSelectionCleared = function (toolbar) {
    this.data = false;
  };

  StrokeWidth.prototype.createControls = function (toolbar) {
    this.createControl(toolbar);
    this._attachEvents();
  };

  /**
   * Get html of control
   * @returns {string} result - html of controls
   * @private
   */
  StrokeWidth.prototype._generateHtml = function () {
    var result,
      selectHtml;

    selectHtml = '' +
      '<select ' +
      'class="editable-canvas-stroke-width-input controls-value-item" ' +
      'name="drawer-size"' +
      'data-name="strokeWidth"' +
      '>' +
      '</select>';

    var optionItemDefaultClasses = 'toolbar-item-wrapper toolbar-item-range editable-canvas-stroke-width hidden',
      optionItemAdditionalClasses = '' +
        (this.buttonMode ? ' toolbar-button-item ' : '') +
        (this.preventHightlight ? ' prevent-highlight ' : ''),
      optionItemClasses = optionItemDefaultClasses + optionItemAdditionalClasses;

    result = '' +
      '<li class="' + optionItemClasses + '">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-label editable-canvas-stroke-width-label">' +
      this.drawer.t('Stroke width:') + ' ' +
      '</span>' +
      '<span class="toolbar-label toolbar-label-indicator editable-canvas-stroke-width-indicator"></span>' + '' +
      '</div>' +
      '<input ' +
      'class="editable-canvas-stroke-width-input controls-value-item" ' +
      'name="drawer-stroke-width"' +
      'data-name="strokeWidth"' +
      'type="range"' +
      'min="1"' +
      'max="50"' +
      '>' +
      '</li>';
    return result;
  };

  /**
   * Create/add controls
   * @param {DrawerToolbar} toolbar
   * @returns {jQuery}
   * @private
   */
  StrokeWidth.prototype.createControl = function (toolbar) {
    var toolControlHtml = this._generateHtml();
    this.$toolControl = $(toolControlHtml);
    this.$valueIndicator = this.$toolControl.find('.editable-canvas-stroke-width-indicator');
    toolbar.addControl(this.$toolControl, this.options.buttonOrder);
    return this.$toolControl;
  };

  /**
   * Attach events for control element
   * @private
   */
  StrokeWidth.prototype._attachEvents = function () {
    if (this.$toolControl) {
      this.$toolControl.on('input change toolbarOptionChange', this.onInputChange.bind(this));
    }
  };


  /**
   * Validate width value
   * @param rawValue
   * @returns {*}
   */
  StrokeWidth.prototype.validateValue = function (rawValue) {
    rawValue = parseInt(rawValue, 10);
    var result,
      rawValueIsValid = typeof rawValue === 'number' && isFinite(rawValue) && rawValue;
    if (rawValueIsValid) {
      var decimalRatio = Math.pow(10, this.options.digitsAfterDecimalPoint),
        formattedValue = parseInt(rawValue * decimalRatio, 10) / decimalRatio;
      result = formattedValue;
    }
    return result;
  };

  /**
   * Set line width of current active object
   * @param {number|string} value - Width of line/arrow in px
   */
  StrokeWidth.prototype.setStrokeWidth = function (value) {
    value = parseInt(value, 10);
    var fCanvas = this.drawer.fCanvas,
      target = fCanvas.getActiveObject();
    this.drawer.lineStrokeWidth = value;
    if (target) {
      if (!this.data) {
        this.collectDataFromObject(target);
      }
      target.set('left', this.data.left);
      target.set('top', this.data.top);
      target.set('strokeWidth', parseInt(value, 10));
      fCanvas.renderAll();
    }
  };

  /**
   * This function is called every time user changes width via control
   * @private
   */
  StrokeWidth.prototype.onInputChange = function () {
    var $opacityInput = $(this.$toolControl).find('input'),
      rawValue = $opacityInput.val(),
      validatedValue = this.validateValue(rawValue);
    this.$valueIndicator.text(validatedValue + 'px');
    this.setStrokeWidth(validatedValue);
  };

  StrokeWidth.prototype.collectDataFromObject = function (target) {
    var result = {},
      decimalRatio = Math.pow(10, this.options.digitsAfterDecimalPoint),
      strokeWidth = target.strokeWidth;
    result.strokeWidth = strokeWidth;
    result.top = target.top;
    result.left = target.left;

    this.data = result;
    return result;
  };

  StrokeWidth.prototype.updateControls = function (dataToFill) {
    dataToFill = dataToFill || this.data;
    var rawValue = dataToFill.strokeWidth,
      validatedValue = this.validateValue(rawValue);
    this.drawer.lineStrokeWidth = validatedValue;
    this.$toolControl.find('input').val(validatedValue);
    this.$valueIndicator.text(validatedValue + 'px');
  };

  StrokeWidth.prototype.showControls = function () {
    this.$toolControl.removeClass('hidden');
  };

  StrokeWidth.prototype.hideControls = function (force) {
    this.$toolControl.addClass('hidden');
  };

  pluginsNamespace.StrokeWidth = StrokeWidth;
}(jQuery, DrawerJs.plugins, DrawerJs.plugins.BaseToolOptions, DrawerJs.util));
(function ($, pluginsNamespace, util, BaseTextOptionTool) {
  'use strict';

  /**
   * Creates controls for changing font size of text;
   *
   * @param drawer
   * Instance of drawer
   * @param {Object} options
   * Configuration object.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseTextOptionTool
   */

  var TextAlign = function TextAlignConstructor(drawer, options) {
    BaseTextOptionTool.call(this, drawer);

    // init options
    this._setupOptions(options);
  };

  TextAlign.prototype = Object.create(BaseTextOptionTool.prototype);
  TextAlign.prototype.constructor = BaseTextOptionTool;

  TextAlign.prototype.name = 'TextAlign';
  TextAlign.prototype.optionName = 'TextAlign';
  TextAlign.prototype.useCombobox = true;
  TextAlign.prototype.buttonMode = true;
  TextAlign.prototype.focusTextOnChange = true;
  TextAlign.prototype.preventHightlight = true;
  TextAlign.prototype.showOnEditMode = false;
  TextAlign.prototype.hideOnEditMode = true;
  TextAlign.prototype.stylesToObject = true;
  TextAlign.prototype.onlyPredefined = true;
  TextAlign.prototype.buttonIconClass = 'fa-align-left';

  TextAlign.prototype.valueType = {
    textAlign: 'string'
  };

  TextAlign.prototype._defaultOptions = {
    defaultValues: {
      textAlign: "left"
    },
    predefined: {
      textAlign: ["left", "center", "right", "justify"]
    },
    valueMap: {
      left: {
        value: 'left',
        classString: 'fa fa-align-left'
      },
      center: {
        value: 'center',
        classString: 'fa fa-align-center'
      },
      right: {
        value: 'right',
        classString: 'fa fa-align-right'
      },
      justify: {
        value: 'justify',
        classString: 'fa fa-align-justify'
      }
    }
  };

  TextAlign.prototype.controlTemplate = function () {
    var result,
      $predefined,
      selectHtml;

    $predefined = this.options.predefined.textAlign.map(function (size, i) {
      return '<option value="' + size + '">' + size + '</option>';
    }).join('');

    selectHtml = '' +
      '<select ' +
      'class="editable-canvas-text-textalign-input controls-value-item" ' +
      'name="drawer-size"' +
      'data-name="textAlign"' +
      'value="' + this.options.defaultValues.textAlign + '">' +
      $predefined +
      '</select>';

    var optionItemDefaultClasses = 'toolbar-item-wrapper editable-canvas-text-option editable-canvas-text-textalign hidden',
      optionItemAdditionalClasses = '' +
        (this.buttonMode ? ' toolbar-button-item ' : '') +
        (this.preventHightlight ? ' prevent-highlight ' : ''),
      optionItemClasses = optionItemDefaultClasses + optionItemAdditionalClasses;

    result = '' +
      '<li class="' + optionItemClasses + '">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-item-label">' +
      this.drawer.t('Font size:') + ' ' +
      '</span>' +
      '<span class="toolbar-item-valueholder"></span>' +
      '<span class="toolbar-item-icon fa ' + this.buttonIconClass + '"></span>' +
      '</div>' +
      '<div class="toolbar-dropdown-block collapsed">' +
      selectHtml +
      '</div>' +
      '</li>';

    return result;
  };

  pluginsNamespace.TextAlign = TextAlign;
}(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.plugins.BaseTextOptionTool));
(function ($, pluginsNamespace, util, BaseTextOptionTool) {
  'use strict';

  /**
   * Creates controls for changing text background color;
   *
   * @param drawer
   * Instance of drawer
   * @param {Object} options
   * Configuration object.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseTextOptionTool
   */

  var TextBackgroundColor = function TextBackgroundColorConstructor(drawer, options) {
    BaseTextOptionTool.call(this, drawer);

    // init options
    this._setupOptions(options);

    this.colorControl = new pluginsNamespace.ColorpickerControl(this.drawer, this.options);
  };

  TextBackgroundColor.prototype = Object.create(BaseTextOptionTool.prototype);
  TextBackgroundColor.prototype.constructor = BaseTextOptionTool;

  TextBackgroundColor.prototype.name = 'TextBackgroundColor';
  TextBackgroundColor.prototype.optionName = 'TextBackgroundColor';
  TextBackgroundColor.prototype.focusTextOnChange = true;
  TextBackgroundColor.prototype.useCombobox = false;
  TextBackgroundColor.prototype.buttonMode = false;

  TextBackgroundColor.prototype._defaultOptions = {
    colorText: 'Text background:',
    defaultValues: {
      textBackgroundColor: ''
    },
  };

  TextBackgroundColor.prototype.onlyPredefined = true;
  TextBackgroundColor.prototype.valueType = {
    textBackgroundColor: 'color'
  };

  TextBackgroundColor.prototype.updateSingleControl = function (valueName, value) {
    if (valueName === 'textBackgroundColor') {
      this.colorControl.setColor(value);
    }
  };

  TextBackgroundColor.prototype.getStylesFromControls = function () { };

  TextBackgroundColor.prototype.getStylesFromChangeEvent = function (data) {
    var result;
    if (data) {
      if (typeof data === 'object') {
        result = data.styles;
      }
      if (typeof data === 'string') {
        result = {
          textBackgroundColor: data
        };
      }
    }
    this._lastData = result;
    return result;
  };


  TextBackgroundColor.prototype.setupControl = function (toolbar, $toolControl, changeCallback) {
    this.colorChangeHandler = changeCallback;

    this.$toolControl = this.colorControl.createControl(toolbar, this.onInputChange.bind(this));
    // cache control components
    this.$toolControl.$colorIndicator = this.$toolControl.find('.color-indicator');
    this.$toolControl.$colorDropdown = this.$toolControl.find('.color-dropdown');

    this.$toolControl.$colorIndicator.attr('data-name', 'textBackgroundColor');
    this.$toolControl.$colorIndicator.addClass('controls-value-item');

    this.$toolControl.addClass('editable-canvas-text-option editable-canvas-text-backgroundcolor');

    this.colorDropdownVisible = false;
  };

  pluginsNamespace.TextBackgroundColor = TextBackgroundColor;
}(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.plugins.BaseTextOptionTool));
(function ($, pluginsNamespace, util, BaseTextOptionTool) {
  'use strict';

  /**
   * Creates controls for changing text color;
   *
   * @param drawer
   * Instance of drawer
   * @param {Object} options
   * Configuration object.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseTextOptionTool
   */

  var TextColor = function TextColorConstructor(drawer, options) {
    BaseTextOptionTool.call(this, drawer);

    // init options
    this._setupOptions(options);
    this.colorControl = new pluginsNamespace.ColorpickerControl(this.drawer, this.options);
  };

  TextColor.prototype = Object.create(BaseTextOptionTool.prototype);
  TextColor.prototype.constructor = BaseTextOptionTool;

  TextColor.prototype.name = 'TextColor';
  TextColor.prototype.optionName = 'TextColor';
  TextColor.prototype.focusTextOnChange = true;
  TextColor.prototype.onlyPredefined = true;

  TextColor.prototype._defaultOptions = {
    colorText: 'Font color:',
    defaultValues: {
      fill: ''
    },
  };

  TextColor.prototype.valueType = {
    fill: 'color'
  };

  TextColor.prototype.updateSingleControl = function (valueName, value) {
    if (valueName === 'fill') {
      this.colorControl.setColor(value);
    }
  };

  TextColor.prototype.getStylesFromControls = function () { };

  TextColor.prototype.getStylesFromChangeEvent = function (data) {
    var result;
    if (data) {
      if (typeof data === 'object') {
        result = data.styles;
      }
      if (typeof data === 'string') {
        result = {
          fill: data
        };
      }
    }
    this._lastData = result;
    return result;
  };

  TextColor.prototype.setupControl = function (toolbar, $toolControl, changeCallback) {
    this.colorChangeHandler = changeCallback;

    this.$toolControl = this.colorControl.createControl(toolbar, this.onInputChange.bind(this));
    // cache control components
    this.$toolControl.$colorIndicator = this.$toolControl.find('.color-indicator');
    this.$toolControl.$colorDropdown = this.$toolControl.find('.color-dropdown');

    this.$toolControl.$colorIndicator.attr('data-name', 'fill');
    this.$toolControl.$colorIndicator.addClass('controls-value-item');

    this.$toolControl.addClass('editable-canvas-text-option editable-canvas-text-color');

    this.colorDropdownVisible = false;
  };

  pluginsNamespace.TextColor = TextColor;
}(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.plugins.BaseTextOptionTool));
(function ($, pluginsNamespace, util, BaseTextOptionTool) {
  'use strict';

  /**
   * Creates controls for changing font size of text;
   *
   * @param drawer
   * Instance of drawer
   * @param {Object} options
   * Configuration object.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseTextOptionTool
   */

  var TextDecoration = function TextDecorationConstructor(drawer, options) {
    BaseTextOptionTool.call(this, drawer);
    this._setupOptions(options);
  };

  TextDecoration.prototype = Object.create(BaseTextOptionTool.prototype);
  TextDecoration.prototype.constructor = BaseTextOptionTool;

  TextDecoration.prototype.name = 'TextDecoration';
  TextDecoration.prototype.optionName = 'TextDecoration';
  TextDecoration.prototype.focusTextOnChange = true;
  TextDecoration.prototype.onlyPredefined = true;
  /**
   * @default
   * @const
   * @override
   * @type {string}
   */
  TextDecoration.prototype.buttonIconClass = 'fa-strikethrough';
  TextDecoration.prototype.valueType = {
    textDecoration: 'string'
  };

  /**
   * @type {object}
   * @private
   */
  TextDecoration.prototype._defaultOptions = {
    defaultValues: {
      textDecoration: ''
    },
    predefined: {
      textDecoration: ["underline", "overline", "line-through"]
    }
  };

  TextDecoration.prototype.controlTemplate = function () {
    var result,
      $predefined,
      selectHtml;

    $predefined = this.options.predefined.textDecoration.map(function (size, i) {
      return '<option value="' + size + '">' + size + '</option>';
    }).join('');

    selectHtml = '' +
      '<select ' +
      'class="editable-canvas-text-decoration-input controls-value-item" ' +
      'name="drawer-size"' +
      'data-name="textDecoration"' +
      'value="' + this.options.defaultValues.textDecoration + '">' +
      $predefined +
      '</select>';

    var optionItemDefaultClasses = 'toolbar-item-wrapper editable-canvas-text-option editable-canvas-text-decoration hidden',
      optionItemAdditionalClasses = this.buttonMode ? ' toolbar-button-item ' : '',
      optionItemClasses = optionItemDefaultClasses + optionItemAdditionalClasses;

    result = '' +
      '<li class="' + optionItemClasses + '">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-item-label">' +
      this.drawer.t('Text Decoration:') + ' ' +
      '</span>' +
      '<span class="toolbar-item-valueholder"></span>' +
      '<span class="toolbar-item-icon fa ' + this.buttonIconClass + '"></span>' +
      '</div>' +
      '<div class="toolbar-dropdown-block collapsed">' +
      selectHtml +
      '</div>' +
      '</li>';

    return result;
  };

  pluginsNamespace.TextDecoration = TextDecoration;
}(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.plugins.BaseTextOptionTool));
(function ($, pluginsNamespace, util, BaseTextOptionTool) {
  'use strict';

  /**
   * Creates controls for changing font family;
   *
   * @param drawer
   * Instance of drawer
   * @param {Object} options
   * Configuration object.
   * @param {Object} options.fonts
   * Specifies the list of fonts available to select.
   * The format is:
   *
   * <code>
   * <pre class="prettyprint javascript">
   *   {
   *    'Font display name': 'Font-family CSS value'
   *   }
   * </pre>
   * </code>
   *
   * Example:
   *
   * <code>
   * <pre class="prettyprint javascript">
   * fonts: {
   *     'Georgia': 'Georgia, serif',
   *     'Palatino': "'Palatino Linotype', 'Book Antiqua', Palatino, serif",
   *     'Times New Roman': "'Times New Roman', Times, serif"
   * }
   * </pre>
   * </code>
   *
   *
   * @param {String} options.defaultFont
   * Default font display name from <code>fonts</code> config
   * that will be selected when edit mode activates.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseTextOptionTool
   */

  var TextFontFamily = function TextFontFamilyConstructor(drawer, options) {
    BaseTextOptionTool.call(this, drawer);

    this._setupOptions(options);

    this.activeFont = this.options.fonts[this.options.defaultFont];
    this.drawer.on(this.drawer.EVENT_TEXT_GET_STYLES, this._onGetStyles.bind(this));
  };

  TextFontFamily.prototype = Object.create(BaseTextOptionTool.prototype);
  TextFontFamily.prototype.constructor = BaseTextOptionTool;

  TextFontFamily.prototype.name = 'TextFontFamily';
  TextFontFamily.prototype.optionName = 'TextFontFamily';
  TextFontFamily.prototype.buttonIconClass = 'fa-font';
  TextFontFamily.prototype.focusTextOnChange = true;
  TextFontFamily.prototype.onlyPredefined = true;
  TextFontFamily.prototype.valueType = {
    fontFamily: 'string'
  };

  TextFontFamily.prototype._defaultOptions = {
    defaultValues: {
      fontFamily: 'Georgia'
    },
    fonts: {
      'Georgia': 'Georgia, serif',
      'Palatino': "'Palatino Linotype', 'Book Antiqua', Palatino, serif",
      'Times New Roman': "'Times New Roman', Times, serif",

      'Arial': 'Arial, Helvetica, sans-serif',
      'Arial Black': "'Arial Black', Gadget, sans-serif",
      'Comic Sans MS': "'Comic Sans MS', cursive, sans-serif",
      'Impact': 'Impact, Charcoal, sans-serif',
      'Lucida Grande': "'Lucida Sans Unicode', 'Lucida Grande', sans-serif",
      'Tahoma': 'Tahoma, Geneva, sans-serif',
      'Trebuchet MS': "'Trebuchet MS', Helvetica, sans-serif",
      'Verdana': 'Verdana, Geneva, sans-serif',

      'Courier New': "'Courier New', Courier, monospace",
      'Lucida Console': "'Lucida Console', Monaco, monospace"
    },
    defaultFont: 'Georgia'
  };

  TextFontFamily.prototype.changeFont = function (fontFamilyName, withoutSetStyles) {
    var _this = this;

    _this.activeFont = _this.options.fonts[fontFamilyName];

    if (!withoutSetStyles) {
      _this.setStyles({
        fontFamily: _this.activeFont
      });
    }

    _this.$toolControl.find('.editable-canvas-fontfamily').css('font-family', _this.activeFont);
    _this.$toolControl.find('.editable-canvas-fontfamily').text(fontFamilyName || '');
    _this.$toolControl.find('.fonts-dropdown').addClass('hidden');
  };

  TextFontFamily.prototype.updateSingleControl = function (valueName, value) {
    if (valueName === 'fontFamily') {
      var font = this.getFontByCss(value);
      this.changeFont(font, true);
    }
  };

  TextFontFamily.prototype._collectDefaultOptions = function (pluginName) {
    var textConfig = this.drawer.getPluginConfig('Text'),
      result = {
        fonts: $.extend(true, {}, textConfig.fonts || {}),
        defaultFont: textConfig.defaultFont
      };
    return result;
  };

  TextFontFamily.prototype._onGetStyles = function (fEvent, tool, result) {
    result = result || {};
    result.defaultValues = result.defaultValues || {};
    result.defaultValues.fontFamily = this.options.fonts[this.options.defaultFont];
  };

  /**
   * Get font family from css string
   * @param {string} fontCssString
   * @returns {*}
   */
  TextFontFamily.prototype.getFontByCss = function (fontCssString) {
    var fName = null;
    $.each(this.options.fonts, function (fontName, fontCss) {
      if (fontCss == fontCssString) {
        fName = fontName;
      }
    });
    return fName;
  };

  TextFontFamily.prototype.setupControl = function (toolbar, $toolControl, changeCallback) {
    var _this = this;
    util.bindClick(
      $toolControl.find('.toolbar-label'), 'fontFamily',
      function () {
        $toolControl.find('.fonts-dropdown').toggleClass('hidden');
      });

    //
    $toolControl.find('li a').each(function (i, fontItem) {
      var $fontItem = $(fontItem);
      util.bindClick($fontItem, 'fontFamily',
        function () {
          var fname = $(this).data('fontName');
          _this.changeFont(fname);
        });
    });
  };

  TextFontFamily.prototype.controlTemplate = function () {
    var result,
      $predefined = '',
      selectHtml;

    $.each(this.options.fonts, function (fontName, fontCss) {
      var fontItem = '' +
        '<option ' +
        'style="font-family: ' + fontCss + ';"' +
        'value="' + fontName + '"' +
        'data-font-name="' + fontName + '">' +
        fontName +
        '</option>';

      $predefined += fontItem;
    });

    selectHtml = '' +
      '<select ' +
      'class="editable-canvas-text-fontfamily-input controls-value-item" ' +
      'name="drawer-size"' +
      'data-name="fontFamily"' +
      'value="' + this.options.defaultValues.fontFamily + '">' +
      $predefined +
      '</select>';

    var optionItemDefaultClasses = 'toolbar-item-wrapper editable-canvas-text-option editable-canvas-text-fontfamily hidden',
      optionItemAdditionalClasses = this.buttonMode ? ' toolbar-button-item ' : '',
      optionItemClasses = optionItemDefaultClasses + optionItemAdditionalClasses;

    result = '' +
      '<li class="' + optionItemClasses + '">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-item-label">' +
      this.drawer.t('Font family:') + ' ' +
      '</span>' +
      '<span class="toolbar-item-valueholder"></span>' +
      '<span class="toolbar-item-icon fa ' + this.buttonIconClass + '"></span>' +
      '</div>' +
      '<div class="toolbar-dropdown-block collapsed">' +
      selectHtml +
      '</div>' +
      '</li>';

    return result;
  };

  pluginsNamespace.TextFontFamily = TextFontFamily;
}(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.plugins.BaseTextOptionTool));
(function ($, pluginsNamespace, util, BaseTextOptionTool) {
  'use strict';

  /**
   * Creates controls for changing font size of text;
   *
   * @param drawer
   * Instance of drawer
   * @param {Object} options
   * Configuration object.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseTextOptionTool
   */

  var TextFontSize = function TextFontSizeConstructor(drawer, options) {
    BaseTextOptionTool.call(this, drawer);

    this._setupOptions(options);
  };

  TextFontSize.prototype = Object.create(BaseTextOptionTool.prototype);
  TextFontSize.prototype.constructor = BaseTextOptionTool;

  TextFontSize.prototype.name = 'TextFontSize';
  TextFontSize.prototype.optionName = 'TextFontSize';
  TextFontSize.prototype.buttonIconClass = 'fa-text-height';

  TextFontSize.prototype.valueType = {
    fontSize: 'number'
  };

  TextFontSize.prototype._defaultOptions = {
    defaultValues: {
      fontSize: 48
    },
    predefined: {
      fontSize: [6, 12, 14, 16, 20, 24, 32, 40, 48, 72]
    }
  };

  TextFontSize.prototype.controlTemplate = function () {
    var result,
      $predefined,
      selectHtml;

    $predefined = this.options.predefined.fontSize.map(function (size, i) {
      return '<option value="' + size + '">' + size + '</option>';
    }).join('');

    selectHtml = '' +
      '<select ' +
      'class="editable-canvas-text-fontsize-input controls-value-item" ' +
      'name="drawer-size"' +
      'data-name="fontSize"' +
      'value="' + this.options.defaultValues.fontSize + '">' +
      $predefined +
      '</select>';

    var optionItemDefaultClasses = 'toolbar-item-wrapper editable-canvas-text-option editable-canvas-text-fontsize hidden',
      optionItemAdditionalClasses = this.buttonMode ? ' toolbar-button-item ' : '',
      optionItemClasses = optionItemDefaultClasses + optionItemAdditionalClasses;

    result = '' +
      '<li class="' + optionItemClasses + '">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-item-label">' +
      this.drawer.t('Font size:') + ' ' +
      '</span>' +
      '<span class="toolbar-item-valueholder"></span>' +
      '<span class="toolbar-item-icon fa ' + this.buttonIconClass + '"></span>' +
      '</div>' +
      '<div class="toolbar-dropdown-block collapsed">' +
      selectHtml +
      '</div>' +
      '</li>';

    return result;
  };

  pluginsNamespace.TextFontSize = TextFontSize;
}(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.plugins.BaseTextOptionTool));
(function ($, pluginsNamespace, util, BaseTextOptionTool) {
  'use strict';

  /**
   * Creates controls for changing font style of text;
   *
   * @param drawer
   * Instance of drawer
   * @param {Object} options
   * Configuration object.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseTextOptionTool
   */

  var TextFontStyle = function TextFontStyleConstructor(drawer, options) {
    BaseTextOptionTool.call(this, drawer);

    // init options
    this._setupOptions(options);
  };

  TextFontStyle.prototype = Object.create(BaseTextOptionTool.prototype);
  TextFontStyle.prototype.constructor = BaseTextOptionTool;

  TextFontStyle.prototype.name = 'TextFontStyle';
  TextFontStyle.prototype.optionName = 'TextFontStyle';
  TextFontStyle.prototype.onlyPredefined = true;
  TextFontStyle.prototype.focusTextOnChange = true;
  TextFontStyle.prototype.buttonIconClass = 'fa-italic';
  TextFontStyle.prototype.valueType = {
    fontStyle: 'string'
  };

  TextFontStyle.prototype._defaultOptions = {
    defaultValues: {
      fontStyle: ''
    },
    predefined: {
      fontStyle: ["normal", "italic", "oblique"]
    }
  };

  TextFontStyle.prototype.controlTemplate = function () {
    var result,
      $predefined,
      selectHtml;

    $predefined = this.options.predefined.fontStyle.map(function (size, i) {
      return '<option value="' + size + '">' + size + '</option>';
    }).join('');

    selectHtml = '' +
      '<select ' +
      'class="editable-canvas-text-fontstyle-input controls-value-item" ' +
      'name="drawer-size"' +
      'data-name="fontStyle"' +
      'value="' + this.options.defaultValues.fontStyle + '">' +
      $predefined +
      '</select>';

    var optionItemDefaultClasses = 'toolbar-item-wrapper editable-canvas-text-option editable-canvas-text-fontstyle hidden',
      optionItemAdditionalClasses = this.buttonMode ? ' toolbar-button-item ' : '',
      optionItemClasses = optionItemDefaultClasses + optionItemAdditionalClasses;

    result = '' +
      '<li class="' + optionItemClasses + '">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-item-label">' +
      this.drawer.t('Font style:') + ' ' +
      '</span>' +
      '<span class="toolbar-item-valueholder"></span>' +
      '<span class="toolbar-item-icon fa ' + this.buttonIconClass + '"></span>' +
      '</div>' +
      '<div class="toolbar-dropdown-block collapsed">' +
      selectHtml +
      '</div>' +
      '</li>';

    return result;
  };

  pluginsNamespace.TextFontStyle = TextFontStyle;
}(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.plugins.BaseTextOptionTool));
(function ($, pluginsNamespace, util, BaseTextOptionTool) {
  'use strict';

  /**
   * Creates controls for changing font size of text;
   *
   * @param drawer
   * Instance of drawer
   * @param {Object} options
   * Configuration object.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseTextOptionTool
   */

  var TextFontWeight = function TextFontWeightConstructor(drawer, options) {
    BaseTextOptionTool.call(this, drawer);

    // init options
    this._setupOptions(options);
  };

  TextFontWeight.prototype = Object.create(BaseTextOptionTool.prototype);
  TextFontWeight.prototype.constructor = BaseTextOptionTool;


  TextFontWeight.prototype.name = 'TextFontWeight';
  TextFontWeight.prototype.optionName = 'TextFontWeight';
  TextFontWeight.prototype.focusTextOnChange = true;
  TextFontWeight.prototype.onlyPredefined = true;
  TextFontWeight.prototype.buttonIconClass = 'fa-bold';

  TextFontWeight.prototype._defaultOptions = {
    defaultValues: {
      fontWeight: 'normal'
    },
    predefined: {
      fontWeight: ['normal', 'bold', 'light', 100, 200, 300, 400, 500, 600, 700, 800, 900]
    }
  };

  TextFontWeight.prototype.controlTemplate = function () {
    var result,
      $predefined,
      selectHtml;

    $predefined = this.options.predefined.fontWeight.map(function (size, i) {
      return '<option value="' + size + '">' + size + '</option>';
    }).join('');

    selectHtml = '' +
      '<select ' +
      'class="editable-canvas-text-fontweight-input controls-value-item" ' +
      'name="drawer-size"' +
      'data-name="fontWeight"' +
      'value="' + this.options.defaultValues.fontWeight + '">' +
      $predefined +
      '</select>';

    var optionItemDefaultClasses = 'toolbar-item-wrapper editable-canvas-text-option editable-canvas-text-fontweight hidden',
      optionItemAdditionalClasses = this.buttonMode ? ' toolbar-button-item ' : '',
      optionItemClasses = optionItemDefaultClasses + optionItemAdditionalClasses;

    result = '' +
      '<li class="' + optionItemClasses + '">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-item-label">' +
      this.drawer.t('Font Weight:') + ' ' +
      '</span>' +
      '<span class="toolbar-item-valueholder"></span>' +
      '<span class="toolbar-item-icon fa ' + this.buttonIconClass + '"></span>' +
      '</div>' +
      '<div class="toolbar-dropdown-block collapsed">' +
      selectHtml +
      '</div>' +
      '</li>';

    return result;
  };

  pluginsNamespace.TextFontWeight = TextFontWeight;
}(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.plugins.BaseTextOptionTool));
(function ($, pluginsNamespace, util, BaseTextOptionTool) {
  'use strict';

  var classicDefaultValue = 1.16, // Standard value of line height
    fabricDefaultLineHeight = fabric.Text.prototype.lineHeight || classicDefaultValue;

  /**
   * Creates controls for changing line height;
   *
   * @param drawer
   * Instance of drawer
   * @param {Object} options
   * Configuration object.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @augments DrawerJs.plugins.BaseTextOptionTool
   */

  var TextLineHeight = function TextLineHeightConstructor(drawer, options) {
    BaseTextOptionTool.call(this, drawer);

    this._setupOptions(options);
  };

  TextLineHeight.prototype = Object.create(BaseTextOptionTool.prototype);
  TextLineHeight.prototype.constructor = BaseTextOptionTool;

  TextLineHeight.prototype.name = 'TextLineHeight';
  TextLineHeight.prototype.optionName = 'TextLineHeight';
  TextLineHeight.prototype.showOnEditMode = false;
  TextLineHeight.prototype.hideOnEditMode = true;
  TextLineHeight.prototype.stylesToObject = true;
  TextLineHeight.prototype.preventHightlight = true;
  TextLineHeight.prototype.buttonIconClass = 'fa-arrows-v';
  TextLineHeight.prototype.onlyPredefined = true;

  TextLineHeight.prototype.valueType = {
    lineHeight: 'number'
  };

  TextLineHeight.prototype._defaultOptions = {
    defaultValues: {
      lineHeight: fabricDefaultLineHeight
    },
    predefined: {
      lineHeight: [1, 1.16, 1.25, 1.5, 1.75, 2, 3]
    }
  };

  TextLineHeight.prototype.collectDataFromObject = function (tool) {
    var result = {
      lineHeight: tool.lineHeight
    };
    this._lastData = this._lastData || {};
    $.extend(true, this._lastData, result);
    return result;
  };

  TextLineHeight.prototype.controlTemplate = function () {
    var result,
      $predefined,
      selectHtml;

    $predefined = this.options.predefined.lineHeight.map(function (size, i) {
      var sizeInPercent = parseInt(Math.round(size * 100), 10);
      return '<option value="' + size + '">' + sizeInPercent + '%' + '</option>';
    }).join('');

    selectHtml = '' +
      '<select ' +
      'class="editable-canvas-text-lineheight-input controls-value-item" ' +
      'name="drawer-size"' +
      'data-name="lineHeight"' +
      'value="' + this.options.defaultValues.lineHeight + '">' +
      $predefined +
      '</select>';

    var optionItemDefaultClasses = 'toolbar-item-wrapper editable-canvas-text-option editable-canvas-text-lineheight hidden',
      optionItemAdditionalClasses = '' +
        (this.buttonMode ? ' toolbar-button-item ' : '') +
        (this.preventHightlight ? ' prevent-highlight ' : ''),
      optionItemClasses = optionItemDefaultClasses + optionItemAdditionalClasses;

    result = '' +
      '<li class="' + optionItemClasses + '">' +
      '<div class="toolbar-item-description">' +
      '<span class="toolbar-item-label">' +
      this.drawer.t('Line height:') + ' ' +
      '</span>' +
      '<span class="toolbar-item-valueholder"></span>' +
      '<span class="toolbar-item-icon fa ' + this.buttonIconClass + '"></span>' +
      '</div>' +
      '<div class="toolbar-dropdown-block collapsed">' +
      selectHtml +
      '</div>' +
      '</li>';

    return result;
  };

  pluginsNamespace.TextLineHeight = TextLineHeight;
}(jQuery, DrawerJs.plugins, DrawerJs.util, DrawerJs.plugins.BaseTextOptionTool));
(function ($, pluginsNamespace, BaseToolOptions, util) {
  "use strict";

  /**
   * Provides toolbar controls for configuring border size and color.
   * Uses colorpicker plugin, so depends on it.
   *
   * @param {DrawerJs.Drawer} drawer
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {String} options.color
   * Default border color.
   *
   * @param {String} options.borderTypes
   * Object with all borders that will be available for selection
   * <br><br>
   *
   * Example:
   * <code>
   * <pre>
   * "None": {
   *   width: 0,
   *   description: 'None'
   * },
   * "Solid thin": {
   *   width: 1,
   *   preview: this.assetsFolder + 'border-solid-thin.png'
   * },
   * </pre>
   * </code>
   *
   * @param {String} options.defaultBorder
   * Border that will be selected when object is created.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var ShapeBorder = function BorderConfigPlugin(drawer, options) {
    this.optionName = 'border';
    this.name = 'ShapeBorder';
    // call super constructor
    BaseToolOptions.call(this, drawer);

    /**
     * Option name. On selecting tool/object, if this.toolName is in array of
     * object allowed options - tool will show controls
     * @type {String}
     */

    // DrawerJs is created, so basePath is set and  util.getDrawerFolderUrl() works properly.
    // so we can set assets folder
    this.assetsFolder = util.getDrawerFolderUrl() + 'src/';

    // complete default option paths by assets path.
    this._setGlobalClickHandler();
    this._completeDefaultOptionsPaths();

    // setup colorpicker for border color
    this.colorpicker = new pluginsNamespace.ColorpickerControl(this.drawer, this.options);

    this.currentColor = this.options.color;
    this.currentBorder = this.options.borderTypes[this.options.defaultBorder];
    this.activeToolIsShape = false;

    this.drawer.on(this.drawer.EVENT_CANVAS_START_RESIZE, this.hideStyleDropdown.bind(this));
  };

  ShapeBorder.prototype = Object.create(BaseToolOptions.prototype);
  ShapeBorder.prototype.constructor = BaseToolOptions;


  //////////////////////////////////////////////////////////////////////////////////////////
  ShapeBorder.prototype._defaultOptions = {
    color: 'rgba(0, 0, 0, 0)',
    borderTypes: {
      "None": {
        width: 0,
        description: 'None',
        color: 'transparent'
      },
      "Solid thin": {
        width: 1,
        preview: 'border-solid-thin.png'
      },
      "Solid bold": {
        width: 5,
        preview: 'border-solid-bold.png'
      },
      "Dashed thin": {
        width: 1,
        dashArray: [8, 8],
        preview: 'border-dashed-thin.png'
      },
      "Dashed bold": {
        width: 5,
        dashArray: [8, 8],
        preview: 'border-dashed-bold.png'
      }
    },
    defaultBorder: "None"
  };


  /**
   * Complete path to previews by prepending path to assets folder to them
   */
  ShapeBorder.prototype._completeDefaultOptionsPaths = function () {
    for (var borderType in this.options.borderTypes) {
      var borderOption = this.options.borderTypes[borderType];
      if (borderOption.preview) {
        borderOption.preview = this.assetsFolder + borderOption.preview;
      }
    }
  };

  /**
   * Set listeners for clicks - to properly close controls on outside clicks
   */
  ShapeBorder.prototype._setGlobalClickHandler = function () {
    var self = this;
    $(window.document).off('click.DrawerShapeBorder').on('click.DrawerShapeBorder', function (e) {
      var $target = $(e.target),
        isIndicator = $target.hasClass('border-type-indicator');
      if (self.isDropdownVisible && !isIndicator) {
        self.hideStyleDropdown();
      }
    });
  };


  /**
   * When an object is selected on canvas we want to reflect that object's color and border type
   * on the toolbar.
   *
   * This method checks selected object type and if it's a shape it gets that shape's color
   * and border type and sets them as active on the color/border selection toolbar.
   *
   * @param fabricEvent
   * @param fabricEvent
   */
  ShapeBorder.prototype.updateControlsFromObject = function (target) {
    // get color
    this.updateColorFromObject(target);
    // get stroke
    this.updateStrokeFromObject(target);
    // now update controls state
    this.updateControls();
  };


  /**
   * Sets this.currentColor same as object stroke color,
   * or TRASPARENT if object has no stroke
   *
   * @param  {fabric.Object} object
   */
  ShapeBorder.prototype.updateColorFromObject = function (object) {
    var color = null;
    color = object.get('stroke');
    if (color) {
      this.currentColor = color;
    } else {
      this.currentColor = pluginsNamespace.ColorpickerControl.TRANSPARENT;
    }
  };


  /**
   * Sets this.currentBorder same as object stroke,
   * if object border patten matches with one of predefined patterns.
   *
   * @param  {fabric.Object} object
   */
  ShapeBorder.prototype.updateStrokeFromObject = function (object) {
    // get stroke params
    var shapeDashArray = object.get('strokeDashArray');
    var shapeDashWidth = object.get('strokeWidth');
    // search for pattern
    for (var b in this.options.borderTypes) {
      if (this.options.borderTypes.hasOwnProperty(b)) {
        var bType = this.options.borderTypes[b];

        var dashArrayEquals = JSON.stringify(bType.dashArray) === JSON.stringify(shapeDashArray);

        if (!bType.dashArray && !shapeDashArray) {
          dashArrayEquals = true;
        }

        if (dashArrayEquals && bType.width == shapeDashWidth) {
          this.currentBorder = bType;
        }
      }
    }
  };


  /**
   * Apply selected border style to added object.
   *
   * @param evt
   * @param fabricEvent
   */
  ShapeBorder.prototype._onObjectAdded = function (evt, fabricEvent) {
    // do not react on object:added, if canvas is not loaded fully
    if (this.drawer.mode != this.drawer.MODE_ACTIVE)
      return;

    var currentShape = fabricEvent.target;

    this.applyBorderStyle(currentShape);
    this.drawer.fCanvas.renderAll();
  };


  /**
   * Update controls a
   * If activated tool is Line/Arrow:
   * 1) save old values of borderColor and border type
   * 2) make current border color same as drawer active color
   * 3) make current border 'Solid thin'
   * @param {BaseTool} tool
   */
  ShapeBorder.prototype.onActivateTool = function (tool) {

    if (tool instanceof pluginsNamespace.Line ||
      tool instanceof pluginsNamespace.ArrowOneSide ||
      tool instanceof pluginsNamespace.ArrowTwoSide) {
      this.colorBeforeLineShape = this.currentColor;
      this.borderBeforeLineShape = this.currentBorder;

      this.currentColor = this.drawer.activeColor;
      this.currentBorder = this.options.borderTypes["Solid thin"];
      this.updateControls();
    } else if (this.colorBeforeLineShape !== undefined) {
      this.currentColor = this.colorBeforeLineShape;
      this.currentBorder = this.borderBeforeLineShape;

      delete this.colorBeforeLineShape;
      delete this.borderBeforeLineShape;
      this.updateControls();
    }
  };



  /**
   * Creates controls and adds them to toolbar.
   * @param {DrawerToolbar} toolbar to add control to
   */
  ShapeBorder.prototype.createControls = function (toolbar) {
    var _this = this;

    // ------ color button -----------
    this.colorButton = this.colorpicker.createControl(toolbar, this.onColorSelected.bind(this));
    this.colorpicker.setColor(this.options.color);

    // rewrite this, ugh...
    this.colorButton.css('display', 'inline-block');
    this.colorButton.find('.toolbar-label').text(this.drawer.t('Border:'));
    // ------ /color button -----------

    // ------ border type button -----------
    _this.$borderTypeButton = $(
      '<li class="editable-canvas-border-type" ' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      '>' +
      '<span class="toolbar-label editable-canvas-border-type-label">' +
      this.drawer.t('Border type:') + ' ' +
      '</span>' +
      '<span class="border-type-indicator" ' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      '>' +
      '</span>' +
      '<span class="border-type-dropdown toolbar-dropdown-block hidden" ' +
      'data-editable-canvas-sizeable="toolbar-button" ' +
      // 'data-editable-canvas-cssrules="top"' +
      '>' +
      '</span>' +
      '</li>');


    _this.isDropdownVisible = false;

    _this.$borderTypeButton.$dropdown = _this.$borderTypeButton.find('.border-type-dropdown');
    _this.$borderTypeButton.$indicator = _this.$borderTypeButton.find('.border-type-indicator');

    util.bindClick(_this.$borderTypeButton, 'border-type-dropdown', _this.toggleStyleDropdown.bind(_this));

    var $borderTypes = $('<ul></ul>');
    _this.$borderTypeButton.$dropdown.append($borderTypes);

    var borderTypeHandler = function (event) {
      var $target = $(event.target),
        borderTypeName = $target.attr('data-border-type');
      if (borderTypeName) {
        _this.currentBorder = _this.options.borderTypes[borderTypeName];
        _this.onColorSelected();

        event.stopPropagation();
        event.preventDefault();
      }
    };

    for (var borderTypeName in this.options.borderTypes) {
      if (this.options.borderTypes.hasOwnProperty(borderTypeName)) {
        var borderDefinition = this.options.borderTypes[borderTypeName];

        var li = $(
          '<li data-border-type="' + borderTypeName + '"' +
          'data-editable-canvas-sizeable="toolbar-button" ' +
          // 'data-editable-canvas-cssrules="height,line-height">' +
          // 'data-editable-canvas-cssrules="line-height">' +
          '</li>');
        if (borderDefinition.preview) {
          li.css('background-image', 'url(' + borderDefinition.preview + ')');
        }
        if (borderDefinition.description) {
          li.text(borderDefinition.description);
        }
        util.bindClick(li, 'border-type', borderTypeHandler);

        $borderTypes.append(li);
      }
    }

    toolbar.addControl(_this.$borderTypeButton, this.options.buttonOrder);
    // ------ /border type button -----------

    _this.onColorSelected();
    _this.hideControls();
  };


  /**
   * React on user color selection
   * @param  {String} selectedColor
   */
  ShapeBorder.prototype.onColorSelected = function (selectedColor) {
    if (selectedColor) {
      this.currentColor = selectedColor;
    }

    this.updateControls();

    if (this.drawer.fCanvas) {
      var currentShape = this.drawer.fCanvas.getActiveObject();
      if (currentShape) {
        this.applyBorderStyle(currentShape);
      }
      this.drawer.fCanvas.renderAll();
    }
  };


  /**
   * Apply current border style to object
   * @todo @refactor
   * @param  {fabric.Object} fabricObject
   */
  ShapeBorder.prototype.applyBorderStyle = function (fabricObject) {
    if (util.isShape(fabricObject) || fabricObject instanceof fabric.Line) {
      if (this.currentBorder.color) {
        fabricObject.set('stroke', this.currentBorder.color);
      } else {
        fabricObject.set('stroke', this.currentColor);
      }

      fabricObject.set('strokeWidth', this.currentBorder.width);
      if (this.currentBorder.dashArray) {
        fabricObject.set('strokeDashArray', this.currentBorder.dashArray);
      } else {
        fabricObject.set('strokeDashArray', null);
      }
    }
  };


  /**
   * Updates UI to reflect selected border and color.
   * Hides colorpicker if current border is 'None'
   */
  ShapeBorder.prototype.updateControls = function () {
    // update color button
    this.colorpicker.setColor(this.currentColor);

    // hide dropdowns when something is selected
    this.hideStyleDropdown();

    // also hide color button when border None is selected
    if (this.currentBorder.description == 'None') {
      this.colorpicker.hideControls();
    } else {
      this.colorpicker.showControls();
    }

    // set indicator background
    var $indicator = this.$borderTypeButton.find('.border-type-indicator');
    var background = this.currentBorder.preview ? 'url(' + this.currentBorder.preview + ')'
      : 'none';
    $indicator.css('background-image', background);

    // set indicator text
    if (this.currentBorder.description) {
      $indicator.text(this.drawer.t(
        this.currentBorder.description
      ));
    } else {
      $indicator.text('');
    }
  };


  ShapeBorder.prototype.showControls = function () {
    this.colorpicker.showControls();
    this.$borderTypeButton.show();
  };

  ShapeBorder.prototype.hideControls = function () {
    this.colorpicker.hideControls();
    this.$borderTypeButton.hide();
  };


  ShapeBorder.prototype.toggleStyleDropdown = function () {
    var needToShow = !this.isDropdownVisible;
    if (needToShow) {
      this.showStyleDropdown();
    } else {
      this.hideStyleDropdown();
    }
  };

  ShapeBorder.prototype.hideStyleDropdown = function () {
    this.$borderTypeButton.$dropdown.addClass('hidden');
    this.isDropdownVisible = false;
    if (this.$clonedDropdown) {
      this.$clonedDropdown.remove();
    }
  };

  ShapeBorder.prototype.showStyleDropdown = function () {
    this.$borderTypeButton.$dropdown.removeClass('hidden');
    this.isDropdownVisible = true;

    var toolbar = this.drawer && this.drawer.toolbars && this.drawer.toolbars.toolOptionsToolbar,
      toolbarOptions = toolbar && toolbar.options,
      notInsidePopup = toolbarOptions && toolbarOptions.position !== 'popup',
      outside = toolbarOptions && toolbarOptions.positionType === 'outside',
      insideScrollable = toolbarOptions && toolbarOptions.compactType === 'scrollable',
      toolbarHaveScrollable = notInsidePopup && outside && insideScrollable;
    if (toolbarHaveScrollable) {
      var $dropdown = this.$borderTypeButton.$dropdown,
        clone;

      $dropdown.removeClass('hidden');
      clone = $dropdown.clone(true);

      this.$borderTypeButton.$dropdown.closest('.toolbar-placeholder').append(clone);
      clone.addClass('border-type-dropdown-cloned');
      this.$clonedDropdown = clone;

      var drawerSizes = util.getScrollOffset(this.drawer.$canvasEditContainer),
        parentSizes = util.getScrollOffset(this.$borderTypeButton),
        canvasRect = this.drawer.$canvasEditContainer.get(0).getBoundingClientRect();

      var buttonSizes = this.$borderTypeButton.get(0).getBoundingClientRect(),
        buttonCenter = buttonSizes.left + buttonSizes.width / 2,
        clonedDropdownOffset = (buttonCenter - canvasRect.left - (parentSizes.left - drawerSizes.left)) - this.$clonedDropdown.width() / 2;

      var offsetRightFromCss = 12,
        toRightOffset = buttonSizes.left + buttonSizes.width - canvasRect.left - (parentSizes.left - drawerSizes.left) - this.$clonedDropdown.width() - offsetRightFromCss;
      this.$clonedDropdown.css({
        'left': toRightOffset
      });
      $dropdown.addClass('hidden');
    }
  };

  pluginsNamespace.ShapeBorder = ShapeBorder;

}(jQuery, DrawerJs.plugins, DrawerJs.plugins.BaseToolOptions, DrawerJs.util));

(function (jQuery, BaseShape, pluginsNamespace) {
  /**
   * Provides an arrow button which can be used
   * to draw lines with arrow at one side.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * Tool is using drawerInstance.options['lineAngleTooltip']
   * Default settings are:
   * { enabled: false,
   *    fontSize: 11,
   *    fontFamily:  'Arial, sans serif',
   *    color: 'black'};
   *
   * @constructor
   * @memberof DrawerJs.plugins
   * @extends {BaseShape}
   */
  var ArrowOneSide = function ArrowOneSideConstructor(drawerInstance) {
    // call super
    BaseShape.call(this, drawerInstance);

    this.name = 'ArrowOneSide';

    /**
     * List of tool options to show when tool is activated.
     * Deviating from BaseShape tool, Arrow has no 'color', only 'border'.
     * @type {String[]}
     */
    this.toolOptionsList = ['border', 'opacity', 'lineWidth', 'strokeWidth'];

    this.btnClass = 'btn-arrow-one-side';
    this.faClass = 'fa-long-arrow-right';
    this.tooltip = drawerInstance.t('Draw an arrow');
    this.group = {
      name: 'lines',
      tooltip: drawerInstance.t('Lines and arrows')
    };

    this._setupOptions({});
    $.extend(true, this.options.lineAngleTooltip, drawerInstance.options.lineAngleTooltip || {});

    // add fallback for fontFamily
    this.options.lineAngleTooltip.fontFamily += ', ' + this._defaultOptions.lineAngleTooltip.fontFamily;
  };

  ArrowOneSide.prototype = Object.create(BaseShape.prototype);
  ArrowOneSide.prototype.constructor = ArrowOneSide;

  ArrowOneSide.prototype.checkOnlyWidth = true;

  ArrowOneSide.prototype._defaultOptions = {
    lineAngleTooltip: {
      enabled: false,
      fontSize: 11,
      fontFamily: 'Arial, sans serif',
      color: 'black'
    }
  };


  /**
   * Create new shape with minimal size.
   * Is called from BaseShape nmouseDown handler.
   *
   * @param  {Number} left [description]
   * @param  {Number} top  [description]
   * @return {fabric.ErasableLine}
   */
  ArrowOneSide.prototype.createShape = function (left, top) {
    var arrow = new fabric.ErasableArrow([left, top, left + 1, top + 1], {
      oneSided: true
    });
    arrow.set('strokeWidth', this.drawerInstance.lineStrokeWidth || 1);
    arrow.set('stroke', this.drawerInstance.activeColor);
    arrow.set('opacity', this.drawerInstance.activeOpacity);
    arrow.set('oneSided', true);

    this.createAngleTooltip(arrow);

    return arrow;
  };


  /**
   * Update shape with new left, top,
   * Is called from BaseShape mouseMove handler
   *
   * @param  {fabric.Line} line    [description]
   * @param  {Number} newLeft [description]
   * @param  {Number} newTop  [description]
   */
  ArrowOneSide.prototype.updateShape = function (arrow, newLeft, newTop) {
    arrow.set('y2', newTop);
    arrow.set('x2', newLeft);

    this.updateAngleTooltip(arrow);
  };


  /**
   * Is called from BaseShape mouseUp handler.
   */
  ArrowOneSide.prototype.finishShape = function (left, top) {
    this.removeAngleTooltip();

  };


  /**
   * Create text object for line angle tooltip
   *
   * @param  {fabric.Line} line
   */
  ArrowOneSide.prototype.createAngleTooltip = function (line) {
    if (this.options.lineAngleTooltip.enabled) {
      this.angleTooltip = new fabric.IText('Text');
      this.angleTooltip.set('fontFamily', this.options.lineAngleTooltip.fontFamily);
      this.angleTooltip.set('fontSize', this.options.lineAngleTooltip.fontSize);
      this.angleTooltip.set('left', line.x1 - 10);
      this.angleTooltip.set('top', line.y1 - 10);
      this.angleTooltip.set('stroke', this.options.lineAngleTooltip.color);
      this.angleTooltip.set('fill', this.options.lineAngleTooltip.color);
      this.angleTooltip.set('text', '');

      this.drawerInstance.fCanvas.add(this.angleTooltip);
      this.updateAngleTooltip(line);
    }
  };


  /**
   * Update angle tooltip with line current angle
   *
   * @param  {fabric.Line} line
   */
  ArrowOneSide.prototype.updateAngleTooltip = function (line) {
    if (!this.options.lineAngleTooltip.enabled)
      return;

    // calc line angle
    var angleRad = Math.atan((line.y2 - line.y1) / (line.x2 - line.x1));
    var angle = Math.abs(fabric.util.radiansToDegrees(angleRad));
    this.angleTooltip.setText(angle.toFixed().toString());

    // determine tooltip position
    var tooltipOffsetX = this.options.lineAngleTooltip.fontSize;
    var tooltipOffsetY = -this.options.lineAngleTooltip.fontSize;
    // if line is pointing to the left
    if (line.x2 < line.x1) {
      tooltipOffsetX = -(this.options.lineAngleTooltip.fontSize + 10);
    }
    // if line is pointing downside
    if (line.y2 > line.y1) {
      tooltipOffsetY = 2;
    }

    this.angleTooltip.set('left', line.x1 + tooltipOffsetX);
    this.angleTooltip.set('top', line.y1 + tooltipOffsetY);
    // this is needed to overpower strange issue, when tooltip is always same color as line
    this.angleTooltip.set('stroke', this.options.lineAngleTooltip.color);
    this.angleTooltip.set('fill', this.options.lineAngleTooltip.color);
  };


  /**
   * Removes angle tooltip.
   */
  ArrowOneSide.prototype.removeAngleTooltip = function () {
    if (this.options.lineAngleTooltip.enabled) {
      if (this.angleTooltip) {
        this.angleTooltip.remove();
        delete this.tooltip;
      }
    }
  };


  pluginsNamespace.ArrowOneSide = ArrowOneSide;

}(jQuery, DrawerJs.plugins.BaseShape, DrawerJs.plugins));
(function (jQuery, BaseShape, pluginsNamespace) {
  /**
   * Provides an arrow button which can be used
   * to draw lines with arrow at both sides.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * Tool is using drawerInstance.options['lineAngleTooltip']
   * Default settings are:
   * { enabled: false,
   *    fontSize: 11,
   *    fontFamily:  'Arial, sans serif',
   *    color: 'black'};
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var ArrowTwoSide = function ArrowTwoSizeConstructor(drawerInstance) {
    // call super
    BaseShape.call(this, drawerInstance);

    this.name = 'ArrowTwoSide';
    /**
     * List of tool options to show when tool is activated.
     * Deviating from BaseShape tool, Arrow has no 'color', only 'border'.
     * @type {String[]}
     */
    this.toolOptionsList = ['border', 'opacity', 'lineWidth', 'strokeWidth'];

    this.btnClass = 'btn-arrow-two-side';
    this.faClass = 'fa-arrows-h';
    this.tooltip = drawerInstance.t('Draw a two-sided arrow');
    this.group = {
      name: 'lines',
      tooltip: drawerInstance.t('Lines and arrows')
    };

    this._setupOptions({});
    $.extend(true, this.options.lineAngleTooltip, drawerInstance.options.lineAngleTooltip || {});

    // add fallback for fontFamily
    this.options.lineAngleTooltip.fontFamily += ', ' + this._defaultOptions.lineAngleTooltip.fontFamily;
  };

  ArrowTwoSide.prototype = Object.create(BaseShape.prototype);
  ArrowTwoSide.prototype.constructor = ArrowTwoSide;

  ArrowTwoSide.prototype.checkOnlyWidth = true;

  ArrowTwoSide.prototype._defaultOptions = {
    lineAngleTooltip: {
      enabled: false,
      fontSize: 11,
      fontFamily: 'Arial, sans serif',
      color: 'black'
    }
  };


  /**
   * Create new shape with minimal size.
   * Is called from BaseShape nmouseDown handler.
   *
   * @param  {Number} left [description]
   * @param  {Number} top  [description]
   * @return {fabric.ErasableLine}
   */
  ArrowTwoSide.prototype.createShape = function (left, top) {
    var arrow = new fabric.ErasableArrow([left, top, left + 1, top + 1], {
      oneSided: false
    });
    arrow.set('strokeWidth', this.drawerInstance.lineStrokeWidth || 1);
    arrow.set('stroke', this.drawerInstance.activeColor);
    arrow.set('opacity', this.drawerInstance.activeOpacity);
    arrow.set('oneSided', false);

    this.createAngleTooltip(arrow);

    return arrow;
  };


  /**
   * Update shape with new left, top,
   * Is called from BaseShape mouseMove handler
   *
   * @param  {fabric.Line} line    [description]
   * @param  {Number} newLeft [description]
   * @param  {Number} newTop  [description]
   */
  ArrowTwoSide.prototype.updateShape = function (arrow, newLeft, newTop) {
    arrow.set('y2', newTop);
    arrow.set('x2', newLeft);

    this.updateAngleTooltip(arrow);
  };


  /**
   * Is called from BaseShape mouseUp handler.
   */
  ArrowTwoSide.prototype.finishShape = function (left, top) {
    this.removeAngleTooltip();
  };



  /**
   * Create text object for line angle tooltip
   *
   * @param  {fabric.Line} line
   */
  ArrowTwoSide.prototype.createAngleTooltip = function (line) {
    if (this.options.lineAngleTooltip.enabled) {
      this.angleTooltip = new fabric.IText('Text');
      this.angleTooltip.set('fontFamily', this.options.lineAngleTooltip.fontFamily);
      this.angleTooltip.set('fontSize', this.options.lineAngleTooltip.fontSize);
      this.angleTooltip.set('left', line.x1 - this.options.lineAngleTooltip.fontSize);
      this.angleTooltip.set('top', line.y1 - this.options.lineAngleTooltip.fontSize);
      this.angleTooltip.set('stroke', this.options.lineAngleTooltip.color);
      this.angleTooltip.set('fill', this.options.lineAngleTooltip.color);
      this.angleTooltip.set('text', '');

      this.drawerInstance.fCanvas.add(this.angleTooltip);
      this.updateAngleTooltip(line);
    }
  };


  /**
   * Update angle tooltip with line current angle
   *
   * @param  {fabric.Line} line
   */
  ArrowTwoSide.prototype.updateAngleTooltip = function (line) {
    if (!this.options.lineAngleTooltip.enabled)
      return;

    // calc line angle
    var angleRad = Math.atan((line.y2 - line.y1) / (line.x2 - line.x1));
    var angle = Math.abs(fabric.util.radiansToDegrees(angleRad));
    this.angleTooltip.setText(angle.toFixed().toString());

    // determine tooltip position
    var tooltipOffsetX = this.options.lineAngleTooltip.fontSize;
    var tooltipOffsetY = -this.options.lineAngleTooltip.fontSize;
    // if line is pointing to the left
    if (line.x2 < line.x1) {
      tooltipOffsetX = -(this.options.lineAngleTooltip.fontSize + 10);
    }
    // if line is pointing downside
    if (line.y2 > line.y1) {
      tooltipOffsetY = 2;
    }

    this.angleTooltip.set('left', line.x1 + tooltipOffsetX);
    this.angleTooltip.set('top', line.y1 + tooltipOffsetY);
    // this is needed to overpower strange issue, when tooltip is always same color as line
    this.angleTooltip.set('stroke', this.options.lineAngleTooltip.color);
    this.angleTooltip.set('fill', this.options.lineAngleTooltip.color);
  };



  /**
   * Removes angle tooltip.
   */
  ArrowTwoSide.prototype.removeAngleTooltip = function () {
    if (this.options.lineAngleTooltip.enabled) {
      if (this.angleTooltip) {
        this.angleTooltip.remove();
        delete this.tooltip;
      }
    }
  };


  pluginsNamespace.ArrowTwoSide = ArrowTwoSide;

}(jQuery, DrawerJs.plugins.BaseShape, DrawerJs.plugins));
(function ($, BaseShape, pluginsNamespace) {
  /**
   * Provides a circle button which can be used to draw circles.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {String} [options.centeringMode='normal']
   * Defines centering method when drawing a shape.
   * <br><br>
   * Valid values are:
   * <br><br>
   * <code>normal</code>: circle's top left corner will be placed to the
   * position of first mouse click and will be resized from that point.
   * <br><br>
   * <code>from_center</code>: circle's center point will be placed to the
   * position of first mouse click and will be resized from center.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var Circle = function CircleConstructor(drawerInstance, options) {
    var _this = this;

    BaseShape.call(_this, drawerInstance);

    this.name = 'Circle';
    this.btnClass = 'btn-circle';
    this.faClass = 'fa-circle';
    this.tooltip = drawerInstance.t('Draw a circle');

    this.options = options || {};
    this.centeringMode =
      this.options.centeringMode || BaseShape.CENTERING_MODE.NORMAL;
  };

  Circle.prototype = Object.create(BaseShape.prototype);
  Circle.prototype.constructor = Circle;

  Circle.prototype.minShapeSize = 5;

  Circle.prototype.createShape = function (left, top) {
    this.startLeft = left;
    this.startTop = top;

    var circle = new fabric.PCircle({
      left: left,
      top: top,
      radius: 1,
      fill: this.drawerInstance.activeColor,
      opacity: this.drawerInstance.activeOpacity
    });

    return circle;
  };

  Circle.prototype.updateShape = function (circle, newLeft, newTop) {
    var width = newLeft - this.startLeft;
    var height = newTop - this.startTop;

    var widthAbs = Math.abs(width);
    var heightAbs = Math.abs(height);

    var radius = widthAbs < heightAbs ? widthAbs : heightAbs;

    radius = Math.abs(radius);
    radius = radius / 2;

    if (this.centeringMode == BaseShape.CENTERING_MODE.FROM_CENTER) {
      radius *= 2;
      circle.set('left', this.startLeft - radius);
      circle.set('top', this.startTop - radius);
    }

    if (this.centeringMode == BaseShape.CENTERING_MODE.NORMAL) {
      if (width < 0) {
        circle.set('left', this.startLeft - (radius * 2));
      } else {
        circle.set('left', this.startLeft);
      }

      if (height < 0) {
        circle.set('top', this.startTop - (radius * 2));
      } else {
        circle.set('top', this.startTop);
      }
    }

    circle.set('radius', radius);
  };

  pluginsNamespace.Circle = Circle;
}(jQuery, DrawerJs.plugins.BaseShape, DrawerJs.plugins));
(function ($, BaseTool, pluginsNamespace, util) {
  /**
   * Tool to add and upload image to canvas.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {String} [options.maxImageSizeKb='5120']
   * Max size of image to upload in KB. Default is 5 MB;
   * <br><br>
   * If null, or 0 is set - then size is unlimited.
   * Negative values are considered as 0;
   *
   * @param {Boolean} [options.scaleDownLargeImage=true]
   * If set to true - images. larger then canvas, will be scaled down
   *
   * @param {String[]} [options.acceptedMIMETypes=['image/jpeg', 'image/png', 'image/gif']]
   * If set to true - images. larger then canvas, will be scaled down
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var ImageTool = function ImageConstructor(drawerInstance, options) {
    var _this = this;

    BaseTool.call(_this, drawerInstance);

    this.drawer = drawerInstance;
    this.name = 'Image';
    this.btnClass = 'btn-image';
    this.faClass = 'fa-image';
    this.tooltip = drawerInstance.t('Insert an image');

    this._setupOptions(options);
  };

  // Derive ImageTool from BaseTool
  ImageTool.prototype = Object.create(BaseTool.prototype);
  ImageTool.prototype.constructor = ImageTool;

  /**
   * Default options
   * @type {{defaultMaxSize: string, scaleDownLargeImage: boolean, acceptedMIMETypes: string[]}}
   */
  ImageTool.prototype._defaultOptions = {
    maxImageSizeKb: 5120, // 5 MB
    scaleDownLargeImage: true,
    centerImage: true,
    cropIsActive: true,
    acceptedMIMETypes: ['image/jpeg', 'image/png', 'image/gif']
  };


  /**
   * Tool activation method.
   * Is called in lifecycle of event Drawer.EVENT_DO_ACTIVATE_TOOL.
   * Calls  BaseTool._activateTool .
   *
   * @private
   */
  ImageTool.prototype._activateTool = function () {
    var _this = this;
    this.drawerInstance.log('TOOL', 'Image._activateTool()');
    BaseTool.prototype._activateTool.call(this);

    this._showDialog();

    // deactivate tool. Slight delay is needed, because without it
    // tool is deactivated before listeners on EVENT_DO_ACTIVATE_TOOL in drawer are executed
    // which lead to incorrect way of setting drawer.lastUsedPluginName
    util.setTimeout(function () {
      _this.drawerInstance.trigger(_this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL, [_this]);
    }, 300);

  };


  /**
   * Shows file open dialog
   * @private
   */
  ImageTool.prototype._showDialog = function () {
    var acceptedMIMEStr = this.options.acceptedMIMETypes.join(',');
    $el = $('<input type="file" accept="' + acceptedMIMEStr + '">');
    $el.on('change', this._processFileInput.bind(this));

    $el.click();

  };

  /**
   * Callback to process user selected files.
   *
   * @param {Event} e
   * @private
   */
  ImageTool.prototype._processFileInput = function (e) {
    var _this = this;
    var files = e.target.files;

    // check there was file choosen
    if (files.length < 1) {
      _this.drawerInstance.showError(this.drawerInstance.t('No file was selected!'));
      return;
    }
    var file = files[0];

    // check file
    if (!this._checkFile(file)) {
      return;
    }

    var fileReader = new FileReader();
    // on file load - create HTML5 Image from it
    fileReader.onload = function (onloadEvent) {
      _this.drawerInstance.log('IMAGE LOADED:', file.name);
      var triggerImageCrop = _this.options.cropIsActive && _this.drawer._pluginsInstances.ImageCrop;
      if (triggerImageCrop) {
        _this._triggerImageCrop(fileReader.result);
      } else {
        _this.loadImage(fileReader.result);
      }
    };

    fileReader.readAsDataURL(file);
  };


  /**
   * Makes some checks  to file.
   *
   * @param {File} file
   * @returns {boolean}
   * @private
   */
  ImageTool.prototype._checkFile = function (file) {
    var _this = this;
    // crude check of file type
    if (file.type.indexOf('image') < 0) {
      _this.drawerInstance.showError(this.drawerInstance.t('Incorrect file type!'));
      return false;
    }

    // check for maxSize
    if ((this.options.maxImageSizeKb > 0) &&
      (file.size > this.options.maxImageSizeKb * 1024)) {
      var err = this.drawerInstance.t('File is to big!. Maximum file size is ');
      err = err + this.options.maxImageSizeKb + ' KB';
      _this.drawerInstance.showError(err);
      return false;
    }

    return true;
  };


  /**
   * Load image from url/dataUrl, then call addImage()
   *
   * @param {string} dataUrl src of image or dataUrl
   * @private
   */
  ImageTool.prototype.loadImage = function (dataUrl, options) {
    var _this = this;
    var image = new Image();

    // after Image was created from file data - create fabric.Image from it
    image.onload = function () {
      _this.addImage(image, options);
    };

    // show error on fail
    image.onerror = function () {
      var err = _this.drawerInstance.t('Image failed to create!');
      _this.drawerInstance.showError(err);
    };

    // this will start creating image
    image.src = dataUrl;
  };

  /**
   * Init crop plugin
   * @param {string} image - src of image, can be base64 encoded url
   * @private
   */
  ImageTool.prototype._triggerImageCrop = function (image) {
    var dataToEvent = {
      url: image,
      callback: this.loadImage.bind(this)
    };
    this.drawerInstance.trigger(this.drawerInstance.EVENT_IMAGE_CROP, dataToEvent);
  };

  /**
   * Adds image to canvas.
   *
   * @param {Image} image
   */
  ImageTool.prototype.addImage = function (image, options) {
    var fCanvas = this.drawerInstance.fCanvas;
    var fabricImage = new fabric.ErasableImage(image);

    options = options ? options : this.options;
    if (options.scaleDownLargeImage) {
      this._fitLargeImage(fabricImage);
    } else {
      fabricImage.left = options.left ? options.left : 0;
      fabricImage.top = options.top ? options.top : 0;
      fabricImage.scaleX = options.scaleX ? options.scaleX : 1;
      fabricImage.scaleY = options.scaleY ? options.scaleY : 1;
    }
    fabricImage.opacity = this.drawerInstance.activeOpacity;


    if (options.centerImage) {
      fCanvas.centerObject(fabricImage);
    }

    fCanvas.add(fabricImage);
    fCanvas.setActiveObject(fabricImage);
  };


  /**
   * If option options.scaleDownLargeImage is set,
   * scales images, larger then canvas to fit it.
   *
   * @param {fabric.Image} fImage
   * @private
   */
  ImageTool.prototype._fitLargeImage = function (fImage) {
    var fCanvas = this.drawerInstance.fCanvas;

    var w = fCanvas.width * 0.95;
    var h = fCanvas.height * 0.95;
    var scaleX = 1.0, scaleY = 1.0;
    if (fImage.width > w) {
      scaleX = w / fImage.width;
    }
    if (fImage.height > h) {
      scaleY = h / fImage.height;
    }
    var scale = Math.min(scaleX, scaleY);
    fImage.set({ 'scaleX': scale, 'scaleY': scale });
  };

  pluginsNamespace.Image = ImageTool;

}(jQuery, DrawerJs.plugins.BaseTool, DrawerJs.plugins, DrawerJs.util));

(function (DrawerApi) {

  /**
   * Sets background image from given url.
   *
   * @param {String}   imageUrl
   * @param {Function} callback callback on success
   */
  DrawerApi.prototype.addImageFromUrl = function (imageUrl, options) {
    this.drawer.api.checkIsActive();
    var tool = this.drawer.getPluginInstance('Image');
    tool.loadImage(imageUrl, options);
  };

  /**
   * Sets background image from given image object.
   *
   * @param {Image}   image
   * @param {Object}   options
   * @param {Function} callback callback on success
   */
  DrawerApi.prototype.addImage = function (image, options) {
    this.drawer.api.checkIsActive();
    var tool = this.drawer.getPluginInstance('Image');
    tool.addImage(image, options);
  };

})(DrawerJs.DrawerApi);

(function ($, BaseShape, pluginsNamespace) {
  /**
   * Provides a line button which can be used to draw lines.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * Tool is using drawerInstance.options['lineAngleTooltip']
   * Default settings are:
   * {  enabled: false,
   *    fontSize: 11,
   *    fontFamily:  'Arial, sans serif',
   *    color: 'black'};
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var Line = function LineConstructor(drawerInstance) {
    // call super
    BaseShape.call(this, drawerInstance);

    this.name = 'Line';
    /**
     * List of tool options to show when tool is activated.
     * Deviating from BaseShape tool, Line has no 'color', only 'border'.
     * @type {String[]}
     */
    this.toolOptionsList = ['border', 'opacity', 'lineWidth', 'strokeWidth'];

    this.btnClass = 'btn-line';
    this.faClass = 'fa-line';
    this.tooltip = drawerInstance.t('Draw a line');

    this.group = {
      name: 'lines',
      tooltip: drawerInstance.t('Lines and arrows')
    };

    this._setupOptions({});
    $.extend(true, this.options.lineAngleTooltip, drawerInstance.options.lineAngleTooltip || {});

    // add fallback for fontFamily
    this.options.lineAngleTooltip.fontFamily += ', ' + this._defaultOptions.lineAngleTooltip.fontFamily;
  };

  Line.prototype = Object.create(BaseShape.prototype);
  Line.prototype.constructor = Line;

  Line.prototype.checkOnlyWidthOrHeight = true;

  Line.prototype._defaultOptions = {
    lineAngleTooltip: {
      enabled: false,
      fontSize: 11,
      fontFamily: 'Arial, sans serif',
      color: 'black'
    }
  };


  /**
   * Create new shape with minimal size.
   * Is called from BaseShape nmouseDown handler.
   *
   * @param  {Number} left [description]
   * @param  {Number} top  [description]
   * @return {fabric.ErasableLine}
   */
  Line.prototype.createShape = function (left, top) {
    var line = new fabric.ErasableLine();

    line.set('x1', left);
    line.set('x2', left + 1);
    line.set('y1', top);
    line.set('y2', top + 1);
    line.set('stroke', this.drawerInstance.activeColor);
    line.set('opacity', this.drawerInstance.activeOpacity);
    line.set('strokeWidth', this.drawerInstance.lineStrokeWidth || 2);

    this.createAngleTooltip(line);

    return line;
  };


  /**
   * Update shape with new left, top,
   * Is called from BaseShape mouseMove handler
   *
   * @param  {fabric.Line} line    [description]
   * @param  {Number} newLeft [description]
   * @param  {Number} newTop  [description]
   */
  Line.prototype.updateShape = function (line, newLeft, newTop) {
    line.set('x2', newLeft);
    line.set('y2', newTop);

    this.updateAngleTooltip(line);
  };


  /**
   * Is called from BaseShape mouseUp handler.
   */
  Line.prototype.finishShape = function () {
    this.removeAngleTooltip();
  };


  /**
   * Create text object for line angle tooltip
   *
   * @param  {fabric.Line} line
   */
  Line.prototype.createAngleTooltip = function (line) {
    if (!this.options.lineAngleTooltip.enabled)
      return;

    this.angleTooltip = new fabric.IText('Text');
    this.angleTooltip.set('fontFamily', this.options.lineAngleTooltip.fontFamily);
    this.angleTooltip.set('fontSize', this.options.lineAngleTooltip.fontSize);
    this.angleTooltip.set('left', line.x1 - 10);
    this.angleTooltip.set('top', line.y1 - 10);
    this.angleTooltip.set('stroke', this.options.lineAngleTooltip.color);
    this.angleTooltip.set('fill', this.options.lineAngleTooltip.color);
    this.angleTooltip.set('text', '');

    this.drawerInstance.fCanvas.add(this.angleTooltip);
    this.updateAngleTooltip(line);
  };


  /**
   * Update angle tooltip with line current angle
   *
   * @param  {fabric.Line} line
   */
  Line.prototype.updateAngleTooltip = function (line) {
    if (!this.options.lineAngleTooltip.enabled)
      return;

    // calc line angle
    var angleRad = Math.atan((line.y2 - line.y1) / (line.x2 - line.x1));
    var angle = Math.abs(fabric.util.radiansToDegrees(angleRad));
    this.angleTooltip.setText(angle.toFixed().toString());

    // determine tooltip position
    var tooltipOffsetX = this.options.lineAngleTooltip.fontSize;
    var tooltipOffsetY = -this.options.lineAngleTooltip.fontSize;
    // if line is pointing to the left
    if (line.x2 < line.x1) {
      tooltipOffsetX = -(this.options.lineAngleTooltip.fontSize + 10);
    }
    // if line is pointing downside
    if (line.y2 > line.y1) {
      tooltipOffsetY = 2;
    }

    this.angleTooltip.set('left', line.x1 + tooltipOffsetX);
    this.angleTooltip.set('top', line.y1 + tooltipOffsetY);
    // this is needed to overpower strange issue, when tooltip is always same color as line
    this.angleTooltip.set('stroke', this.options.lineAngleTooltip.color);
    this.angleTooltip.set('fill', this.options.lineAngleTooltip.color);
  };


  /**
   * Removes angle tooltip.
   */
  Line.prototype.removeAngleTooltip = function () {
    if (this.options.lineAngleTooltip.enabled) {
      if (this.angleTooltip) {
        this.angleTooltip.remove();
        delete this.tooltip;
      }
    }
  };

  pluginsNamespace.Line = Line;

}(jQuery, DrawerJs.plugins.BaseShape, DrawerJs.plugins));
(function ($, BaseShape, pluginsNamespace, util) {
  "use strict";

  var MOUSE_DOWN = util.mouseDown('Polygon');
  var MOUSE_MOVE = util.mouseMove('Polygon');
  var MOUSE_UP = util.mouseUp('Polygon');

  /**
   * Provides a polygon button which can be used to draw polygons.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var Polygon = function PolygonConstructor(drawerInstance, options) {
    var _this = this;

    BaseShape.call(_this, drawerInstance);

    this.name = 'Polygon';
    this.btnClass = 'btn-polygon';
    this.faClass = 'fa-star';
    this.tooltip = drawerInstance.t('Draw a Polygon');
    this.helpTooltipText = drawerInstance.t('Click to start a new line');

    this.options = options || {};
    this.centeringMode =
      this.options.centeringMode || BaseShape.CENTERING_MODE.NORMAL;

    drawerInstance.on(drawerInstance.EVENT_EDIT_STOP, function () {
      _this.finishDraw();
    });

    drawerInstance.on(drawerInstance.EVENT_DO_ACTIVATE_TOOL,
      function (event, tool) {
        if (!(tool instanceof Polygon)) {
          _this.finishDraw();
        }
      });
  };

  Polygon.prototype = Object.create(BaseShape.prototype);
  Polygon.prototype.constructor = Polygon;

  Polygon.prototype.setUpHandlers = function () {
    var _this = this;

    _this.drawerInstance.fCanvas._oldSelectionState = _this.drawerInstance.fCanvas.selection;
    _this.drawerInstance.fCanvas.selection = false;

    _this.$stopButton = $('<button class="stop-polygon">' +
      _this.drawerInstance.t('Stop drawing a polygon') +
      '</button>');
    _this.drawerInstance.$canvasEditContainer.append(_this.$stopButton);
    _this.$stopButton.click(function () {
      _this.finishDraw();
    });

    _this.drawerInstance
      .on(_this.drawerInstance.EVENT_KEYDOWN, function (event, originalEvent) {
        if (originalEvent.which == 27) {
          _this.finishDraw();
        }
      });

    var drawingArea = $(this.drawerInstance.fCanvas.upperCanvasEl);

    var newPointHandler = function (event) {
      _this.addNewPoint(event);

      if (!_this.poly.__mouseEventSet) {
        _this.setUpMoveEvent();
        _this.poly.__mouseEventSet = true;
      }

      _this.drawerInstance.fCanvas.renderAll();
    };

    if (_this.drawerInstance.touchDevice) {
      drawingArea.on(MOUSE_DOWN, function (event) {
        newPointHandler(event);
        event.preventDefault();
        event.stopPropagation();
        return false;
      });

      drawingArea.on(MOUSE_UP, function (event) {
        drawingArea.off(MOUSE_MOVE);
        _this.poly.__mouseEventSet = false;
        event.preventDefault();
        event.stopPropagation();
        return false;
      });
    } else {
      drawingArea.on(MOUSE_DOWN, function (event) {
        newPointHandler(event);
        event.preventDefault();
        event.stopPropagation();
        return false;
      });
    }
  };

  Polygon.prototype.addNewPoint = function (event) {
    var _this = this,
      pointCoords = this.drawer.fCanvas.getPointer(event),
      left = pointCoords.x,
      top = pointCoords.y;

    if (!_this.poly) {
      _this.startLeft = left;
      _this.startTop = top;

      _this.poly = new fabric.SegmentablePolygon([[
        {
          x: 0,
          y: 0
        }
      ]]);
      _this.poly.set('fill', _this.drawerInstance.activeColor);
      _this.poly.set('opacity', _this.drawerInstance.activeOpacity);
      _this.poly.set('left', left);
      _this.poly.set('top', top);
      _this.poly.set('evented', false);
      _this.poly.set('selectable', false);
      _this.drawerInstance.fCanvas.add(_this.poly);
    }

    var points = _this.poly.get('points');

    var centerPoint = _this.poly.getCenterPoint();
    left = left - centerPoint.x;
    top = top - centerPoint.y;

    points[0].push({
      x: left,
      y: top
    });

    _this.poly.set('points', points);

    _this.fixPosition();
  };

  Polygon.prototype.setUpMoveEvent = function () {
    var _this = this;
    var drawingArea = $(this.drawerInstance.fCanvas.upperCanvasEl);

    drawingArea.on(MOUSE_MOVE, function (event) {
      var points = _this.poly.get('points'),
        pointCoords = _this.drawerInstance.fCanvas.getPointer(event),
        centerPoint = _this.poly.getCenterPoint(),
        left = pointCoords.x - centerPoint.x,
        top = pointCoords.y - centerPoint.y;

      if (points[0].length >= 2) {
        points[0][points[0].length - 1].x = left;
        points[0][points[0].length - 1].y = top;

        _this.poly.set('points', points);

        _this.fixPosition();

        _this.drawerInstance.fCanvas.renderAll();
      }
    });
  };

  Polygon.prototype.fixPosition = function () {
    var _this = this;

    _this.poly._fixPoints();
    _this.poly.setCoords();

    var points = _this.poly.get('points');

    var centerPoint = _this.poly.getCenterPoint();
    var firstPointX = centerPoint.x + points[0][0].x;
    var firstPointY = centerPoint.y + points[0][0].y;

    var firstPointGlobalX = firstPointX;
    var firstPointGlobalY = firstPointY;

    var xDiff = _this.startLeft - firstPointGlobalX;
    var yDiff = _this.startTop - firstPointGlobalY;
    _this.poly.set('left', _this.poly.get('left') + xDiff);
    _this.poly.set('top', _this.poly.get('top') + yDiff);
  };

  Polygon.prototype.finishDraw = function () {
    var _this = this;

    _this.drawerInstance.fCanvas.selection = _this.drawerInstance.fCanvas._oldSelectionState;

    var drawingArea = $(this.drawerInstance.fCanvas.upperCanvasEl);

    drawingArea.off(MOUSE_MOVE);
    drawingArea.off(MOUSE_DOWN);
    drawingArea.off(MOUSE_UP);

    util.unbindLongPress(drawingArea, 'polygon');

    if (_this.poly) {
      // only remove last point for non-touch device
      if (!_this.drawerInstance.touchDevice) {
        var points = this.poly.get('points');
        points[0].splice(points[0].length - 1, 1);
        this.poly.set('points', points);
      }

      this.fixPosition();

      _this.poly.set('evented', true);
      _this.poly.set('selectable', true);

      var cloned = this.poly.clone();
      this.drawerInstance.fCanvas.add(cloned);
      this.drawerInstance.fCanvas.remove(this.poly);
    }

    this.poly = null;

    this.drawerInstance.fCanvas.renderAll();
    if (_this.$stopButton) {
      util.setTimeout(function () {
        _this.$stopButton.remove();
      }, 1);
    }

    this._deactivateTool();
  };

  pluginsNamespace.Polygon = Polygon;

}(jQuery, DrawerJs.plugins.BaseShape, DrawerJs.plugins, DrawerJs.util));
(function ($, BaseShape, pluginsNamespace) {
  /**
   * Provides a rectangle button which can be used to draw rectangles.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {String} [options.centeringMode='normal']
   * Defines centering method when drawing a shape.
   * <br><br>
   * Valid values are:
   * <br><br>
   * <code>normal</code>: rectangle's top left corner will be placed to the
   * position of first mouse click and will be resized from that point.
   * <br><br>
   * <code>from_center</code>: rectangle's center point will be placed to the
   * position of first mouse click and will be resized from center.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var Rectangle = function RectangleConstructor(drawerInstance, options) {
    var _this = this;

    BaseShape.call(_this, drawerInstance);

    this.name = 'Rectangle';
    this.btnClass = 'btn-rectangle';
    this.faClass = 'fa-stop';
    this.tooltip = drawerInstance.t('Draw a rectangle');

    this.options = options || {};
    this.centeringMode =
      this.options.centeringMode || BaseShape.CENTERING_MODE.NORMAL;
  };

  Rectangle.prototype = Object.create(BaseShape.prototype);
  Rectangle.prototype.constructor = Rectangle;

  Rectangle.prototype.createShape = function (left, top) {
    this.startLeft = left;
    this.startTop = top;

    var rect = new fabric.PRect({
      left: left,
      top: top,
      height: 1,
      width: 1,
      fill: this.drawerInstance.activeColor,
      opacity: this.drawerInstance.activeOpacity
    });

    return rect;
  };

  Rectangle.prototype.updateShape = function (rectangle, newLeft, newTop) {
    var width = newLeft - this.startLeft;
    var height = newTop - this.startTop;

    if (this.centeringMode == BaseShape.CENTERING_MODE.FROM_CENTER) {
      width *= 2;
      height *= 2;
      rectangle.set('left', newLeft - width);
      rectangle.set('top', newTop - height);
    }

    if (width > 0) {
      rectangle.set('width', width);
    } else {
      rectangle.set('left', newLeft);
      rectangle.set('width', width * -1);
    }

    if (height > 0) {
      rectangle.set('height', height);
    } else {
      rectangle.set('top', newTop);
      rectangle.set('height', height * -1);
    }

  };

  pluginsNamespace.Rectangle = Rectangle;

}(jQuery, DrawerJs.plugins.BaseShape, DrawerJs.plugins));
(function ($, BaseShape, pluginsNamespace, util) {
  "use strict";

  /**
   * Provides an editable text shape.
   *
   * @param drawerInstance
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {number} options.editIconSize
   * Sets the 'edit icon' size, in pixels.
   * Default is 32px. More then 64px is not recommended.
   *
   * @param {boolean} options.editIconMode
   * Sets if 'edit icon' mode is on.
   *
   * @param {object.<string, array>} options.predefined
   * List of available values from control dropdown
   *
   * @param {object.<string, array>} options.defaultValues
   * List of default values of styles
   *
   * @memberof DrawerJs.plugins
   *
   * @constructor
   * @augments DrawerJs.plugins.BaseShape
   */
  var Text = function TextConstructor(drawerInstance, options) {
    // call super c-tor
    BaseShape.call(this, drawerInstance);


    this._setupOptions(options);

    this.tooltip = this.drawerInstance.t('Draw a text');
    this.helpTooltipText = this.drawerInstance.t('Click to place a text');

    this.drawerInstance.on(this.drawerInstance.EVENT_OPTIONS_TOOLBAR_CREATED + this.eventNs, this._onOptionsToolbarCreated.bind(this));
  };

  Text.prototype = Object.create(BaseShape.prototype);
  Text.prototype.constructor = Text;


  Text.prototype.name = 'Text';
  /**
   * List of tool options to show when tool is activated.
   * Deviating from BaseShape tool, Line has no 'color', only 'border'.
   * @type {String[]}
   */
  Text.prototype.toolOptionsList = ['color', 'border', 'opacity'];


  // tool event namespace
  Text.prototype.eventNs = '.textTool';

  Text.prototype.name = 'Text';
  Text.prototype.btnClass = 'btn-text';
  Text.prototype.faClass = 'fa-font';
  /////////////////////////////////////////////////////////

  /**
   * On tool options toolbar created - create controls and set handlers
   * To proper react on objects selection and
   *
   * @param  {Event} ev
   * @param  {Drawer} toolbar toolbar which was created
   * @private
   */
  Text.prototype._onOptionsToolbarCreated = function (ev, toolbar) {

    // react on text selection change
    this.drawerInstance.on(this.drawerInstance.EVENT_TEXT_SELECTION_CHANGED, this._onTextSelectionChanged.bind(this));
    // react on edit mode entering
    this.drawerInstance.on(this.drawerInstance.EVENT_TEXT_EDITING_ENTERED, this._onTextEditingEntered.bind(this));
    // react on edit mode exiting
    this.drawerInstance.on(this.drawerInstance.EVENT_TEXT_EDITING_EXITED, this._onTextEditingExited.bind(this));
  };

  Text.prototype._deactivateTool = function () {
    if (!this.active) {
      return;
    }

    // call _deactivateTool() of parent
    BaseShape.prototype._deactivateTool.call(this);
  };


  /**
   * Removes tool controls.
   * If  doDeleteToolbarCreationListeners is true - removes listeners on toolbar creation event.
   * So, tool will not appear on toolbar next time, when toolbar is created.
   *
   * @param {boolean} doDeleteToolbarCreationListeners
   */
  Text.prototype.removeTool = function (doDeleteToolbarCreationListeners) {
    // sign off option toolbar creation
    if (doDeleteToolbarCreationListeners) {
      this.drawerInstance.off(this.drawerInstance.EVENT_OPTIONS_TOOLBAR_CREATED + this.eventNs);
    }

    // call parent removeTool()
    BaseShape.prototype.removeTool.call(this, doDeleteToolbarCreationListeners);
  };

  /**
   * React on edit mode entering
   * @param  {fabric.Event} fEvent
   * @param  {fabric.Object} tool - Object of active tool
   * @private
   */
  Text.prototype._onTextEditingEntered = function (fEvent, tool) {
    console.info('EVENT_TEXT_EDITING_ENTERED');
    this._onTextSelectionChanged(fEvent, tool);
  };

  /**
   * React on edit mode exiting
   * @param  {fabric.Event} fEvent
   * @param  {fabric.Object} tool - Object of active tool
   * @private
   */
  Text.prototype._onTextEditingExited = function (fEvent, tool) {
    if (tool && tool.target) {
      tool.target._lastSelection = undefined;
      tool.target._lastStyles = undefined;
    }
  };

  /**
   * React on styles changes of current text object
   * @param  {fabric.Event} ev
   * @param  {fabric.Object} tool - Object of active tool
   * @private
   */
  Text.prototype._onTextSelectionChanged = function (ev, tool) {
    var targetObj = (tool && tool.target) || this.drawerInstance.getActiveObject(); // @todo

    var currentPos = {
      start: targetObj.selectionStart,
      end: targetObj.selectionEnd
    },
      lastPos = targetObj._lastSelection || {},
      emptySelection = currentPos.start === currentPos.end,
      firstChar = currentPos.start === 0,
      sameStartPosition = currentPos.start === lastPos.start && currentPos.start !== undefined,
      sameEndPosition = currentPos.end === lastPos.end && currentPos.end !== undefined,
      samePosition = sameStartPosition && sameEndPosition;

    if (!samePosition) {
      targetObj._lastSelection = currentPos;

      var objectStyles = targetObj.getObjStyles(),
        prevStyles = targetObj._lastStyles || {},
        getPrevCharStyles = (emptySelection && !firstChar),
        prevCharStyles = getPrevCharStyles && targetObj.getSelectionStyles(currentPos.start - 1),
        styles = prevCharStyles || targetObj.getSelectionStyles(),
        stylesAreChanged = targetObj._hasStyleChanged(prevStyles, prevCharStyles || styles),
        stylesWithMultipleValues = [];

      // For not empty selection
      if (!emptySelection) {
        styles = $.extend(true, {}, styles);
        var stylesArrayForEachChar = targetObj.getSelectionStyles(currentPos.start, currentPos.end),
          firstStyleObj = $.extend(true, {}, stylesArrayForEachChar[0]);
        stylesArrayForEachChar.forEach(function (stylesObj, i) {
          for (var styleName in objectStyles) {
            var charsHaveDifferentStyles = stylesObj[styleName] !== firstStyleObj[styleName],
              alreadyMultiple = stylesWithMultipleValues.indexOf(styleName) !== -1;
            if (charsHaveDifferentStyles && !alreadyMultiple) {
              stylesWithMultipleValues.push(styleName);
              styles[styleName] = undefined;
            }
          }
        });
        stylesAreChanged = stylesWithMultipleValues.length || targetObj._hasStyleChanged(prevStyles, styles);
      }
      this.drawerInstance.setTemporaryStyles(styles);

      if (stylesAreChanged) {
        targetObj._lastStyles = styles;

        var drawer = this.drawerInstance;
        drawer.trigger(drawer.EVENT_TEXT_STYLES_CHANGED, [styles, objectStyles, stylesWithMultipleValues]);
      }
    }
  };

  Text.prototype.addShape = function (left, top, text, styles) {
    styles = styles || {};
    left = parseInt(left, 10);
    left = left || left === 0 ? left : 0;
    top = parseInt(top, 10);
    top = top || top === 0 ? top : 0;
    var shape = this.createShape(left, top, text, styles);
    shape.set('left', left);
    shape.set('top', top);
    this.shape = shape;
    // finish drawing
    this.finishItemDraw();

    // some tools are supposed to draw one shape and then deactivate
    if (this.onlyOneItem) {
      this.drawerInstance.trigger(this.drawerInstance.EVENT_DO_DEACTIVATE_TOOL, this);
    }
  };

  Text.prototype.createShape = function (left, top, textString, styles) {
    styles = styles || {};
    textString = textString || 'Text';
    this.startLeft = left;
    this.startTop = top;

    this.text = new fabric.ErasableText(textString, { editIconMode: this.options.editIconMode, editIconSize: this.options.editIconSize });

    var drawer = this.drawerInstance,
      defaultValues = this.options.defaultValues || {},
      collectedStyles = {};

    drawer.trigger(drawer.EVENT_TEXT_GET_STYLES, [this, collectedStyles]);

    collectedStyles = collectedStyles || {};
    collectedStyles.defaultValues = collectedStyles.defaultValues || {}; //@todo


    this.text.set('fontFamily', styles.fontFamily || defaultValues.fontFamily || collectedStyles.defaultValues.fontFamily);
    this.text.set('fontSize', styles.fontSize || defaultValues.fontSize || collectedStyles.defaultValues.fontSize);
    this.text.set('lineHeight', styles.lineHeight || defaultValues.lineHeight || collectedStyles.defaultValues.lineHeight);
    this.text.set('fill', styles.fill || defaultValues.fill || collectedStyles.defaultValues.fill || this.drawerInstance.activeColor);
    this.text.set('opacity', styles.opacity || defaultValues.opacity || collectedStyles.defaultValues.opacity || this.drawerInstance.activeOpacity);

    this.text.set('left', left - this.text.width / 2);
    this.text.set('top', top - this.text.height / 2);

    return this.text;
  };

  Text.prototype.updateShape = function (text, newLeft, newTop) {
  };

  /**
   * Checks if object is instance of fabric.Text
   * @param  {fabric.Object} obj - Instance of Fabric.Object
   * @return {Boolean}
   */
  Text.prototype.isObjectText = function (obj) {
    return obj instanceof fabric.IText;
  };

  pluginsNamespace.Text = Text;

}(jQuery, DrawerJs.plugins.BaseShape, DrawerJs.plugins, DrawerJs.util));

(function ($, BaseShape, pluginsNamespace) {
  /**
   * Provides a triangle button which can be used to draw triangles.
   *
   * @param {DrawerJs.Drawer} drawerInstance
   * Instance of {@link DrawerJs.Drawer}.
   *
   * @param {Object} options
   * Configuration object.
   *
   * @param {String} [options.centeringMode='normal']
   * Defines centering method when drawing a shape.
   * <br><br>
   * Valid values are:
   * <br><br>
   * <code>normal</code>: triangle's top left corner will be placed to the
   * position of first mouse click and will be resized from that point.
   * <br><br>
   * <code>from_center</code>: triangle's center point will be placed to the
   * position of first mouse click and will be resized from center.
   *
   * @constructor
   * @memberof DrawerJs.plugins
   */
  var Triangle = function TriangleConstructor(drawerInstance, options) {
    var _this = this;

    BaseShape.call(_this, drawerInstance);

    this.name = 'Triangle';
    this.btnClass = 'btn-triangle';
    this.faClass = 'fa-play';
    this.tooltip = drawerInstance.t('Draw a triangle');

    this.options = options || {};
    this.centeringMode =
      this.options.centeringMode || BaseShape.CENTERING_MODE.NORMAL;
  };

  Triangle.prototype = Object.create(BaseShape.prototype);
  Triangle.prototype.constructor = Triangle;

  BaseShape.prototype.minShapeSize = 8;

  Triangle.prototype.createShape = function (left, top) {
    this.startLeft = left;
    this.startTop = top;

    var triangle = new fabric.PTriangle({
      width: 1,
      height: 1,
      left: left,
      top: top,
      fill: this.drawerInstance.activeColor,
      opacity: this.drawerInstance.activeOpacity
    });
    return triangle;
  };

  Triangle.prototype.updateShape = function (triangle, newLeft, newTop) {
    var width = newLeft - this.startLeft;
    var height = newTop - this.startTop;

    if (this.centeringMode === BaseShape.CENTERING_MODE.NORMAL) {
      if (width > 0) {
        triangle.set('width', width);
        triangle.set('left', newLeft);
      } else {
        triangle.set('width', width * -1);
        triangle.set('left', newLeft);
      }

      if (height > 0) {
        triangle.set('angle', 0);
        triangle.set('height', height);
        if (width > 0) {
          triangle.set('left', newLeft - width);
        } else {
          triangle.set('left', newLeft);
        }
      } else {
        triangle.set('angle', 180);
        triangle.set('height', height * -1);
        triangle.set('top', newTop + height * -1);
        if (width > 0) {
          triangle.set('left', newLeft);
        } else {
          triangle.set('left', newLeft + width * -1);
        }
      }


    } else if (
      this.centeringMode === BaseShape.CENTERING_MODE.FROM_CENTER
    ) {

      width *= 2;
      height *= 2;
      triangle.set('left', newLeft - width);
      triangle.set('top', newTop - height);
      triangle.set('width', width);
      triangle.set('height', height);
    }
  };

  pluginsNamespace.Triangle = Triangle;

}(jQuery, DrawerJs.plugins.BaseShape, DrawerJs.plugins));
