/* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */
 /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */

var fabric = fabric || { version: "1.6.7" };
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 object to another
   * @memberOf fabric.util.object
   * @param {Object} destination Where to copy to
   * @param {Object} source Where to copy from
   * @return {Object}
   */

  function extend(destination, source, 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 = source.map(function(v) {
          return clone(v, deep)
        })
      }
      else if (source instanceof Object) {
        for (var property in source) {
          destination[property] = clone(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') {
      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
     * @return {Object}
     */
    toObject: function() {
      return {
        type: this.type,
        coords: this.coords,
        colorStops: this.colorStops,
        offsetX: this.offsetX,
        offsetY: this.offsetY,
        gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform
      };
    },

    /* _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
   * @return {Object} Object representation of a pattern instance
   */
  toObject: function() {

    var source;

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

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

  /* _TO_SVG_START_ */
  /**
   * Returns SVG representation of a pattern
   * @param {fabric.Object} object
   * @return {String} SVG representation of a pattern
   */
  toSVG: function(object) {
    var patternSource = typeof this.source === 'function' ? this.source() : this.source,
        patternWidth = patternSource.width / object.getWidth(),
        patternHeight = patternSource.height / object.getHeight(),
        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: true,

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

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

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

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

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

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

    /**
     * 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 Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]);
    },

    /**
     * Sets viewport transform of this canvas instance
     * @param {Array} vpt the transform in the form of context.transform
     * @return {fabric.Canvas} instance
     * @chainable true
     */
    setViewportTransform: function (vpt) {
      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;

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

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

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


(function() {

  /**
   * 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),
          vpt = this.viewportTransform,
          vptPointer = this.restorePointerVpt(pointer),
          p = fabric.util.transformPoint(vptPointer, invertedM);
      return fabric.util.transformPoint(p, vpt);
    },

    /**
     * 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;
      for ( ; i < len; i++) {
        allObjects[i].set('active', false);
      }
      this._discardActiveGroup();
      this._discardActiveObject();
      return this;
    },

    /**
     * Deactivates all objects and dispatches appropriate events
     * @return {fabric.Canvas} thisArg
     * @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');

  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,

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

    /**
     * Constructor
     * @param {Object} [options] Options object
     */
    initialize: function(options) {
      if (options) {
        this.setOptions(options);
      }
    },

    /**
     * @private
     * @param {Object} [options] Options object
     */
    _initGradient: function(options) {
      if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) {
        this.set('fill', new fabric.Gradient(options.fill));
      }
      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 (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);
      this._renderBackground(ctx);
      this._setStrokeStyles(ctx);
      this._setFillStyles(ctx);
      if (this.transformMatrix) {
        ctx.transform.apply(ctx, this.transformMatrix);
      }
      this.clipTo && fabric.util.clipContext(this, ctx);
      this._render(ctx, noTransform);
      this.clipTo && ctx.restore();

      ctx.restore();
    },

    /**
     * Draws a background for the object big as its width and height;
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderBackground: function(ctx) {
      if (!this.backgroundColor) {
        return;
      }

      ctx.fillStyle = this.backgroundColor;

      ctx.fillRect(
        -this.width / 2,
        -this.height / 2,
        this.width,
        this.height
      );
      // 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) {
      if (this.group) {
        this.group._setOpacity(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;
      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)
     * @return {Object} Object with left, top, width, height properties
     */
    getBoundingRect: function() {
      this.oCoords || this.setCoords();
      return fabric.util.makeBoundingBoxFromPoints([
        this.oCoords.tl,
        this.oCoords.tr,
        this.oCoords.br,
        this.oCoords.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;
      this.setCoords();
      return this;
    },

    /**
     * Scales an object to a given width, with respect to bounding box (scaling by x/y equally)
     * @param {Number} value New width value
     * @return {fabric.Object} thisArg
     * @chainable
     */
    scaleToWidth: function(value) {
      // adjust to bounding rect factor so that rotated shapes would fit as well
      var boundingRectFactor = this.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);
    },

    /**
     * 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() {
      var theta = degreesToRadians(this.angle),
          vpt = this.getViewportTransform(),
          dim = this._calculateCurrentDimensions(),
          currentWidth = dim.x, currentHeight = dim.y;

      // If width is negative, make postive. Fixes path selection issue
      if (currentWidth < 0) {
        currentWidth = Math.abs(currentWidth);
      }

      var 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,

          // offset added for rotate and scale actions
          coords = fabric.util.transformPoint(this.getCenterPoint(), 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); */

      this.oCoords = {
        // corners
        tl: tl, tr: tr, br: br, bl: bl,
        // middle
        ml: ml, mt: mt, mr: mr, mb: mb,
        // rotating point
        mtr: mtr
      };

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

      return this;
    },

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

  /*
    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) {
    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
      }
      var _currentValue = currentValue.concat().sort(),
          _origValue = origValue.concat().sort();
      return !_origValue.some(function(v, i) {
        return !_isEqual(_currentValue[i], v);
      });
    }
    else if (origValue instanceof Object) {
      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
     * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
     */
    hasStateChanged: function() {
      return !_isEqual(this.originalState, this);
    },

    /**
     * Saves state of an object
     * @param {Object} [options] Object with additional `stateProperties` array to include when saving state
     * @return {fabric.Object} thisArg
     */
    saveState: function(options) {
      saveProps(this, 'originalState', this.stateProperties);
      if (options && options.stateProperties) {
        saveProps(this, 'originalState', 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) {
      this.originalState = { };
      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,
          h = this.height,
          addStrokeToW = true,
          addStrokeToH = true;

      if (this.type === 'line' && this.strokeLineCap === 'butt') {
        addStrokeToH = w;
        addStrokeToW = h;
      }

      if (addStrokeToH) {
        h += h < 0 ? -strokeWidth : strokeWidth;
      }

      if (addStrokeToW) {
        w += w < 0 ? -strokeWidth : 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(),
          w = dim.x, h = dim.y,
          p = fabric.util.transformPoint(new fabric.Point(w, h), 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 + 2 * this.padding,
          height = wh.y + strokeWidth + 2 * this.padding;

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

      if (!points) {
        points = [0, 0, 0, 0];
      }

      this.callSuper('initialize', options);

      this.set('x1', points[0]);
      this.set('y1', points[1]);
      this.set('x2', points[2]);
      this.set('y2', points[3]);

      this._setWidthHeight(options);
    },

    /**
     * @private
     * @param {Object} [options] Options
     */
    _setWidthHeight: function(options) {
      options || (options = { });

      this.width = Math.abs(this.x2 - this.x1);
      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());
    },

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

      this.callSuper('initialize', options);
      this.set('radius', options.radius || 0);

      this.startAngle = options.startAngle || this.startAngle;
      this.endAngle = options.endAngle || this.endAngle;
    },

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

      this.callSuper('initialize', options);

      this.set('width', options.width || 100)
          .set('height', options.height || 100);
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _render: function(ctx) {
      var widthBy2 = this.width / 2,
          heightBy2 = this.height / 2;

      ctx.beginPath();
      ctx.moveTo(-widthBy2, heightBy2);
      ctx.lineTo(0, -heightBy2);
      ctx.lineTo(widthBy2, heightBy2);
      ctx.closePath();

      this._renderFill(ctx);
      this._renderStroke(ctx);
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderDashedStroke: function(ctx) {
      var widthBy2 = this.width / 2,
          heightBy2 = this.height / 2;

      ctx.beginPath();
      fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray);
      fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray);
      fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray);
      ctx.closePath();
    },

    /* _TO_SVG_START_ */
    /**
     * Returns SVG representation of an instance
     * @param {Function} [reviver] Method for further parsing of svg representation.
     * @return {String} svg representation of an instance
     */
    toSVG: function(reviver) {
      var markup = this._createBaseSVGMarkup(),
          widthBy2 = this.width / 2,
          heightBy2 = this.height / 2,
          points = [
            -widthBy2 + ' ' + heightBy2,
            '0 ' + -heightBy2,
            widthBy2 + ' ' + heightBy2
          ]
          .join(',');

      markup.push(
        '<polygon ', 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) {
      options = options || { };

      this.callSuper('initialize', options);

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

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

    /**
     * @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.Path, /** @lends fabric.PathGroup.prototype */ {

    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'path-group',

    /**
     * Fill value
     * @type String
     * @default
     */
    fill: '',

    /**
     * Constructor
     * @param {Array} paths
     * @param {Object} [options] Options object
     * @return {fabric.PathGroup} thisArg
     */
    initialize: function(paths, options) {

      options = options || { };
      this.paths = paths || [];

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

      if (options.toBeParsed) {
        this.parseDimensionsFromPaths(options);
        delete options.toBeParsed;
      }
      this.setOptions(options);
      this.setCoords();

      if (options.sourcePath) {
        this.setSourcePath(options.sourcePath);
      }
    },

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

    /**
     * Renders this group on a specified context
     * @param {CanvasRenderingContext2D} ctx Context to render this instance on
     */
    render: function(ctx) {
      // do not render if object is not visible
      if (!this.visible) {
        return;
      }

      ctx.save();

      if (this.transformMatrix) {
        ctx.transform.apply(ctx, this.transformMatrix);
      }
      this.transform(ctx);

      this._setShadow(ctx);
      this.clipTo && fabric.util.clipContext(this, ctx);
      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);
      }
      this.clipTo && ctx.restore();
      ctx.restore();
    },

    /**
     * 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) {
      for (var i = this._objects.length; i--; ){
        this._updateObjectCoords(this._objects[i], skipCoordsChange);
      }
    },

    /**
     * @private
     * @param {Object} object
     * @param {Boolean} [skipCoordsChange] if true, coordinates of object dose not change
     */
    _updateObjectCoords: function(object, 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(),
          center = this.getCenterPoint();

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

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

      return this;
    },

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

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

    /**
     * Properties that are delegated to group objects when reading/writing
     * @param {Object} delegatedProperties
     */
    delegatedProperties: {
      fill:             true,
      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) {
      // do not render if object is not visible
      if (!this.visible) {
        return;
      }

      ctx.save();
      if (this.transformMatrix) {
        ctx.transform.apply(ctx, this.transformMatrix);
      }
      this.transform(ctx);
      this._setShadow(ctx);
      this.clipTo && fabric.util.clipContext(this, ctx);
      this._transformDone = true;
      // the array is now sorted in order of highest first, so start from end
      for (var i = 0, len = this._objects.length; i < len; i++) {
        this._renderObject(this._objects[i], ctx);
      }

      this.clipTo && ctx.restore();
      ctx.restore();
      this._transformDone = 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) {
      this.callSuper('_renderControls', ctx, noTransform);
      for (var i = 0, len = this._objects.length; i < len; i++) {
        this._objects[i]._renderControls(ctx);
      }
    },

    /**
     * @private
     */
    _renderObject: function(object, ctx) {
      // 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.scaleX = options.scaleX;
      object.scaleY = options.scaleY;
      object.skewX = options.skewX;
      object.skewY = options.skewY;
      object.angle = options.angle;
      object.flipX = false;
      object.flipY = false;
      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() {
      this.forEachObject(function(object) {
        object.setCoords();
      });
      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;

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

      if (!onlyWidthHeight) {
        obj.left = minXY.x || 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,

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

  /**
   * Text class
   * @class fabric.Text
   * @extends fabric.Object
   * @return {fabric.Text} thisArg
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#text}
   * @see {@link fabric.Text#initialize} for constructor definition
   */
  fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ {

    /**
     * Properties which when set cause object to change dimensions
     * @type Object
     * @private
     */
    _dimensionAffectingProps: {
      fontSize: true,
      fontWeight: true,
      fontFamily: true,
      fontStyle: true,
      lineHeight: true,
      text: true,
      charSpacing: true,
      textAlign: true,
      strokeWidth: false,
    },

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

    /**
     * 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.setOptions(options);
      this.__skipDimension = false;
      this._initDimensions();
    },

    /**
     * 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.clipTo && fabric.util.clipContext(this, ctx);
      this._setOpacity(ctx);
      this._setShadow(ctx);
      this._setupCompositeOperation(ctx);
      this._renderTextBackground(ctx);
      this._setStrokeStyles(ctx);
      this._setFillStyles(ctx);
      this._renderText(ctx);
      this._renderTextDecoration(ctx);
      this.clipTo && ctx.restore();
    },

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

    /*
     * Calculate object dimensions from its properties
     * @override
     * @private
     */
    _getNonTransformedDimensions: function() {
      return { x: this.width, y: this.height };
    },

    /**
     * @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
     */
    _renderTextBackground: function(ctx) {
      this._renderBackground(ctx);
      this._renderTextLinesBackground(ctx);
    },

    /**
     * @private
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _renderTextLinesBackground: function(ctx) {
      if (!this.textBackgroundColor) {
        return;
      }
      var lineTopOffset = 0, heightOfLine,
          lineWidth, lineLeftOffset;

      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;
      }
      // 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
     */
    _shouldClearCache: function() {
      var shouldClear = false;
      if (this._forceClearCache) {
        this._forceClearCache = false;
        return true;
      }
      for (var prop in this._dimensionAffectingProps) {
        if (this['__' + prop] !== this[prop]) {
          this['__' + prop] = this[prop];
          shouldClear = 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;
      }

      ctx.save();
      this._setTextStyles(ctx);

      if (this._shouldClearCache()) {
        this._initDimensions(ctx);
      }
      this.drawSelectionBackground(ctx);
      if (!noTransform) {
        this.transform(ctx);
      }
      if (this.transformMatrix) {
        ctx.transform.apply(ctx, this.transformMatrix);
      }
      if (this.group && this.group.type === 'path-group') {
        ctx.translate(this.left, this.top);
      }
      this._render(ctx);
      ctx.restore();
    },

    /**
     * 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 (key in this._dimensionAffectingProps) {
        this._initDimensions();
        this.setCoords();
      }
    },

    /**
     * Returns complexity of an instance
     * @return {Number} complexity
     */
    complexity: function() {
      return 1;
    }
  });

  /* _FROM_SVG_START_ */
  /**
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement})
   * @static
   * @memberOf fabric.Text
   * @see: http://www.w3.org/TR/SVG/text.html#TextElement
   */
  fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(
    'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' '));

  /**
   * Default SVG font size
   * @static
   * @memberOf fabric.Text
   */
  fabric.Text.DEFAULT_SVG_FONT_SIZE = 16;

  /**
   * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>)
   * @static
   * @memberOf fabric.Text
   * @param {SVGElement} element Element to parse
   * @param {Object} [options] Options object
   * @return {fabric.Text} Instance of fabric.Text
   */
  fabric.Text.fromElement = function(element, options) {
    if (!element) {
      return null;
    }

    var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES);
    options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes);

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

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

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

    /**
     * @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 && 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 = 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,

    /**
     * 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.ctx = fabric.util.createCanvasElement().getContext('2d');
      this.callSuper('initialize', text, options);
      this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());

      // add width to this list of props that effect line wrapping.
      this._dimensionAffectingProps.width = true;
    },

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

      // clear dynamicMinWidth as it will be different after we re-wrap line
      this.dynamicMinWidth = 0;

      // wrap lines
      this._textLines = this._splitTextIntoLines();
      // 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() {
      var originalAlign = this.textAlign;
      this.ctx.save();
      this._setTextStyles(this.ctx);
      this.textAlign = 'left';
      var lines = this._wrapText(this.ctx, this.text);
      this.textAlign = originalAlign;
      this.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;
  }

})();

/*!
 * tui-code-snippet.min.js
 * @version 1.3.0
 * @author NHNEnt FE Development Lab <dl_javascript@nhnent.com>
 * @license MIT
 */
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.util=e():(t.tui=t.tui||{},t.tui.util=e())}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="dist",e(0)}([function(t,e,n){"use strict";var r={},o=n(1),i=o.extend;i(r,o),i(r,n(3)),i(r,n(2)),i(r,n(4)),i(r,n(5)),i(r,n(6)),i(r,n(7)),i(r,n(8)),i(r,n(9)),r.browser=n(10),r.popup=n(11),r.formatDate=n(12),r.defineClass=n(13),r.defineModule=n(14),r.defineNamespace=n(15),r.CustomEvents=n(16),r.Enum=n(17),r.ExMap=n(18),r.HashMap=n(20),r.Map=n(19),t.exports=r},function(t,e,n){"use strict";function r(t,e){var n,r,o,i,u=Object.prototype.hasOwnProperty;for(o=1,i=arguments.length;o<i;o+=1){n=arguments[o];for(r in n)u.call(n,r)&&(t[r]=n[r])}return t}function o(t){return t.__fe_id||(l+=1,t.__fe_id=l),t.__fe_id}function i(t){return f.isExisty(p(t,"__fe_id"))}function u(){l=0}function s(t){var e,n=[];for(e in t)t.hasOwnProperty(e)&&n.push(e);return n}function c(t){var e=arguments.length,n=1;if(e<1)return!0;for(;n<e;n+=1)if(!a(t,arguments[n]))return!1;return!0}function a(t,e){var n,r=[],o=[];if(isNaN(t)&&isNaN(e)&&f.isNumber(t)&&f.isNumber(e))return!0;if(t===e)return!0;if(f.isFunction(t)&&f.isFunction(e)||t instanceof Date&&e instanceof Date||t instanceof RegExp&&e instanceof RegExp||t instanceof String&&e instanceof String||t instanceof Number&&e instanceof Number)return t.toString()===e.toString();if(!(t instanceof Object&&e instanceof Object))return!1;if(t.isPrototypeOf(e)||e.isPrototypeOf(t)||t.constructor!==e.constructor||t.prototype!==e.prototype)return!1;if(h.inArray(t,r)>-1||h.inArray(e,o)>-1)return!1;for(n in e){if(e.hasOwnProperty(n)!==t.hasOwnProperty(n))return!1;if(typeof e[n]!=typeof t[n])return!1}for(n in t){if(e.hasOwnProperty(n)!==t.hasOwnProperty(n))return!1;if(typeof e[n]!=typeof t[n])return!1;if("object"==typeof t[n]||"function"==typeof t[n]){if(r.push(t),o.push(e),!a(t[n],e[n]))return!1;r.pop(),o.pop()}else if(t[n]!==e[n])return!1}return!0}function p(t,e){for(var n=arguments,r=n[0],o=1,i=n.length;o<i;o+=1){if(f.isUndefined(r)||f.isNull(r))return;r=r[n[o]]}return r}var f=n(2),h=n(3),l=0;t.exports={extend:r,stamp:o,hasStamp:i,resetLastId:u,keys:Object.prototype.keys||s,compareJSON:c,pick:p}},function(t,e){"use strict";function n(t){return!r(t)&&!o(t)}function r(t){return void 0===t}function o(t){return null===t}function i(t){return n(t)&&t!==!1}function u(t){return!i(t)}function s(t){var e=n(t)&&("[object Arguments]"===P.call(t)||!!t.callee);return e}function c(t){return t instanceof Array}function a(t){return t===Object(t)}function p(t){return t instanceof Function}function f(t){return"number"==typeof t||t instanceof Number}function h(t){return"string"==typeof t||t instanceof String}function l(t){return"boolean"==typeof t||t instanceof Boolean}function y(t){return"[object Array]"===P.call(t)}function d(t){return"[object Function]"===P.call(t)}function m(t){return"[object Number]"===P.call(t)}function v(t){return"[object String]"===P.call(t)}function _(t){return"[object Boolean]"===P.call(t)}function g(t){return"object"==typeof HTMLElement?t&&(t instanceof HTMLElement||!!t.nodeType):!(!t||!t.nodeType)}function x(t){return"object"==typeof HTMLElement?t&&t instanceof HTMLElement:!(!t||!t.nodeType||1!==t.nodeType)}function b(t){return!(n(t)&&!E(t))||(c(t)||s(t)?0===t.length:!(a(t)&&!p(t))||!O(t))}function E(t){return h(t)&&""===t}function O(t){var e;for(e in t)if(t.hasOwnProperty(e))return!0;return!1}function w(t){return!b(t)}function N(t){return t instanceof Date}function A(t){return"[object Date]"===P.call(t)}var P=Object.prototype.toString;t.exports={isExisty:n,isUndefined:r,isNull:o,isTruthy:i,isFalsy:u,isArguments:s,isArray:c,isArraySafe:y,isObject:a,isFunction:p,isFunctionSafe:d,isNumber:f,isNumberSafe:m,isDate:N,isDateSafe:A,isString:h,isStringSafe:v,isBoolean:l,isBooleanSafe:_,isHTMLNode:g,isHTMLTag:x,isEmpty:b,isNotEmpty:w}},function(t,e,n){"use strict";var r,o=n(4),i=n(2),u=Array.prototype.slice,s=function(t,e,n){var r,o=[];for(i.isUndefined(e)&&(e=t||0,t=0),n=n||1,r=n<0?-1:1,e*=r;t*r<e;t+=n)o.push(t);return o},c=function(){var t=u.call(arguments),e=[];return o.forEach(t,function(t){o.forEach(t,function(t,n){e[n]||(e[n]=[]),e[n].push(t)})}),e},a=function(t,e,n){var r,o;if(n=n||0,!i.isArray(e))return-1;if(Array.prototype.indexOf)return Array.prototype.indexOf.call(e,t,n);for(o=e.length,r=n;n>=0&&r<o;r+=1)if(e[r]===t)return r;return-1};r={inArray:a,range:s,zip:c},t.exports=r},function(t,e,n){"use strict";function r(t,e,n){var r=0,o=t.length;for(n=n||null;r<o&&e.call(n,t[r],r,t)!==!1;r+=1);}function o(t,e,n){var r;n=n||null;for(r in t)if(t.hasOwnProperty(r)&&e.call(n,t[r],r,t)===!1)break}function i(t,e,n){f.isArray(t)?r(t,e,n):o(t,e,n)}function u(t,e,n){var r=[];return n=n||null,i(t,function(){r.push(e.apply(n,arguments))}),r}function s(t,e,n){var r,o,i,u=0;for(n=n||null,f.isArray(t)?(o=t.length,i=t[u]):(r=h.keys(t),o=r.length,i=t[r[u+=1]]),u+=1;u<o;u+=1)i=e.call(n,i,t[r?r[u]:u]);return i}function c(t){var e;try{e=Array.prototype.slice.call(t)}catch(n){e=[],r(t,function(t){e.push(t)})}return e}function a(t,e,n){var r,o;if(n=n||null,!f.isObject(t)||!f.isFunction(e))throw new Error("wrong parameter");return f.isArray(t)?(r=[],o=function(t,e){t.push(e[0])}):(r={},o=function(t,e){t[e[1]]=e[0]}),i(t,function(){e.apply(n,arguments)&&o(r,arguments)},n),r}function p(t,e){var n=u(t,function(t){return t[e]});return n}var f=n(2),h=n(1);t.exports={forEachOwnProperties:o,forEachArray:r,forEach:i,toArray:c,map:u,reduce:s,filter:a,pluck:p}},function(t,e){"use strict";function n(t,e){var n,r=Array.prototype.slice;return t.bind?t.bind.apply(t,r.call(arguments,1)):(n=r.call(arguments,2),function(){return t.apply(e,n.length?n.concat(r.call(arguments)):arguments)})}t.exports={bind:n}},function(t,e){"use strict";function n(t){function e(){}return e.prototype=t,new e}function r(t,e){var r=n(e.prototype);r.constructor=t,t.prototype=r}t.exports={createObject:n,inherit:r}},function(t,e,n){"use strict";function r(t){var e={"&quot;":'"',"&amp;":"&","&lt;":"<","&gt;":">","&#39;":"'","&nbsp;":" "};return t.replace(/&amp;|&lt;|&gt;|&quot;|&#39;|&nbsp;/g,function(t){return e[t]?e[t]:t})}function o(t){var e={'"':"quot","&":"amp","<":"lt",">":"gt","'":"#39"};return t.replace(/[<>&"']/g,function(t){return e[t]?"&"+e[t]+";":t})}function i(t){return/[<>&"']/.test(t)}function u(t,e){for(var n,r,o=0,i=t.length,u={};o<i;o+=1)r=t.charAt(o),u[r]=1;for(o=0,i=e.length;o<i;o+=1)r=e.charAt(o),u[r]&&(u[r]+=1);return u=s.filter(u,function(t){return t>1}),u=c.keys(u).sort(),n=u.join("")}var s=n(4),c=n(1);t.exports={decodeHTMLEntity:r,encodeHTMLEntity:o,hasEncodableString:i,getDuplicatedChar:u}},function(t,e){"use strict";function n(t,e){function n(){o=u.call(arguments),window.clearTimeout(r),r=window.setTimeout(function(){t.apply(null,o)},e)}var r,o;return e=e||0,n}function r(){return Number(new Date)}function o(t,e){function n(){return a=u.call(arguments),p?(f(a),void(p=!1)):(c=i.timestamp(),o=o||c,s(a),void(c-o>=e&&f(a)))}function r(){p=!0,o=null}var o,s,c,a,p=!0,f=function(e){t.apply(null,e),o=null};return e=e||0,s=i.debounce(f,e),n.reset=r,n}var i={},u=Array.prototype.slice;i.timestamp=r,i.debounce=n,i.throttle=o,t.exports=i},function(t,e,n){"use strict";function r(t,e){var n=i.map(o.keys(e),function(t,n){var r=0===n?"":"&";return r+t+"="+e[t]}).join(""),r=document.createElement("img");return r.src=t+"?"+n,r.style.display="none",document.body.appendChild(r),document.body.removeChild(r),r}var o=n(1),i=n(4);t.exports={imagePing:r}},function(t,e){"use strict";var n,r,o={chrome:!1,firefox:!1,safari:!1,msie:!1,edge:!1,others:!1,version:0},i=window.navigator,u=i.appName.replace(/\s/g,"_"),s=i.userAgent,c=/MSIE\s([0-9]+[.0-9]*)/,a=/Trident.*rv:11\./,p=/Edge\/(\d+)\./,f={firefox:/Firefox\/(\d+)\./,chrome:/Chrome\/(\d+)\./,safari:/Version\/([\d.]+).*Safari\/(\d+)/},h={Microsoft_Internet_Explorer:function(){var t=s.match(c);t?(o.msie=!0,o.version=parseFloat(t[1])):o.others=!0},Netscape:function(){var t=!1;if(a.exec(s))o.msie=!0,o.version=11,t=!0;else if(p.exec(s))o.edge=!0,o.version=s.match(p)[1],t=!0;else for(n in f)if(f.hasOwnProperty(n)&&(r=s.match(f[n]),r&&r.length>1)){o[n]=t=!0,o.version=parseFloat(r[1]||0);break}t||(o.others=!0)}},l=h[u];l&&h[u](),t.exports=o},function(t,e,n){"use strict";function r(){this.openedPopup={},this.closeWithParentPopup={},this.postBridgeUrl=""}var o=n(4),i=n(2),u=n(5),s=n(10),c=n(1),a=0;r.prototype.getPopupList=function(t){var e;return e=i.isExisty(t)?this.openedPopup[t]:this.openedPopup},r.prototype.openPopup=function(t,e){var n,r,o;if(e=c.extend({popupName:"popup_"+a+"_"+Number(new Date),popupOptionStr:"",useReload:!0,closeWithParent:!0,method:"get",param:{}},e||{}),e.method=e.method.toUpperCase(),this.postBridgeUrl=e.postBridgeUrl||this.postBridgeUrl,o="POST"===e.method&&e.param&&s.msie&&11===s.version,!i.isExisty(t))throw new Error("Popup#open() need popup url.");a+=1,e.param&&("GET"===e.method?t=t+(/\?/.test(t)?"&":"?")+this._parameterize(e.param):"POST"===e.method&&(o||(r=this.createForm(t,e.param,e.method,e.popupName),t="about:blank"))),n=this.openedPopup[e.popupName],i.isExisty(n)?n.closed?this.openedPopup[e.popupName]=n=this._open(o,e.param,t,e.popupName,e.popupOptionStr):(e.useReload&&n.location.replace(t),n.focus()):this.openedPopup[e.popupName]=n=this._open(o,e.param,t,e.popupName,e.popupOptionStr),this.closeWithParentPopup[e.popupName]=e.closeWithParent,(!n||n.closed||i.isUndefined(n.closed))&&alert("please enable popup windows for this website"),e.param&&"POST"===e.method&&!o&&(n&&r.submit(),r.parentNode&&r.parentNode.removeChild(r)),window.onunload=u.bind(this.closeAllPopup,this)},r.prototype.close=function(t,e){var n=e||window;t=!!i.isExisty(t)&&t,t&&(window.onunload=null),n.closed||(n.opener=window.location.href,n.close())},r.prototype.closeAllPopup=function(t){var e=i.isExisty(t);o.forEachOwnProperties(this.openedPopup,function(t,n){(e&&this.closeWithParentPopup[n]||!e)&&this.close(!1,t)},this)},r.prototype.focus=function(t){this.getPopupList(t).focus()},r.prototype.parseQuery=function(){var t,e,n={};return t=window.location.search.substr(1),o.forEachArray(t.split("&"),function(t){e=t.split("="),n[decodeURIComponent(e[0])]=decodeURIComponent(e[1])}),n},r.prototype.createForm=function(t,e,n,r,i){var u,s=document.createElement("form");return i=i||document.body,s.method=n||"POST",s.action=t||"",s.target=r||"",s.style.display="none",o.forEachOwnProperties(e,function(t,e){u=document.createElement("input"),u.name=e,u.type="hidden",u.value=t,s.appendChild(u)}),i.appendChild(s),s},r.prototype._parameterize=function(t){var e=[];return o.forEachOwnProperties(t,function(t,n){e.push(encodeURIComponent(n)+"="+encodeURIComponent(t))}),e.join("&")},r.prototype._open=function(t,e,n,r,o){var i;return t?(i=window.open(this.postBridgeUrl,r,o),setTimeout(function(){i.redirect(n,e)},100)):i=window.open(n,r,o),i},t.exports=new r},function(t,e,n){"use strict";function r(t,e,n){var r,o,i,u;return t=Number(t),e=Number(e),n=Number(n),r=t>-1&&t<100||t>1969&&t<2070,o=e>0&&e<13,!(!r||!o)&&(u=a[e],2===e&&t%4===0&&(t%100===0&&t%400!==0||(u=29)),i=n>0&&n<=u)}function o(t,e,n){var o,c,a,f=u.pick(n,"meridiemSet","AM")||"AM",h=u.pick(n,"meridiemSet","PM")||"PM";return c=i.isDate(e)?{year:e.getFullYear(),month:e.getMonth()+1,date:e.getDate(),hour:e.getHours(),minute:e.getMinutes()}:{year:e.year,month:e.month,date:e.date,hour:e.hour,minute:e.minute},!!r(c.year,c.month,c.date)&&(c.meridiem="",/([^\\]|^)[aA]\b/.test(t)&&(o=c.hour>11?h:f,c.hour>12&&(c.hour%=12),0===c.hour&&(c.hour=12),c.meridiem=o),a=t.replace(s,function(t){return t.indexOf("\\")>-1?t.replace(/\\/,""):p[t](c)||""}))}var i=n(2),u=n(1),s=/[\\]*YYYY|[\\]*YY|[\\]*MMMM|[\\]*MMM|[\\]*MM|[\\]*M|[\\]*DD|[\\]*D|[\\]*HH|[\\]*H|[\\]*A/gi,c=["Invalid month","January","February","March","April","May","June","July","August","September","October","November","December"],a=[0,31,28,31,30,31,30,31,31,30,31,30,31],p={M:function(t){return Number(t.month)},MM:function(t){var e=t.month;return Number(e)<10?"0"+e:e},MMM:function(t){return c[Number(t.month)].substr(0,3)},MMMM:function(t){return c[Number(t.month)]},D:function(t){return Number(t.date)},d:function(t){return p.D(t)},DD:function(t){var e=t.date;return Number(e)<10?"0"+e:e},dd:function(t){return p.DD(t)},YY:function(t){return Number(t.year)%100},yy:function(t){return p.YY(t)},YYYY:function(t){var e="20",n=t.year;return n>69&&n<100&&(e="19"),Number(n)<100?e+String(n):n},yyyy:function(t){return p.YYYY(t)},A:function(t){return t.meridiem},a:function(t){return t.meridiem},hh:function(t){var e=t.hour;return Number(e)<10?"0"+e:e},HH:function(t){return p.hh(t)},h:function(t){return String(Number(t.hour))},H:function(t){return p.h(t)},m:function(t){return String(Number(t.minute))},mm:function(t){var e=t.minute;return Number(e)<10?"0"+e:e}};t.exports=o},function(t,e,n){"use strict";function r(t,e){var n;return e||(e=t,t=null),n=e.init||function(){},t&&o(n,t),e.hasOwnProperty("static")&&(i(n,e["static"]),delete e["static"]),i(n.prototype,e),n}var o=n(6).inherit,i=n(1).extend;t.exports=r},function(t,e,n){"use strict";function r(t,e){var n=e||{};return i.isFunction(n[u])&&n[u](),o(t,n)}var o=n(15),i=n(2),u="initialize";t.exports=r},function(t,e,n){"use strict";function r(t,e,n){var r,u,s,c;return r=t.split("."),r.unshift(window),u=o.reduce(r,function(t,e){return t[e]=t[e]||{},t[e]}),n?(c=r.pop(),s=i.pick.apply(null,r),u=s[c]=e):i.extend(u,e),u}var o=n(4),i=n(1);t.exports=r},function(t,e,n){"use strict";function r(){this.events=null,this.contexts=null}var o=n(4),i=n(2),u=n(1),s=/\s+/g;r.mixin=function(t){u.extend(t.prototype,r.prototype)},r.prototype._getHandlerItem=function(t,e){var n={handler:t};return e&&(n.context=e),n},r.prototype._safeEvent=function(t){var e,n=this.events;return n||(n=this.events={}),t&&(e=n[t],e||(e=[],n[t]=e),n=e),n},r.prototype._safeContext=function(){var t=this.contexts;return t||(t=this.contexts=[]),t},r.prototype._indexOfContext=function(t){for(var e=this._safeContext(),n=0;e[n];){if(t===e[n][0])return n;n+=1}return-1},r.prototype._memorizeContext=function(t){var e,n;i.isExisty(t)&&(e=this._safeContext(),n=this._indexOfContext(t),n>-1?e[n][1]+=1:e.push([t,1]))},r.prototype._forgetContext=function(t){var e,n;i.isExisty(t)&&(e=this._safeContext(),n=this._indexOfContext(t),n>-1&&(e[n][1]-=1,e[n][1]<=0&&e.splice(n,1)))},r.prototype._bindEvent=function(t,e,n){var r=this._safeEvent(t);this._memorizeContext(n),r.push(this._getHandlerItem(e,n))},r.prototype.on=function(t,e,n){var r=this;i.isString(t)?(t=t.split(s),o.forEach(t,function(t){r._bindEvent(t,e,n)})):i.isObject(t)&&(n=e,o.forEach(t,function(t,e){r.on(e,t,n)}))},r.prototype.once=function(t,e,n){function r(){e.apply(n,arguments),u.off(t,r,n)}var u=this;return i.isObject(t)?(n=e,void o.forEach(t,function(t,e){u.once(e,t,n)})):void this.on(t,r,n)},r.prototype._spliceMatches=function(t,e){var n,r=0;if(i.isArray(t))for(n=t.length;r<n;r+=1)e(t[r])===!0&&(t.splice(r,1),n-=1,r-=1)},r.prototype._matchHandler=function(t){var e=this;return function(n){var r=t===n.handler;return r&&e._forgetContext(n.context),r}},r.prototype._matchContext=function(t){var e=this;return function(n){var r=t===n.context;return r&&e._forgetContext(n.context),r}},r.prototype._matchHandlerAndContext=function(t,e){var n=this;return function(r){var o=t===r.handler,i=e===r.context,u=o&&i;return u&&n._forgetContext(r.context),u}},r.prototype._offByEventName=function(t,e){var n=this,r=o.forEachArray,u=i.isFunction(e),c=n._matchHandler(e);t=t.split(s),r(t,function(t){var e=n._safeEvent(t);u?n._spliceMatches(e,c):(r(e,function(t){n._forgetContext(t.context)}),n.events[t]=[])})},r.prototype._offByHandler=function(t){var e=this,n=this._matchHandler(t);o.forEach(this._safeEvent(),function(t){e._spliceMatches(t,n)})},r.prototype._offByObject=function(t,e){var n,r=this;this._indexOfContext(t)<0?o.forEach(t,function(t,e){r.off(e,t)}):i.isString(e)?(n=this._matchContext(t),r._spliceMatches(this._safeEvent(e),n)):i.isFunction(e)?(n=this._matchHandlerAndContext(e,t),o.forEach(this._safeEvent(),function(t){r._spliceMatches(t,n)})):(n=this._matchContext(t),o.forEach(this._safeEvent(),function(t){r._spliceMatches(t,n)}))},r.prototype.off=function(t,e){i.isString(t)?this._offByEventName(t,e):arguments.length?i.isFunction(t)?this._offByHandler(t):i.isObject(t)&&this._offByObject(t,e):(this.events={},this.contexts=[])},r.prototype.fire=function(t){this.invoke.apply(this,arguments)},r.prototype.invoke=function(t){var e,n,r,o;if(!this.hasListener(t))return!0;for(e=this._safeEvent(t),n=Array.prototype.slice.call(arguments,1),r=0;e[r];){if(o=e[r],o.handler.apply(o.context,n)===!1)return!1;r+=1}return!0},r.prototype.hasListener=function(t){return this.getListenerLength(t)>0},r.prototype.getListenerLength=function(t){var e=this._safeEvent(t);return e.length},t.exports=r},function(t,e,n){"use strict";function r(t){t&&this.set.apply(this,arguments)}var o=n(4),i=n(2),u=function(){try{return Object.defineProperty({},"x",{}),!0}catch(t){return!1}}(),s=0;r.prototype.set=function(t){var e=this;i.isArray(t)||(t=o.toArray(arguments)),o.forEach(t,function(t){e._addItem(t)})},r.prototype.getName=function(t){var e,n=this;return o.forEach(this,function(r,o){if(n._isEnumItem(o)&&t===r)return e=o,!1}),e},r.prototype._addItem=function(t){var e;this.hasOwnProperty(t)||(e=this._makeEnumValue(),u?Object.defineProperty(this,t,{enumerable:!0,configurable:!1,writable:!1,value:e}):this[t]=e)},r.prototype._makeEnumValue=function(){var t;return t=s,s+=1,t},r.prototype._isEnumItem=function(t){return i.isNumber(this[t])},t.exports=r},function(t,e,n){"use strict";function r(t){this._map=new i(t),this.size=this._map.size}var o=n(4),i=n(19),u=["get","has","forEach","keys","values","entries"],s=["delete","clear"];o.forEachArray(u,function(t){r.prototype[t]=function(){return this._map[t].apply(this._map,arguments)}}),o.forEachArray(s,function(t){r.prototype[t]=function(){var e=this._map[t].apply(this._map,arguments);return this.size=this._map.size,e}}),r.prototype.set=function(){return this._map.set.apply(this._map,arguments),this.size=this._map.size,this},r.prototype.setObject=function(t){o.forEachOwnProperties(t,function(t,e){this.set(e,t)},this)},r.prototype.deleteByKeys=function(t){o.forEachArray(t,function(t){this["delete"](t)},this)},r.prototype.merge=function(t){t.forEach(function(t,e){this.set(e,t)},this)},r.prototype.filter=function(t){var e=new r;return this.forEach(function(n,r){t(n,r)&&e.set(r,n)}),e},t.exports=r},function(t,e,n){"use strict";function r(t,e){this._keys=t,this._valueGetter=e,this._length=this._keys.length,this._index=-1,this._done=!1}function o(t){this._valuesForString={},this._valuesForIndex={},this._keys=[],t&&this._setInitData(t),this.size=0}var i=n(4),u=n(2),s=n(3),c=n(10),a=n(5),p={},f={};r.prototype.next=function(){var t={};do this._index+=1;while(u.isUndefined(this._keys[this._index])&&this._index<this._length);return this._index>=this._length?t.done=!0:(t.done=!1,t.value=this._valueGetter(this._keys[this._index],this._index)),t},o.prototype._setInitData=function(t){if(!u.isArray(t))throw new Error("Only Array is supported.");i.forEachArray(t,function(t){this.set(t[0],t[1])},this)},o.prototype._isNaN=function(t){return"number"==typeof t&&t!==t},o.prototype._getKeyIndex=function(t){var e,n=-1;return u.isString(t)?(e=this._valuesForString[t],e&&(n=e.keyIndex)):n=s.inArray(t,this._keys),n},o.prototype._getOriginKey=function(t){var e=t;return t===p?e=void 0:t===f&&(e=NaN),e},o.prototype._getUniqueKey=function(t){var e=t;return u.isUndefined(t)?e=p:this._isNaN(t)&&(e=f),e},o.prototype._getValueObject=function(t,e){return u.isString(t)?this._valuesForString[t]:(u.isUndefined(e)&&(e=this._getKeyIndex(t)),e>=0?this._valuesForIndex[e]:void 0)},o.prototype._getOriginValue=function(t,e){return this._getValueObject(t,e).origin},o.prototype._getKeyValuePair=function(t,e){return[this._getOriginKey(t),this._getOriginValue(t,e)]},o.prototype._createValueObject=function(t,e){return{keyIndex:e,origin:t}},o.prototype.set=function(t,e){var n,r=this._getUniqueKey(t),o=this._getKeyIndex(r);return o<0&&(o=this._keys.push(r)-1,this.size+=1),n=this._createValueObject(e,o),u.isString(t)?this._valuesForString[t]=n:this._valuesForIndex[o]=n,this},o.prototype.get=function(t){var e=this._getUniqueKey(t),n=this._getValueObject(e);return n&&n.origin},o.prototype.keys=function(){return new r(this._keys,a.bind(this._getOriginKey,this))},o.prototype.values=function(){return new r(this._keys,a.bind(this._getOriginValue,this))},o.prototype.entries=function(){return new r(this._keys,a.bind(this._getKeyValuePair,this))},o.prototype.has=function(t){return!!this._getValueObject(t)},o.prototype["delete"]=function(t){var e;u.isString(t)?this._valuesForString[t]&&(e=this._valuesForString[t].keyIndex,delete this._valuesForString[t]):(e=this._getKeyIndex(t),e>=0&&delete this._valuesForIndex[e]),e>=0&&(delete this._keys[e],this.size-=1)},o.prototype.forEach=function(t,e){e=e||this,i.forEachArray(this._keys,function(n){u.isUndefined(n)||t.call(e,this._getValueObject(n).origin,n,this)},this)},o.prototype.clear=function(){o.call(this)},function(){window.Map&&(c.firefox&&c.version>=37||c.chrome&&c.version>=42)&&(o=window.Map)}(),t.exports=o},function(t,e,n){"use strict";function r(t){this.length=0,t&&this.setObject(t)}var o=n(4),i=n(2),u="Ã¥";r.prototype.set=function(t,e){2===arguments.length?this.setKeyValue(t,e):this.setObject(t)},r.prototype.setKeyValue=function(t,e){this.has(t)||(this.length+=1),this[this.encodeKey(t)]=e},r.prototype.setObject=function(t){var e=this;o.forEachOwnProperties(t,function(t,n){e.setKeyValue(n,t)})},r.prototype.merge=function(t){var e=this;t.each(function(t,n){e.setKeyValue(n,t)})},r.prototype.encodeKey=function(t){return u+t},r.prototype.decodeKey=function(t){var e=t.split(u);return e[e.length-1]},r.prototype.get=function(t){return this[this.encodeKey(t)]},r.prototype.has=function(t){return this.hasOwnProperty(this.encodeKey(t))},r.prototype.remove=function(t){return arguments.length>1&&(t=o.toArray(arguments)),i.isArray(t)?this.removeByKeyArray(t):this.removeByKey(t)},r.prototype.removeByKey=function(t){var e=this.has(t)?this.get(t):null;return null!==e&&(delete this[this.encodeKey(t)],this.length-=1),e},r.prototype.removeByKeyArray=function(t){var e=[],n=this;return o.forEach(t,function(t){e.push(n.removeByKey(t))}),e},r.prototype.removeAll=function(){var t=this;this.each(function(e,n){t.remove(n)})},r.prototype.each=function(t){var e,n=this;o.forEachOwnProperties(this,function(r,o){if(o.charAt(0)===u&&(e=t(r,n.decodeKey(o))),e===!1)return e})},r.prototype.keys=function(){var t=[],e=this;return this.each(function(n,r){t.push(e.decodeKey(r))}),t},r.prototype.find=function(t){var e=[];return this.each(function(n,r){t(n,r)&&e.push(n)}),e},r.prototype.toArray=function(){var t=[];return this.each(function(e){t.push(e)}),t},t.exports=r}])});

/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})}

/**
 * Toast UI Colorpicker
 * @version 1.0.2
 */
!function e(t,o,n){function i(s,l){if(!o[s]){if(!t[s]){var a="function"==typeof require&&require;if(!l&&a)return a(s,!0);if(r)return r(s,!0);var c=new Error("Cannot find module '"+s+"'");throw c.code="MODULE_NOT_FOUND",c}var u=o[s]={exports:{}};t[s][0].call(u.exports,function(e){var o=t[s][1][e];return i(o?o:e)},u,u.exports,e,t,o,n)}return o[s].exports}for(var r="function"==typeof require&&require,s=0;s<n.length;s++)i(n[s]);return i}({1:[function(e,t,o){"use strict";tui.util.defineNamespace("tui.component.colorpicker",{domutil:e("./src/js/core/domutil"),domevent:e("./src/js/core/domevent"),Collection:e("./src/js/core/collection"),View:e("./src/js/core/view"),Drag:e("./src/js/core/drag"),create:e("./src/js/factory"),Palette:e("./src/js/palette"),Slider:e("./src/js/slider"),colorutil:e("./src/js/colorutil"),svgvml:e("./src/js/svgvml")})},{"./src/js/colorutil":2,"./src/js/core/collection":3,"./src/js/core/domevent":4,"./src/js/core/domutil":5,"./src/js/core/drag":6,"./src/js/core/view":7,"./src/js/factory":8,"./src/js/palette":10,"./src/js/slider":11,"./src/js/svgvml":12}],2:[function(e,t,o){"use strict";var n=/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i,i={leadingZero:function(e,t){var o="",n=0;if((e+"").length>t)return e+"";for(;n<t-1;n+=1)o+="0";return(o+e).slice(t*-1)},isValidRGB:function(e){return n.test(e)},hexToRGB:function(e){var t,o,n;return!!i.isValidRGB(e)&&(e=e.substring(1),t=parseInt(e.substr(0,2),16),o=parseInt(e.substr(2,2),16),n=parseInt(e.substr(4,2),16),[t,o,n])},rgbToHEX:function(e,t,o){var n="#"+i.leadingZero(e.toString(16),2)+i.leadingZero(t.toString(16),2)+i.leadingZero(o.toString(16),2);return!!i.isValidRGB(n)&&n},rgbToHSV:function(e,t,o){var n,i,r,s,l,a;if(e/=255,t/=255,o/=255,n=Math.max(e,t,o),i=Math.min(e,t,o),l=n,a=n-i,s=0===n?0:a/n,n===i)r=0;else{switch(n){case e:r=(t-o)/a+(t<o?6:0);break;case t:r=(o-e)/a+2;break;case o:r=(e-t)/a+4}r/=6}return[Math.round(360*r),Math.round(100*s),Math.round(100*l)]},hsvToRGB:function(e,t,o){var n,i,r,s,l,a,c,u;if(e=Math.max(0,Math.min(360,e)),t=Math.max(0,Math.min(100,t)),o=Math.max(0,Math.min(100,o)),t/=100,o/=100,0===t)return n=i=r=o,[Math.round(255*n),Math.round(255*i),Math.round(255*r)];switch(e/=60,s=Math.floor(e),l=e-s,a=o*(1-t),c=o*(1-t*l),u=o*(1-t*(1-l)),s){case 0:n=o,i=u,r=a;break;case 1:n=c,i=o,r=a;break;case 2:n=a,i=o,r=u;break;case 3:n=a,i=c,r=o;break;case 4:n=u,i=a,r=o;break;default:n=o,i=a,r=c}return[Math.round(255*n),Math.round(255*i),Math.round(255*r)]}};t.exports=i},{}],3:[function(e,t,o){(function(e){"use strict";function o(e){this.items={},this.length=0,s(e)&&(this.getItemID=e)}var n=e.tui.util,i=n.forEachOwnProperties,r=n.forEachArray,s=n.isFunction,l=n.isObject,a=Array.prototype.slice;o.and=function(e){var t;return e=a.call(arguments),t=e.length,function(o){for(var n=0;n<t;n+=1)if(!e[n].call(null,o))return!1;return!0}},o.or=function(e){var t;return e=a.call(arguments),t=e.length,function(o){for(var n=1,i=e[0].call(null,o);n<t;n+=1)i=i||e[n].call(null,o);return i}},o.merge=function(e){var t=a.call(arguments),i={},s=new o(t[0].getItemID),l=n.extend;return r(t,function(e){l(i,e.items)}),s.items=i,s.length=n.keys(s.items).length,s},o.prototype.getItemID=function(e){return e._id+""},o.prototype.add=function(e){var t,o;return arguments.length>1?void r(a.call(arguments),function(e){this.add(e)},this):(t=this.getItemID(e),o=this.items,o[t]||(this.length+=1),void(o[t]=e))},o.prototype.remove=function(e){var t,o,i=[];return this.length?arguments.length>1?i=n.map(a.call(arguments),function(e){return this.remove(e)},this):(t=this.items,l(e)&&(e=this.getItemID(e)),t[e]?(this.length-=1,o=t[e],delete t[e],o):i):i},o.prototype.clear=function(){this.items={},this.length=0},o.prototype.has=function(e){var t,o;return!!this.length&&(t=s(e),o=!1,t?this.each(function(t){if(e(t)===!0)return o=!0,!1}):(e=l(e)?this.getItemID(e):e,o=n.isExisty(this.items[e])),o)},o.prototype.doWhenHas=function(e,t,o){var i=this.items[e];n.isExisty(i)&&t.call(o||this,i)},o.prototype.find=function(e){var t=new o;return this.hasOwnProperty("getItemID")&&(t.getItemID=this.getItemID),this.each(function(o){e(o)===!0&&t.add(o)}),t},o.prototype.groupBy=function(e,t){var i,r,s={},l=n.isFunction,a=l(e),c=this.getItemID;if(n.isArray(e)){if(n.forEachArray(e,function(e){s[e+""]=new o(c)}),!t)return s;e=t,a=!0}return this.each(function(t){a?r=e(t):(r=t[e],l(r)&&(r=r.apply(t))),i=s[r],i||(i=s[r]=new o(c)),i.add(t)}),s},o.prototype.single=function(){var e;return this.each(function(t){return e=t,!1},this),e},o.prototype.sort=function(e){var t=[];return this.each(function(e){t.push(e)}),s(e)&&(t=t.sort(e)),t},o.prototype.each=function(e,t){i(this.items,e,t||this)},o.prototype.toArray=function(){return this.length?n.map(this.items,function(e){return e}):[]},t.exports=o}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],4:[function(e,t,o){(function(e){"use strict";var o=e.tui.util,n=o.browser,i="_evt",r={START:["touchstart","mousedown"],END:{mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},MOVE:{mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"}},s={on:function(e,t,n,i){return o.isString(t)?void o.forEach(t.split(" "),function(t){s._on(e,t,n,i)}):void o.forEachOwnProperties(t,function(t,o){s._on(e,o,t,n)})},_on:function(e,t,n,r){var l,a,c;l=t+o.stamp(n)+(r?"_"+o.stamp(r):""),e[i]&&e[i][l]||(a=function(t){n.call(r||e,t||window.event)},c=a,"addEventListener"in e?"mouseenter"===t||"mouseleave"===t?(a=function(t){t=t||window.event,s._checkMouse(e,t)&&c(t)},e.addEventListener("mouseenter"===t?"mouseover":"mouseout",a,!1)):("mousewheel"===t&&e.addEventListener("DOMMouseScroll",a,!1),e.addEventListener(t,a,!1)):"attachEvent"in e&&e.attachEvent("on"+t,a),e[i]=e[i]||{},e[i][l]=a)},off:function(e,t,n,i){return o.isString(t)?void o.forEach(t.split(" "),function(t){s._off(e,t,n,i)}):void o.forEachOwnProperties(t,function(t,o){s._off(e,o,t,n)})},_off:function(e,t,n,r){var s=t+o.stamp(n)+(r?"_"+o.stamp(r):""),l=e[i]&&e[i][s];if(l){if("removeEventListener"in e)"mouseenter"===t||"mouseleave"===t?e.removeEventListener("mouseenter"===t?"mouseover":"mouseout",l,!1):("mousewheel"===t&&e.removeEventListener("DOMMouseScroll",l,!1),e.removeEventListener(t,l,!1));else if("detachEvent"in e)try{e.detachEvent("on"+t,l)}catch(a){}if(delete e[i][s],!o.keys(e[i]).length)return o.browser.msie&&o.browser.version<9?void(e[i]=null):void delete e[i]}},once:function(e,t,n,i){function r(){n.apply(i||e,arguments),l._off(e,t,r,i)}var l=this;return o.isObject(t)?void o.forEachOwnProperties(t,function(t,o){s.once(e,o,t,n)}):void s.on(e,t,r,i)},stopPropagation:function(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0},preventDefault:function(e){e.preventDefault?e.preventDefault():e.returnValue=!1},stop:function(e){s.preventDefault(e),s.stopPropagation(e)},disableScrollPropagation:function(e){s.on(e,"mousewheel MozMousePixelScroll",s.stopPropagation)},disableClickPropagation:function(e){s.on(e,r.START.join(" ")+" click dblclick",s.stopPropagation)},getMousePosition:function(e,t){var o;return t?(o=t.getBoundingClientRect(),[e.clientX-o.left-t.clientLeft,e.clientY-o.top-t.clientTop]):[e.clientX,e.clientY]},getWheelDelta:function(e){var t=0;return e.wheelDelta&&(t=e.wheelDelta/120),e.detail&&(t=-e.detail/3),t},_checkMouse:function(e,t){var o=t.relatedTarget;if(!o)return!0;try{for(;o&&o!==e;)o=o.parentNode}catch(n){return!1}return o!==e},trigger:function(e,t,n){var i=/(mouse|click)/;o.isUndefined(n)&&i.exec(t)&&(n=s.mouseEvent(t)),e.dispatchEvent?e.dispatchEvent(n):e.fireEvent&&e.fireEvent("on"+t,n)},mouseEvent:function(e,t){var i,r;return r=o.extend({bubbles:!0,cancelable:"mousemove"!==e,view:window,wheelDelta:0,detail:0,screenX:0,screenY:0,clientX:0,clientY:0,ctrlKey:!1,altKey:!1,shiftKey:!1,metaKey:!1,button:0,relatedTarget:void 0},t),n.msie&&n.version<9&&delete r.wheelDelta,"function"==typeof document.createEvent?(i=document.createEvent("MouseEvents"),i.initMouseEvent(e,r.bubbles,r.cancelable,r.view,r.detail,r.screenX,r.screenY,r.clientX,r.clientY,r.ctrlKey,r.altKey,r.shiftKey,r.metaKey,r.button,document.body.parentNode)):document.createEventObject&&(i=document.createEventObject(),o.forEach(r,function(e,t){i[t]=e},this),i.button={0:1,1:4,2:2}[i.button]||i.button),i},getMouseButton:function(e){var t,o="0,1,3,5,7",n="2,6",i="4";return document.implementation.hasFeature("MouseEvents","2.0")?e.button:(t=e.button+"",~o.indexOf(t)?0:~n.indexOf(t)?2:~i.indexOf(t)?1:void 0)}};t.exports=s}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],5:[function(e,t,o){(function(o){"use strict";function n(e){return e.replace(/^\s\s*/,"").replace(/\s\s*$/,"")}var i,r=e("./domevent"),s=e("./collection"),l=o.tui.util,a="_pos",c=/^auto$|^$|%/;i={appendHTMLElement:function(e,t,o){var n;return o=o||"",n=document.createElement(e),n.className=o,t?t.appendChild(n):document.body.appendChild(n),n},remove:function(e){e&&e.parentNode&&e.parentNode.removeChild(e)},get:function(e){return document.getElementById(e)},_matcher:function(e,t){var o=/^\./,n=/^#/;return o.test(t)?i.hasClass(e,t.replace(".","")):n.test(t)?e.id===t.replace("#",""):e.nodeName.toLowerCase()===t.toLowerCase()},find:function(e,t,o){function n(e,t){for(var l,u=e.childNodes,d=0,f=u.length;d<f;d+=1)if(l=u[d],"#text"!==l.nodeName)if(i._matcher(l,t)){if((c&&o(l)||!c)&&r.push(l),a){s=!0;break}}else if(l.childNodes.length>0&&(n(l,t),s))break}var r=[],s=!1,a=l.isUndefined(o)||o===!1,c=l.isFunction(o);return l.isString(t)&&(t=i.get(t)),t=t||window.document.body,n(t,e),a?r[0]||null:r},closest:function(e,t){var o=e.parentNode;if(i._matcher(e,t))return e;for(;o&&o!==window.document.body;){if(i._matcher(o,t))return o;o=o.parentNode}},text:function(e){var t="",o=0,n=e.nodeType;if(n){if(1===n||9===n||11===n){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)t+=i.text(e)}else if(3===n||4===n)return e.nodeValue}else for(;e[o];o+=1)t+=i.text(e[o]);return t},setData:function(e,t,o){return"dataset"in e?void(e.dataset[t]=o):void e.setAttribute("data-"+t,o)},getData:function(e,t){return"dataset"in e?e.dataset[t]:e.getAttribute("data-"+t)},hasClass:function(e,t){var o;return l.isUndefined(e.classList)?(o=i.getClass(e),o.length>0&&new RegExp("(^|\\s)"+t+"(\\s|$)").test(o)):e.classList.contains(t)},addClass:function(e,t){var o;l.isUndefined(e.classList)?i.hasClass(e,t)||(o=i.getClass(e),i.setClass(e,(o?o+" ":"")+t)):l.forEachArray(t.split(" "),function(t){e.classList.add(t)})},setClass:function(e,t){l.isUndefined(e.className.baseVal)?e.className=t:e.className.baseVal=t},removeClass:function(e,t){var o="";l.isUndefined(e.classList)?(o=(" "+i.getClass(e)+" ").replace(" "+t+" "," "),i.setClass(e,n(o))):e.classList.remove(t)},getClass:function(e){return e&&e.className?l.isUndefined(e.className.baseVal)?e.className:e.className.baseVal:""},getStyle:function(e,t){var o,n=e.style[t]||e.currentStyle&&e.currentStyle[t];return n&&"auto"!==n||!document.defaultView||(o=document.defaultView.getComputedStyle(e,null),n=o?o[t]:null),"auto"===n?null:n},getComputedStyle:function(e){var t=document.defaultView;return t&&t.getComputedStyle?document.defaultView.getComputedStyle(e):{getPropertyValue:function(t){var o=/(\-([a-z]){1})/g;return"float"===t&&(t="styleFloat"),o.test(t)&&(t=t.replace(o,function(){return arguments[2].toUpperCase()})),e.currentStyle[t]?e.currentStyle[t]:null}}},setPosition:function(e,t,o){t=l.isUndefined(t)?0:t,o=l.isUndefined(o)?0:o,e[a]=[t,o],e.style.left=t+"px",e.style.top=o+"px"},getPosition:function(e,t){var o,n,i;return t&&(e[a]=null),e[a]?e[a]:(o=0,n=0,(c.test(e.style.left)||c.test(e.style.top))&&"getBoundingClientRect"in e?(i=e.getBoundingClientRect(),o=i.left,n=i.top):(o=parseFloat(e.style.left||0),n=parseFloat(e.style.top||0)),[o,n])},getSize:function(e){var t,o=i.getStyle(e,"width"),n=i.getStyle(e,"height");return(c.test(o)||c.test(n))&&"getBoundingClientRect"in e?(t=e.getBoundingClientRect(),o=t.width,n=t.height):(o=parseFloat(o||0),n=parseFloat(n||0)),[o,n]},testProp:function(e){for(var t=document.documentElement.style,o=0,n=e.length;o<n;o+=1)if(e[o]in t)return e[o];return!1},getFormData:function(e){var t=new s(function(){return this.length}),o=function(e){return!e.disabled},n={};return t.add.apply(t,i.find("input",e,o).concat(i.find("select",e,o)).concat(i.find("textarea",e,o))),t=t.groupBy(function(e){return e&&e.getAttribute("name")||"_other"}),l.forEach(t,function(e,t){"_other"!==t&&e.each(function(o){var r=o.nodeName.toLowerCase(),s=o.type,a=[];"radio"===s?a=[e.find(function(e){return e.checked}).toArray().pop()]:"checkbox"===s?a=e.find(function(e){return e.checked}).toArray():"select"===r?e.find(function(e){return!!e.childNodes.length}).each(function(e){a=a.concat(i.find("option",e,function(e){return e.selected}))}):a=e.find(function(e){return""!==e.value}).toArray(),a=l.map(a,function(e){return e.value}),a.length?1===a.length&&(a=a[0]):a="",n[t]=a})}),n}};var u=i.testProp(["userSelect","WebkitUserSelect","OUserSelect","MozUserSelect","msUserSelect"]),d="onselectstart"in document,f="";i.disableTextSelection=function(){return d?function(){r.on(window,"selectstart",r.preventDefault)}:function(){var e=document.documentElement.style;f=e[u],e[u]="none"}}(),i.enableTextSelection=function(){return d?function(){r.off(window,"selectstart",r.preventDefault)}:function(){document.documentElement.style[u]=f}}(),i.disableImageDrag=function(){r.on(window,"dragstart",r.preventDefault)},i.enableImageDrag=function(){r.off(window,"dragstart",r.preventDefault)},t.exports=i}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./collection":3,"./domevent":4}],6:[function(e,t,o){(function(o){"use strict";function n(e,t){s.on(t,"mousedown",this._onMouseDown,this),this.options=i.extend({distance:10},e),this.container=t,this._isMoved=!1,this._distance=0,this._dragStartFired=!1,this._dragStartEventData=null}var i=o.tui.util,r=e("./domutil"),s=e("./domevent");n.prototype.destroy=function(){s.off(this.container,"mousedown",this._onMouseDown,this),this.options=this.container=this._isMoved=this._distance=this._dragStartFired=this._dragStartEventData=null},n.prototype._toggleDragEvent=function(e){var t,n,i=this.container;e?(t="on",n="disable"):(t="off",n="enable"),r[n+"TextSelection"](i),r[n+"ImageDrag"](i),s[t](o.document,{mousemove:this._onMouseMove,mouseup:this._onMouseUp},this)},n.prototype._getEventData=function(e){return{target:e.target||e.srcElement,originEvent:e}},n.prototype._onMouseDown=function(e){0===s.getMouseButton(e)&&(this._distance=0,this._dragStartFired=!1,this._dragStartEventData=this._getEventData(e),this._toggleDragEvent(!0))},n.prototype._onMouseMove=function(e){var t=this.options.distance;return s.preventDefault(e),this._isMoved=!0,this._distance<t?void(this._distance+=1):this._dragStartFired||(this._dragStartFired=!0,this.invoke("dragStart",this._dragStartEventData))?void this.fire("drag",this._getEventData(e)):void this._toggleDragEvent(!1)},n.prototype._onMouseUp=function(e){return this._toggleDragEvent(!1),this._isMoved?(this._isMoved=!1,void this.fire("dragEnd",this._getEventData(e))):void this.fire("click",this._getEventData(e))},i.CustomEvents.mixin(n),t.exports=n}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./domevent":4,"./domutil":5}],7:[function(e,t,o){(function(o){"use strict";function n(e,t){var o=i.stamp(this);e=e||{},i.isUndefined(t)&&(t=r.appendHTMLElement("div")),r.addClass(t,"tui-view-"+o),this.id=o,this.container=t,this.childs=new s(function(e){return i.stamp(e)}),this.parent=null}var i=o.tui.util,r=e("./domutil"),s=e("./collection");n.prototype.addChild=function(e,t){t&&t.call(e,this),e.parent=this,this.childs.add(e)},n.prototype.removeChild=function(e,t){var o=i.isNumber(e)?this.childs.items[e]:e;e=i.stamp(o),t&&t.call(o,this),this.childs.remove(e)},n.prototype.render=function(){this.childs.each(function(e){e.render()})},n.prototype.recursive=function(e,t){i.isFunction(e)&&(t||e(this),this.childs.each(function(t){t.recursive(e)}))},n.prototype.resize=function(){for(var e=Array.prototype.slice.call(arguments),t=this.parent;t;)i.isFunction(t._onResize)&&t._onResize.apply(t,e),t=t.parent},n.prototype._beforeDestroy=function(){},n.prototype._destroy=function(){this._beforeDestroy(),this.childs.clear(),this.container.innerHTML="",this.id=this.parent=this.childs=this.container=null},n.prototype.destroy=function(e){this.childs.each(function(e){e.destroy(!0),e._destroy()}),e||this._destroy()},n.prototype.getViewBound=function(){var e=this.container,t=r.getPosition(e),o=r.getSize(e);return{x:t[0],y:t[1],width:o[0],height:o[1]}},t.exports=n}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./collection":3,"./domutil":5}],8:[function(e,t,o){(function(o){"use strict";function n(e){alert(e)}function i(e){var t;return this instanceof i?(e=this.options=r.extend({container:null,color:"#f8f8f8",preset:["#181818","#282828","#383838","#585858","#b8b8b8","#d8d8d8","#e8e8e8","#f8f8f8","#ab4642","#dc9656","#f7ca88","#a1b56c","#86c1b9","#7cafc2","#ba8baf","#a16946"],cssPrefix:"tui-colorpicker-",detailTxt:"Detail"},e),e.container?(t=this.layout=new l(e,e.container),this.palette=new a(e,t.container),this.palette.on({_selectColor:this._onSelectColorInPalette,_toggleSlider:this._onToggleSlider},this),this.slider=new c(e,t.container),this.slider.on("_selectColor",this._onSelectColorInSlider,this),t.addChild(this.palette),t.addChild(this.slider),void this.render(e.color)):void n("Colorpicker(): need container option.")):new i(e)}var r=o.tui.util,s=e("./colorutil"),l=e("./layout"),a=e("./palette"),c=e("./slider");i.prototype._onSelectColorInPalette=function(e){var t=e.color,o=this.options;return s.isValidRGB(t)?(this.fire("selectColor",{color:t,origin:"palette"}),void(o.color!==t&&(o.color=t,this.render(t)))):void this.render()},i.prototype._onToggleSlider=function(){this.slider.toggle(!this.slider.isVisible())},i.prototype._onSelectColorInSlider=function(e){var t=e.color,o=this.options;this.fire("selectColor",{color:t,origin:"slider"}),o.color!==t&&(o.color=t,this.palette.render(t))},i.prototype.setColor=function(e){s.isValidRGB(e)||n("Colorpicker#setColor(): need valid hex string color value"),this.options.color=e,this.render(e)},i.prototype.getColor=function(){return this.options.color},i.prototype.toggle=function(e){this.layout.container.style.display=e?"block":"none"},i.prototype.render=function(e){this.layout.render(e||this.options.color)},i.prototype.destroy=function(){this.layout.destroy(),this.options.container.innerHTML="",this.layout=this.slider=this.palette=this.options=null},r.CustomEvents.mixin(i),t.exports=i}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./colorutil":2,"./layout":9,"./palette":10,"./slider":11}],9:[function(e,t,o){(function(o){"use strict";function n(e,t){this.options=i.extend({cssPrefix:"tui-colorpicker-"},e),t=r.appendHTMLElement("div",t,this.options.cssPrefix+"container"),s.call(this,e,t),this.render()}var i=o.tui.util,r=e("./core/domutil"),s=e("./core/view");i.inherit(n,s),n.prototype.render=function(e){this.recursive(function(t){t.render(e)},!0)},t.exports=n}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./core/domutil":5,"./core/view":7}],10:[function(e,t,o){(function(o){"use strict";function n(e,t){this.options=i.extend({cssPrefix:"tui-colorpicker-",preset:["#181818","#282828","#383838","#585858","#B8B8B8","#D8D8D8","#E8E8E8","#F8F8F8","#AB4642","#DC9656","#F7CA88","#A1B56C","#86C1B9","#7CAFC2","#BA8BAF","#A16946"],detailTxt:"Detail"},e),t=r.appendHTMLElement("div",t,this.options.cssPrefix+"palette-container"),l.call(this,e,t)}var i=o.tui.util,r=e("./core/domutil"),s=e("./core/domevent"),l=e("./core/view"),a=e("../template/palette");i.inherit(n,l),n.prototype._onClick=function(e){var t=this.options,o=e.srcElement||e.target,n={};return r.hasClass(o,t.cssPrefix+"palette-button")?(n.color=o.value,void this.fire("_selectColor",n)):void(r.hasClass(o,t.cssPrefix+"palette-toggle-slider")&&this.fire("_toggleSlider"))},n.prototype._onChange=function(e){var t=this.options,o=e.srcElement||e.target,n={};if(r.hasClass(o,t.cssPrefix+"palette-hex"))return n.color=o.value,void this.fire("_selectColor",n)},n.prototype._beforeDestroy=function(){this._toggleEvent(!1)},n.prototype._toggleEvent=function(e){var t,o=this.options,n=this.container,i=s[e?"on":"off"];i(n,"click",this._onClick,this),t=r.find("."+o.cssPrefix+"palette-hex",n),t&&i(t,"change",this._onChange,this)},n.prototype.render=function(e){var t=this.options,o="";this._toggleEvent(!1),o=a.layout.replace("{{colorList}}",i.map(t.preset,function(o){var n=a.item.replace(/{{color}}/g,o);return n=n.replace("{{selected}}",o===e?" "+t.cssPrefix+"selected":"")}).join("")),o=o.replace(/{{cssPrefix}}/g,t.cssPrefix).replace("{{detailTxt}}",t.detailTxt).replace(/{{color}}/g,e),this.container.innerHTML=o,this._toggleEvent(!0)},i.CustomEvents.mixin(n),t.exports=n}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../template/palette":13,"./core/domevent":4,"./core/domutil":5,"./core/view":7}],11:[function(e,t,o){(function(o){"use strict";function n(e,t){t=r.appendHTMLElement("div",t,e.cssPrefix+"slider-container"),t.style.display="none",c.call(this,e,t),this.options=i.extend({color:"#f8f8f8",cssPrefix:"tui-colorpicker-"},e),this._dragDataCache={},this.sliderHandleElement=null,this.huebarHandleElement=null,this.baseColorElement=null,this.drag=new u({distance:0},t),this.drag.on({dragStart:this._onDragStart,drag:this._onDrag,dragEnd:this._onDragEnd,click:this._onClick},this)}var i=o.tui.util,r=e("./core/domutil"),s=e("./core/domevent"),l=e("./svgvml"),a=e("./colorutil"),c=e("./core/view"),u=e("./core/drag"),d=e("../template/slider"),f=[-7,112],p=[-3,115],h=359.99;i.inherit(n,c),n.prototype._beforeDestroy=function(){this.drag.off(),this.drag=this.options=this._dragDataCache=this.sliderHandleElement=this.huebarHandleElement=this.baseColorElement=null},n.prototype.toggle=function(e){this.container.style.display=e?"block":"none"},n.prototype.isVisible=function(){return"block"===this.container.style.display},n.prototype.render=function(e){var t,o,n=this,i=n.container,s=n.options,l=d.layout;l=l.replace(/{{slider}}/,d.slider),l=l.replace(/{{huebar}}/,d.huebar),l=l.replace(/{{cssPrefix}}/g,s.cssPrefix),n.container.innerHTML=l,n.sliderHandleElement=r.find("."+s.cssPrefix+"slider-handle",i),n.huebarHandleElement=r.find("."+s.cssPrefix+"huebar-handle",i),n.baseColorElement=r.find("."+s.cssPrefix+"slider-basecolor",i),t=a.hexToRGB(e),o=a.rgbToHSV.apply(null,t),this.moveHue(o[0],!0),this.moveSaturationAndValue(o[1],o[2],!0)},n.prototype._moveColorSliderHandle=function(e,t,o){var n,i=this.sliderHandleElement;t=Math.max(f[0],t),t=Math.min(f[1],t),e=Math.max(f[0],e),e=Math.min(f[1],e),l.setTranslateXY(i,e,t),n=t>50?"white":"black",l.setStrokeColor(i,n),o||this.fire("_selectColor",{color:a.rgbToHEX.apply(null,this.getRGB())})},n.prototype.moveSaturationAndValue=function(e,t,o){var n,i,r,s;e=e||0,t=t||0,n=Math.abs(f[0]),i=f[1],r=e*i/100-n,s=i-t*i/100-n,this._moveColorSliderHandle(r,s,o)},n.prototype._moveColorSliderByPosition=function(e,t){var o=f[0];this._moveColorSliderHandle(e+o,t+o)},n.prototype.getSaturationAndValue=function(){var e,t,o=Math.abs(f[0]),n=o+f[1],i=l.getTranslateXY(this.sliderHandleElement);return e=(i[1]+o)/n*100,t=100-(i[0]+o)/n*100,[e,t]},n.prototype._moveHueHandle=function(e,t){var o,n,i=this.huebarHandleElement,r=this.baseColorElement;e=Math.max(p[0],e),e=Math.min(p[1],e),l.setTranslateY(i,e),o=a.hsvToRGB(this.getHue(),100,100),n=a.rgbToHEX.apply(null,o),l.setGradientColorStop(r,n),t||this.fire("_selectColor",{color:a.rgbToHEX.apply(null,this.getRGB())})},n.prototype.moveHue=function(e,t){var o,n,i=0;o=Math.abs(p[0]),n=o+p[1],e=e||0,i=n*e/h-o,this._moveHueHandle(i,t)},n.prototype._moveHueByPosition=function(e){var t=p[0];this._moveHueHandle(e+t)},n.prototype.getHue=function(){var e,t,o=this.huebarHandleElement,n=l.getTranslateXY(o);return e=Math.abs(p[0]),t=e+p[1],(n[0]+e)*h/t},n.prototype.getHSV=function(){var e=this.getSaturationAndValue(),t=this.getHue();return[t].concat(e)},n.prototype.getRGB=function(){return a.hsvToRGB.apply(null,this.getHSV())},n.prototype._prepareColorSliderForMouseEvent=function(e){var t,o=this.options,n=r.closest(e.target,"."+o.cssPrefix+"slider-part");return t=this._dragDataCache={isColorSlider:r.hasClass(n,o.cssPrefix+"slider-left"),parentElement:n}},n.prototype._onClick=function(e){var t=this._prepareColorSliderForMouseEvent(e),o=s.getMousePosition(e.originEvent,t.parentElement);t.isColorSlider?this._moveColorSliderByPosition(o[0],o[1]):this._moveHueByPosition(o[1]),this._dragDataCache=null},n.prototype._onDragStart=function(e){this._prepareColorSliderForMouseEvent(e)},n.prototype._onDrag=function(e){var t=this._dragDataCache,o=s.getMousePosition(e.originEvent,t.parentElement);t.isColorSlider?this._moveColorSliderByPosition(o[0],o[1]):this._moveHueByPosition(o[1])},n.prototype._onDragEnd=function(){this._dragDataCache=null},i.CustomEvents.mixin(n),t.exports=n}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../template/slider":14,"./colorutil":2,"./core/domevent":4,"./core/domutil":5,"./core/drag":6,"./core/view":7,"./svgvml":12}],12:[function(e,t,o){(function(e){"use strict";var o=e.tui.util,n=/[\.\-0-9]+/g,i=-6,r={isOldBrowser:function(){var e=r._isOldBrowser;return o.isExisty(e)||(r._isOldBrowser=e=o.browser.msie&&o.browser.version<9),e},getTranslateXY:function(e){var t;return r.isOldBrowser()?(t=e.style,[parseFloat(t.top),parseFloat(t.left)]):(t=e.getAttribute("transform"))?(t=t.match(n),[parseFloat(t[1]),parseFloat(t[0])]):[0,0]},setTranslateXY:function(e,t,o){r.isOldBrowser()?(e.style.left=t+"px",e.style.top=o+"px"):e.setAttribute("transform","translate("+t+","+o+")")},setTranslateY:function(e,t){r.isOldBrowser()?e.style.top=t+"px":e.setAttribute("transform","translate("+i+","+t+")")},setStrokeColor:function(e,t){r.isOldBrowser()?e.strokecolor=t:e.setAttribute("stroke",t)},setGradientColorStop:function(e,t){r.isOldBrowser()?e.color=t:e.setAttribute("stop-color",t)}};t.exports=r}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],13:[function(e,t,o){"use strict";var n=['<ul class="{{cssPrefix}}clearfix">{{colorList}}</ul>','<div class="{{cssPrefix}}clearfix" style="overflow:hidden">','<input type="button" class="{{cssPrefix}}palette-toggle-slider" value="{{detailTxt}}" />','<input type="text" class="{{cssPrefix}}palette-hex" value="{{color}}" maxlength="7" />','<span class="{{cssPrefix}}palette-preview" style="background-color:{{color}};color:{{color}}">{{color}}</span>',"</div>"].join("\n"),i='<li><input class="{{cssPrefix}}palette-button{{selected}}" type="button" style="background-color:{{color}};color:{{color}}" title="{{color}}" value="{{color}}" /></li>';t.exports={layout:n,item:i}},{}],14:[function(e,t,o){(function(e){"use strict";var o=e.tui.util,n=['<div class="{{cssPrefix}}slider-left {{cssPrefix}}slider-part">{{slider}}</div>','<div class="{{cssPrefix}}slider-right {{cssPrefix}}slider-part">{{huebar}}</div>'].join("\n"),i=['<svg class="{{cssPrefix}}svg {{cssPrefix}}svg-slider">',"<defs>",'<linearGradient id="{{cssPrefix}}svg-fill-color" x1="0%" y1="0%" x2="100%" y2="0%">','<stop offset="0%" stop-color="rgb(255,255,255)" />','<stop class="{{cssPrefix}}slider-basecolor" offset="100%" stop-color="rgb(255,0,0)" />',"</linearGradient>",'<linearGradient id="{{cssPrefix}}svn-fill-black" x1="0%" y1="0%" x2="0%" y2="100%">','<stop offset="0%" style="stop-color:rgb(0,0,0);stop-opacity:0" />','<stop offset="100%" style="stop-color:rgb(0,0,0);stop-opacity:1" />',"</linearGradient>","</defs>",'<rect width="100%" height="100%" fill="url(#{{cssPrefix}}svg-fill-color)"></rect>','<rect width="100%" height="100%" fill="url(#{{cssPrefix}}svn-fill-black)"></rect>','<path transform="translate(0,0)" class="{{cssPrefix}}slider-handle" d="M0 7.5 L15 7.5 M7.5 15 L7.5 0 M2 7 a5.5 5.5 0 1 1 0 1 Z" stroke="black" stroke-width="0.75" fill="none" />',"</svg>"].join("\n"),r=['<div class="{{cssPrefix}}vml-slider">','<v:rect strokecolor="none" class="{{cssPrefix}}vml {{cssPrefix}}vml-slider-bg">','<v:fill class="{{cssPrefix}}vml {{cssPrefix}}slider-basecolor" type="gradient" method="none" color="#ff0000" color2="#fff" angle="90" />',"</v:rect>",'<v:rect strokecolor="#ccc" class="{{cssPrefix}}vml {{cssPrefix}}vml-slider-bg">','<v:fill type="gradient" method="none" color="black" color2="white" o:opacity2="0%" class="{{cssPrefix}}vml" />',"</v:rect>",'<v:shape class="{{cssPrefix}}vml {{cssPrefix}}slider-handle" coordsize="1 1" style="width:1px;height:1px;"path="m 0,7 l 14,7 m 7,14 l 7,0 ar 12,12 2,2 z" filled="false" stroked="true" />',"</div>"].join("\n"),s=['<svg class="{{cssPrefix}}svg {{cssPrefix}}svg-huebar">',"<defs>",'<linearGradient id="g" x1="0%" y1="0%" x2="0%" y2="100%">','<stop offset="0%" stop-color="rgb(255,0,0)" />','<stop offset="16.666%" stop-color="rgb(255,255,0)" />','<stop offset="33.333%" stop-color="rgb(0,255,0)" />','<stop offset="50%" stop-color="rgb(0,255,255)" />','<stop offset="66.666%" stop-color="rgb(0,0,255)" />','<stop offset="83.333%" stop-color="rgb(255,0,255)" />','<stop offset="100%" stop-color="rgb(255,0,0)" />',"</linearGradient>","</defs>",'<rect width="18px" height="100%" fill="url(#g)"></rect>','<path transform="translate(-6,-3)" class="{{cssPrefix}}huebar-handle" d="M0 0 L4 4 L0 8 L0 0 Z" fill="black" stroke="none" />',"</svg>"].join("\n"),l=['<div class="{{cssPrefix}}vml-huebar">','<v:rect strokecolor="#ccc" class="{{cssPrefix}}vml {{cssPrefix}}vml-huebar-bg">','<v:fill type="gradient" method="none" colors="0% rgb(255,0,0), 16.666% rgb(255,255,0), 33.333% rgb(0,255,0), 50% rgb(0,255,255), 66.666% rgb(0,0,255), 83.333% rgb(255,0,255), 100% rgb(255,0,0)" angle="180" class="{{cssPrefix}}vml" />',"</v:rect>",'<v:shape class="{{cssPrefix}}vml {{cssPrefix}}huebar-handle" coordsize="1 1" style="width:1px;height:1px;position:absolute;z-index:1;right:22px;top:-3px;"path="m 0,0 l 4,4 l 0,8 l 0,0 z" filled="true" fillcolor="black" stroked="false" />',"</div>"].join("\n"),a=o.browser.msie&&o.browser.version<9;a&&e.document.namespaces.add("v","urn:schemas-microsoft-com:vml"),t.exports={layout:n,slider:a?r:i,huebar:a?l:s}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[1]);

/*!
 * tui-image-editor.js
 * @version 3.2.0
 * @author NHNEnt FE Development Lab <dl_javascript@nhnent.com>
 * @license MIT
 */
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory(require("tui-code-snippet"), require("fabric/dist/fabric.require"));
	else if(typeof define === 'function' && define.amd)
		define(["tui-code-snippet", "fabric/dist/fabric.require"], factory);
	else if(typeof exports === 'object')
		exports["ImageEditor"] = factory(require("tui-code-snippet"), require("fabric/dist/fabric.require"));
	else
		root["tui"] = root["tui"] || {}, root["tui"]["ImageEditor"] = factory((root["tui"] && root["tui"]["util"]), root["fabric"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_105__) {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};

/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {

/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])
/******/ 			return installedModules[moduleId].exports;

/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			exports: {},
/******/ 			id: moduleId,
/******/ 			loaded: false
/******/ 		};

/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/ 		// Flag the module as loaded
/******/ 		module.loaded = true;

/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}


/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;

/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;

/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "dist";

/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	__webpack_require__(1);

	var _imageEditor = __webpack_require__(2);

	var _imageEditor2 = _interopRequireDefault(_imageEditor);

	__webpack_require__(130);

	__webpack_require__(132);

	__webpack_require__(133);

	__webpack_require__(134);

	__webpack_require__(135);

	__webpack_require__(136);

	__webpack_require__(137);

	__webpack_require__(138);

	__webpack_require__(139);

	__webpack_require__(140);

	__webpack_require__(141);

	__webpack_require__(142);

	__webpack_require__(143);

	__webpack_require__(144);

	__webpack_require__(145);

	__webpack_require__(146);

	__webpack_require__(147);

	__webpack_require__(148);

	__webpack_require__(149);

	__webpack_require__(150);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	module.exports = _imageEditor2.default;

	// commands

/***/ }),
/* 1 */
/***/ (function(module, exports) {

	"use strict";

	// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
	// Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/if (!Element.prototype.matches)
	Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;

	if (!Element.prototype.closest) Element.prototype.closest = function (s) {
	    var el = this;
	    if (!document.documentElement.contains(el)) return null;
	    do {
	        if (el.matches(s)) return el;
	        el = el.parentElement || el.parentNode;
	    } while (el !== null && el.nodeType === 1);
	    return null;
	};

	/*
	 * classList.js: Cross-browser full element.classList implementation.
	 * 1.1.20170427
	 *
	 * By Eli Grey, http://eligrey.com
	 * License: Dedicated to the public domain.
	 *   See https://github.com/eligrey/classList.js/blob/master/LICENSE.md
	 */

	/*global self, document, DOMException */

	/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */

	if ("document" in window.self) {

	    // Full polyfill for browsers with no classList support
	    // Including IE < Edge missing SVGElement.classList
	    if (!("classList" in document.createElement("_")) || document.createElementNS && !("classList" in document.createElementNS("http://www.w3.org/2000/svg", "g"))) {

	        (function (view) {

	            "use strict";

	            if (!('Element' in view)) return;

	            var classListProp = "classList",
	                protoProp = "prototype",
	                elemCtrProto = view.Element[protoProp],
	                objCtr = Object,
	                strTrim = String[protoProp].trim || function () {
	                return this.replace(/^\s+|\s+$/g, "");
	            },
	                arrIndexOf = Array[protoProp].indexOf || function (item) {
	                var i = 0,
	                    len = this.length;
	                for (; i < len; i++) {
	                    if (i in this && this[i] === item) {
	                        return i;
	                    }
	                }
	                return -1;
	            }
	            // Vendors: please allow content code to instantiate DOMExceptions
	            ,
	                DOMEx = function DOMEx(type, message) {
	                this.name = type;
	                this.code = DOMException[type];
	                this.message = message;
	            },
	                checkTokenAndGetIndex = function checkTokenAndGetIndex(classList, token) {
	                if (token === "") {
	                    throw new DOMEx("SYNTAX_ERR", "An invalid or illegal string was specified");
	                }
	                if (/\s/.test(token)) {
	                    throw new DOMEx("INVALID_CHARACTER_ERR", "String contains an invalid character");
	                }
	                return arrIndexOf.call(classList, token);
	            },
	                ClassList = function ClassList(elem) {
	                var trimmedClasses = strTrim.call(elem.getAttribute("class") || ""),
	                    classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
	                    i = 0,
	                    len = classes.length;
	                for (; i < len; i++) {
	                    this.push(classes[i]);
	                }
	                this._updateClassName = function () {
	                    elem.setAttribute("class", this.toString());
	                };
	            },
	                classListProto = ClassList[protoProp] = [],
	                classListGetter = function classListGetter() {
	                return new ClassList(this);
	            };
	            // Most DOMException implementations don't allow calling DOMException's toString()
	            // on non-DOMExceptions. Error's toString() is sufficient here.
	            DOMEx[protoProp] = Error[protoProp];
	            classListProto.item = function (i) {
	                return this[i] || null;
	            };
	            classListProto.contains = function (token) {
	                token += "";
	                return checkTokenAndGetIndex(this, token) !== -1;
	            };
	            classListProto.add = function () {
	                var tokens = arguments,
	                    i = 0,
	                    l = tokens.length,
	                    token,
	                    updated = false;
	                do {
	                    token = tokens[i] + "";
	                    if (checkTokenAndGetIndex(this, token) === -1) {
	                        this.push(token);
	                        updated = true;
	                    }
	                } while (++i < l);

	                if (updated) {
	                    this._updateClassName();
	                }
	            };
	            classListProto.remove = function () {
	                var tokens = arguments,
	                    i = 0,
	                    l = tokens.length,
	                    token,
	                    updated = false,
	                    index;
	                do {
	                    token = tokens[i] + "";
	                    index = checkTokenAndGetIndex(this, token);
	                    while (index !== -1) {
	                        this.splice(index, 1);
	                        updated = true;
	                        index = checkTokenAndGetIndex(this, token);
	                    }
	                } while (++i < l);

	                if (updated) {
	                    this._updateClassName();
	                }
	            };
	            classListProto.toggle = function (token, force) {
	                token += "";

	                var result = this.contains(token),
	                    method = result ? force !== true && "remove" : force !== false && "add";

	                if (method) {
	                    this[method](token);
	                }

	                if (force === true || force === false) {
	                    return force;
	                } else {
	                    return !result;
	                }
	            };
	            classListProto.toString = function () {
	                return this.join(" ");
	            };

	            if (objCtr.defineProperty) {
	                var classListPropDesc = {
	                    get: classListGetter,
	                    enumerable: true,
	                    configurable: true
	                };
	                try {
	                    objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
	                } catch (ex) {
	                    // IE 8 doesn't support enumerable:true
	                    // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36
	                    // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected
	                    if (ex.number === undefined || ex.number === -0x7FF5EC54) {
	                        classListPropDesc.enumerable = false;
	                        objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
	                    }
	                }
	            } else if (objCtr[protoProp].__defineGetter__) {
	                elemCtrProto.__defineGetter__(classListProp, classListGetter);
	            }
	        })(window.self);
	    }

	    // There is full or partial native classList support, so just check if we need
	    // to normalize the add/remove and toggle APIs.

	    (function () {
	        "use strict";

	        var testElement = document.createElement("_");

	        testElement.classList.add("c1", "c2");

	        // Polyfill for IE 10/11 and Firefox <26, where classList.add and
	        // classList.remove exist but support only one argument at a time.
	        if (!testElement.classList.contains("c2")) {
	            var createMethod = function createMethod(method) {
	                var original = DOMTokenList.prototype[method];

	                DOMTokenList.prototype[method] = function (token) {
	                    var i,
	                        len = arguments.length;

	                    for (i = 0; i < len; i++) {
	                        token = arguments[i];
	                        original.call(this, token);
	                    }
	                };
	            };
	            createMethod('add');
	            createMethod('remove');
	        }

	        testElement.classList.toggle("c3", false);

	        // Polyfill for IE 10 and Firefox <24, where classList.toggle does not
	        // support the second argument.
	        if (testElement.classList.contains("c3")) {
	            var _toggle = DOMTokenList.prototype.toggle;

	            DOMTokenList.prototype.toggle = function (token, force) {
	                if (1 in arguments && !this.contains(token) === !force) {
	                    return force;
	                } else {
	                    return _toggle.call(this, token);
	                }
	            };
	        }

	        testElement = null;
	    })();
	}

	/*!
	 * @copyright Copyright (c) 2017 IcoMoon.io
	 * @license   Licensed under MIT license
	 *            See https://github.com/Keyamoon/svgxuse
	 * @version   1.2.6
	 */
	/*jslint browser: true */
	/*global XDomainRequest, MutationObserver, window */
	(function () {
	    "use strict";

	    if (typeof window !== "undefined" && window.addEventListener) {
	        var cache = Object.create(null); // holds xhr objects to prevent multiple requests
	        var checkUseElems;
	        var tid; // timeout id
	        var debouncedCheck = function debouncedCheck() {
	            clearTimeout(tid);
	            tid = setTimeout(checkUseElems, 100);
	        };
	        var unobserveChanges = function unobserveChanges() {
	            return;
	        };
	        var observeChanges = function observeChanges() {
	            var observer;
	            window.addEventListener("resize", debouncedCheck, false);
	            window.addEventListener("orientationchange", debouncedCheck, false);
	            if (window.MutationObserver) {
	                observer = new MutationObserver(debouncedCheck);
	                observer.observe(document.documentElement, {
	                    childList: true,
	                    subtree: true,
	                    attributes: true
	                });
	                unobserveChanges = function unobserveChanges() {
	                    try {
	                        observer.disconnect();
	                        window.removeEventListener("resize", debouncedCheck, false);
	                        window.removeEventListener("orientationchange", debouncedCheck, false);
	                    } catch (ignore) {}
	                };
	            } else {
	                document.documentElement.addEventListener("DOMSubtreeModified", debouncedCheck, false);
	                unobserveChanges = function unobserveChanges() {
	                    document.documentElement.removeEventListener("DOMSubtreeModified", debouncedCheck, false);
	                    window.removeEventListener("resize", debouncedCheck, false);
	                    window.removeEventListener("orientationchange", debouncedCheck, false);
	                };
	            }
	        };
	        var createRequest = function createRequest(url) {
	            // In IE 9, cross origin requests can only be sent using XDomainRequest.
	            // XDomainRequest would fail if CORS headers are not set.
	            // Therefore, XDomainRequest should only be used with cross origin requests.
	            function getOrigin(loc) {
	                var a;
	                if (loc.protocol !== undefined) {
	                    a = loc;
	                } else {
	                    a = document.createElement("a");
	                    a.href = loc;
	                }
	                return a.protocol.replace(/:/g, "") + a.host;
	            }
	            var Request;
	            var origin;
	            var origin2;
	            if (window.XMLHttpRequest) {
	                Request = new XMLHttpRequest();
	                origin = getOrigin(location);
	                origin2 = getOrigin(url);
	                if (Request.withCredentials === undefined && origin2 !== "" && origin2 !== origin) {
	                    Request = XDomainRequest || undefined;
	                } else {
	                    Request = XMLHttpRequest;
	                }
	            }
	            return Request;
	        };
	        var xlinkNS = "http://www.w3.org/1999/xlink";
	        checkUseElems = function checkUseElems() {
	            var base;
	            var bcr;
	            var fallback = ""; // optional fallback URL in case no base path to SVG file was given and no symbol definition was found.
	            var hash;
	            var href;
	            var i;
	            var inProgressCount = 0;
	            var isHidden;
	            var Request;
	            var url;
	            var uses;
	            var xhr;
	            function observeIfDone() {
	                // If done with making changes, start watching for chagnes in DOM again
	                inProgressCount -= 1;
	                if (inProgressCount === 0) {
	                    // if all xhrs were resolved
	                    unobserveChanges(); // make sure to remove old handlers
	                    observeChanges(); // watch for changes to DOM
	                }
	            }
	            function attrUpdateFunc(spec) {
	                return function () {
	                    if (cache[spec.base] !== true) {
	                        spec.useEl.setAttributeNS(xlinkNS, "xlink:href", "#" + spec.hash);
	                        if (spec.useEl.hasAttribute("href")) {
	                            spec.useEl.setAttribute("href", "#" + spec.hash);
	                        }
	                    }
	                };
	            }
	            function onloadFunc(xhr) {
	                return function () {
	                    var body = document.body;
	                    var x = document.createElement("x");
	                    var svg;
	                    xhr.onload = null;
	                    x.innerHTML = xhr.responseText;
	                    svg = x.getElementsByTagName("svg")[0];
	                    if (svg) {
	                        svg.setAttribute("aria-hidden", "true");
	                        svg.style.position = "absolute";
	                        svg.style.width = 0;
	                        svg.style.height = 0;
	                        svg.style.overflow = "hidden";
	                        body.insertBefore(svg, body.firstChild);
	                    }
	                    observeIfDone();
	                };
	            }
	            function onErrorTimeout(xhr) {
	                return function () {
	                    xhr.onerror = null;
	                    xhr.ontimeout = null;
	                    observeIfDone();
	                };
	            }
	            unobserveChanges(); // stop watching for changes to DOM
	            // find all use elements
	            uses = document.getElementsByTagName("use");
	            for (i = 0; i < uses.length; i += 1) {
	                try {
	                    bcr = uses[i].getBoundingClientRect();
	                } catch (ignore) {
	                    // failed to get bounding rectangle of the use element
	                    bcr = false;
	                }
	                href = uses[i].getAttribute("href") || uses[i].getAttributeNS(xlinkNS, "href") || uses[i].getAttribute("xlink:href");
	                if (href && href.split) {
	                    url = href.split("#");
	                } else {
	                    url = ["", ""];
	                }
	                base = url[0];
	                hash = url[1];
	                isHidden = bcr && bcr.left === 0 && bcr.right === 0 && bcr.top === 0 && bcr.bottom === 0;
	                if (bcr && bcr.width === 0 && bcr.height === 0 && !isHidden) {
	                    // the use element is empty
	                    // if there is a reference to an external SVG, try to fetch it
	                    // use the optional fallback URL if there is no reference to an external SVG
	                    if (fallback && !base.length && hash && !document.getElementById(hash)) {
	                        base = fallback;
	                    }
	                    if (uses[i].hasAttribute("href")) {
	                        uses[i].setAttributeNS(xlinkNS, "xlink:href", href);
	                    }
	                    if (base.length) {
	                        // schedule updating xlink:href
	                        xhr = cache[base];
	                        if (xhr !== true) {
	                            // true signifies that prepending the SVG was not required
	                            setTimeout(attrUpdateFunc({
	                                useEl: uses[i],
	                                base: base,
	                                hash: hash
	                            }), 0);
	                        }
	                        if (xhr === undefined) {
	                            Request = createRequest(base);
	                            if (Request !== undefined) {
	                                xhr = new Request();
	                                cache[base] = xhr;
	                                xhr.onload = onloadFunc(xhr);
	                                xhr.onerror = onErrorTimeout(xhr);
	                                xhr.ontimeout = onErrorTimeout(xhr);
	                                xhr.open("GET", base);
	                                xhr.send();
	                                inProgressCount += 1;
	                            }
	                        }
	                    }
	                } else {
	                    if (!isHidden) {
	                        if (cache[base] === undefined) {
	                            // remember this URL if the use element was not empty and no request was sent
	                            cache[base] = true;
	                        } else if (cache[base].onload) {
	                            // if it turns out that prepending the SVG is not necessary,
	                            // abort the in-progress xhr.
	                            cache[base].abort();
	                            delete cache[base].onload;
	                            cache[base] = true;
	                        }
	                    } else if (base.length && cache[base]) {
	                        setTimeout(attrUpdateFunc({
	                            useEl: uses[i],
	                            base: base,
	                            hash: hash
	                        }), 0);
	                    }
	                }
	            }
	            uses = "";
	            inProgressCount += 1;
	            observeIfDone();
	        };
	        var _winLoad;
	        _winLoad = function winLoad() {
	            window.removeEventListener("load", _winLoad, false); // to prevent memory leaks
	            tid = setTimeout(checkUseElems, 0);
	        };
	        if (document.readyState !== "complete") {
	            // The load event fires when all resources have finished loading, which allows detecting whether SVG use elements are empty.
	            window.addEventListener("load", _winLoad, false);
	        } else {
	            // No need to add a listener if the document is already loaded, initialize immediately.
	            _winLoad();
	        }
	    }
	})();

/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * @fileoverview Image-editor application class
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      */


	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _invoker2 = __webpack_require__(68);

	var _invoker3 = _interopRequireDefault(_invoker2);

	var _ui = __webpack_require__(74);

	var _ui2 = _interopRequireDefault(_ui);

	var _action = __webpack_require__(102);

	var _action2 = _interopRequireDefault(_action);

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _graphics = __webpack_require__(104);

	var _graphics2 = _interopRequireDefault(_graphics);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	var _util = __webpack_require__(72);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	var events = _consts2.default.eventNames;
	var commands = _consts2.default.commandNames;
	var keyCodes = _consts2.default.keyCodes,
	    rejectMessages = _consts2.default.rejectMessages;
	var isUndefined = _tuiCodeSnippet2.default.isUndefined,
	    forEach = _tuiCodeSnippet2.default.forEach,
	    CustomEvents = _tuiCodeSnippet2.default.CustomEvents;

	/**
	 * Image editor
	 * @class
	 * @param {string|jQuery|HTMLElement} wrapper - Wrapper's element or selector
	 * @param {Object} [options] - Canvas max width & height of css
	 *  @param {number} [options.includeUI] - Use the provided UI
	 *    @param {Object} [options.includeUI.loadImage] - Basic editing image
	 *      @param {string} options.includeUI.loadImage.path - image path
	 *      @param {string} options.includeUI.loadImage.name - image name
	 *    @param {Object} [options.includeUI.theme] - Theme object
	 *    @param {Array} [options.includeUI.menu] - It can be selected when only specific menu is used. [default all]
	 *    @param {string} [options.includeUI.initMenu] - The first menu to be selected and started.
	 *    @param {string} [options.includeUI.menuBarPosition=bottom] - Menu bar position [top | bottom | left | right]
	 *  @param {number} options.cssMaxWidth - Canvas css-max-width
	 *  @param {number} options.cssMaxHeight - Canvas css-max-height
	 *  @param {Boolean} [options.usageStatistics=true] - Let us know the hostname. If you don't want to send the hostname, please set to false.
	 * @example
	 * var ImageEditor = require('tui-image-editor');
	 * var blackTheme = require('./js/theme/black-theme.js');
	 * var instance = new ImageEditor(document.querySelector('#tui-image-editor'), {
	 *   includeUI: {
	 *     loadImage: {
	 *       path: 'img/sampleImage.jpg',
	 *       name: 'SampleImage'
	 *     },
	 *     theme: blackTheme, // or whiteTheme
	 *     menu: ['shape', 'filter'],
	 *     initMenu: 'filter',
	 *     menuBarPosition: 'bottom'
	 *   },
	 *   cssMaxWidth: 700,
	 *   cssMaxHeight: 500,
	 *   selectionStyle: {
	 *     cornerSize: 20,
	 *     rotatingPointOffset: 70
	 *   }
	 * });
	 */

	var ImageEditor = function () {
	    function ImageEditor(wrapper, options) {
	        _classCallCheck(this, ImageEditor);

	        options = _tuiCodeSnippet2.default.extend({
	            includeUI: false,
	            usageStatistics: true
	        }, options);

	        this.mode = null;

	        this.activeObjectId = null;

	        /**
	         * UI instance
	         * @type {Ui}
	         */
	        if (options.includeUI) {
	            this.ui = new _ui2.default(wrapper, options.includeUI, this.getActions());
	            options = this.ui.setUiDefaultSelectionStyle(options);
	        }

	        /**
	         * Invoker
	         * @type {Invoker}
	         * @private
	         */
	        this._invoker = new _invoker3.default();

	        /**
	         * Graphics instance
	         * @type {Graphics}
	         * @private
	         */
	        this._graphics = new _graphics2.default(this.ui ? this.ui.getEditorArea() : wrapper, {
	            cssMaxWidth: options.cssMaxWidth,
	            cssMaxHeight: options.cssMaxHeight,
	            useItext: !!this.ui,
	            useDragAddIcon: !!this.ui
	        });

	        /**
	         * Event handler list
	         * @type {Object}
	         * @private
	         */
	        this._handlers = {
	            keydown: this._onKeyDown.bind(this),
	            mousedown: this._onMouseDown.bind(this),
	            objectActivated: this._onObjectActivated.bind(this),
	            objectMoved: this._onObjectMoved.bind(this),
	            objectScaled: this._onObjectScaled.bind(this),
	            createdPath: this._onCreatedPath,
	            addText: this._onAddText.bind(this),
	            addObject: this._onAddObject.bind(this),
	            addObjectAfter: this._onAddObjectAfter.bind(this),
	            textEditing: this._onTextEditing.bind(this),
	            textChanged: this._onTextChanged.bind(this),
	            iconCreateResize: this._onIconCreateResize.bind(this),
	            iconCreateEnd: this._onIconCreateEnd.bind(this),
	            selectionCleared: this._selectionCleared.bind(this),
	            selectionCreated: this._selectionCreated.bind(this)
	        };

	        this._attachInvokerEvents();
	        this._attachGraphicsEvents();
	        this._attachDomEvents();
	        this._setSelectionStyle(options.selectionStyle, {
	            applyCropSelectionStyle: options.applyCropSelectionStyle,
	            applyGroupSelectionStyle: options.applyGroupSelectionStyle
	        });

	        if (options.usageStatistics) {
	            (0, _util.sendHostName)();
	        }

	        if (this.ui) {
	            this.ui.initCanvas();
	            this.setReAction();
	        }
	    }

	    /**
	     * Image filter result
	     * @typedef {Object} FilterResult
	     * @property {string} type - filter type like 'mask', 'Grayscale' and so on
	     * @property {string} action - action type like 'add', 'remove'
	     */

	    /**
	     * Flip status
	     * @typedef {Object} FlipStatus
	     * @property {boolean} flipX - x axis
	     * @property {boolean} flipY - y axis
	     * @property {Number} angle - angle
	     */
	    /**
	     * Rotation status
	     * @typedef {Number} RotateStatus
	     * @property {Number} angle - angle
	     */

	    /**
	     * Old and new Size
	     * @typedef {Object} SizeChange
	     * @property {Number} oldWidth - old width
	     * @property {Number} oldHeight - old height
	     * @property {Number} newWidth - new width
	     * @property {Number} newHeight - new height
	     */

	    /**
	     * @typedef {string} ErrorMsg - {string} error message
	     */

	    /**
	     * @typedef {Object} ObjectProps - graphics object properties
	     * @property {number} id - object id
	     * @property {string} type - object type
	     * @property {string} text - text content
	     * @property {string} left - Left
	     * @property {string} top - Top
	     * @property {string} width - Width
	     * @property {string} height - Height
	     * @property {string} fill - Color
	     * @property {string} stroke - Stroke
	     * @property {string} strokeWidth - StrokeWidth
	     * @property {string} fontFamily - Font type for text
	     * @property {number} fontSize - Font Size
	     * @property {string} fontStyle - Type of inclination (normal / italic)
	     * @property {string} fontWeight - Type of thicker or thinner looking (normal / bold)
	     * @property {string} textAlign - Type of text align (left / center / right)
	     * @property {string} textDecoraiton - Type of line (underline / line-throgh / overline)
	     */

	    /**
	     * Set selection style by init option
	     * @param {Object} selectionStyle - Selection styles
	     * @param {Object} applyTargets - Selection apply targets
	     *   @param {boolean} applyCropSelectionStyle - whether apply with crop selection style or not
	     *   @param {boolean} applyGroupSelectionStyle - whether apply with group selection style or not
	     * @private
	     */


	    _createClass(ImageEditor, [{
	        key: '_setSelectionStyle',
	        value: function _setSelectionStyle(selectionStyle, _ref) {
	            var applyCropSelectionStyle = _ref.applyCropSelectionStyle,
	                applyGroupSelectionStyle = _ref.applyGroupSelectionStyle;

	            if (selectionStyle) {
	                this._graphics.setSelectionStyle(selectionStyle);
	            }

	            if (applyCropSelectionStyle) {
	                this._graphics.setCropSelectionStyle(selectionStyle);
	            }

	            if (applyGroupSelectionStyle) {
	                this.on('selectionCreated', function (eventTarget) {
	                    if (eventTarget.type === 'group') {
	                        eventTarget.set(selectionStyle);
	                    }
	                });
	            }
	        }

	        /**
	         * Attach invoker events
	         * @private
	         */

	    }, {
	        key: '_attachInvokerEvents',
	        value: function _attachInvokerEvents() {
	            var UNDO_STACK_CHANGED = events.UNDO_STACK_CHANGED,
	                REDO_STACK_CHANGED = events.REDO_STACK_CHANGED;

	            /**
	             * Undo stack changed event
	             * @event ImageEditor#undoStackChanged
	             * @param {Number} length - undo stack length
	             * @example
	             * imageEditor.on('undoStackChanged', function(length) {
	             *     console.log(length);
	             * });
	             */

	            this._invoker.on(UNDO_STACK_CHANGED, this.fire.bind(this, UNDO_STACK_CHANGED));
	            /**
	             * Redo stack changed event
	             * @event ImageEditor#redoStackChanged
	             * @param {Number} length - redo stack length
	             * @example
	             * imageEditor.on('redoStackChanged', function(length) {
	             *     console.log(length);
	             * });
	             */
	            this._invoker.on(REDO_STACK_CHANGED, this.fire.bind(this, REDO_STACK_CHANGED));
	        }

	        /**
	         * Attach canvas events
	         * @private
	         */

	    }, {
	        key: '_attachGraphicsEvents',
	        value: function _attachGraphicsEvents() {
	            this._graphics.on({
	                'mousedown': this._handlers.mousedown,
	                'objectMoved': this._handlers.objectMoved,
	                'objectScaled': this._handlers.objectScaled,
	                'objectActivated': this._handlers.objectActivated,
	                'addText': this._handlers.addText,
	                'addObject': this._handlers.addObject,
	                'textEditing': this._handlers.textEditing,
	                'textChanged': this._handlers.textChanged,
	                'iconCreateResize': this._handlers.iconCreateResize,
	                'iconCreateEnd': this._handlers.iconCreateEnd,
	                'selectionCleared': this._handlers.selectionCleared,
	                'selectionCreated': this._handlers.selectionCreated,
	                'addObjectAfter': this._handlers.addObjectAfter
	            });
	        }

	        /**
	         * Attach dom events
	         * @private
	         */

	    }, {
	        key: '_attachDomEvents',
	        value: function _attachDomEvents() {
              // ImageEditor supports IE 9 higher
	            // document.addEventListener('keydown', this._handlers.keydown);
	        }

	        /**
	         * Detach dom events
	         * @private
	         */

	    }, {
	        key: '_detachDomEvents',
	        value: function _detachDomEvents() {
	            // ImageEditor supports IE 9 higher
	            // document.removeEventListener('keydown', this._handlers.keydown);
	        }

	        /**
	         * Keydown event handler
	         * @param {KeyboardEvent} e - Event object
	         * @private
	         */
	        /* eslint-disable complexity */

	    }, {
	        key: '_onKeyDown',
	        value: function _onKeyDown(e) {
	            if ((e.ctrlKey || e.metaKey) && e.keyCode === keyCodes.Z) {
	                // There is no error message on shortcut when it's empty
	                this.undo()['catch'](function () {});
	            }

	            if ((e.ctrlKey || e.metaKey) && e.keyCode === keyCodes.Y) {
	                // There is no error message on shortcut when it's empty
	                this.redo()['catch'](function () {});
	            }

	            if (e.keyCode === keyCodes.BACKSPACE || e.keyCode === keyCodes.DEL) {
	                e.preventDefault();
	                this.removeActiveObject();
	            }
	        }
	        /* eslint-enable complexity */

	        /**
	         * Remove Active Object
	         */

	    }, {
	        key: 'removeActiveObject',
	        value: function removeActiveObject() {
	            var activeObject = this._graphics.getActiveObject();
	            var activeObjectGroup = this._graphics.getActiveGroupObject();

	            if (activeObjectGroup) {
	                var objects = activeObjectGroup.getObjects();
	                this.discardSelection();
	                this._removeObjectStream(objects);
	            } else if (activeObject) {
	                var activeObjectId = this._graphics.getObjectId(activeObject);
	                this.removeObject(activeObjectId);
	            }
	        }

	        /**
	         * RemoveObject Sequential processing for prevent invoke lock
	         * @param {Array.<Object>} targetObjects - target Objects for remove
	         * @returns {object} targetObjects
	         * @private
	         */

	    }, {
	        key: '_removeObjectStream',
	        value: function _removeObjectStream(targetObjects) {
	            var _this = this;

	            if (!targetObjects.length) {
	                return true;
	            }

	            var targetObject = targetObjects.pop();

	            return this.removeObject(this._graphics.getObjectId(targetObject)).then(function () {
	                return _this._removeObjectStream(targetObjects);
	            });
	        }

	        /**
	         * mouse down event handler
	         * @param {Event} event mouse down event
	         * @param {Object} originPointer origin pointer
	         *  @param {Number} originPointer.x x position
	         *  @param {Number} originPointer.y y position
	         * @private
	         */

	    }, {
	        key: '_onMouseDown',
	        value: function _onMouseDown(event, originPointer) {
	            /**
	             * The mouse down event with position x, y on canvas
	             * @event ImageEditor#mousedown
	             * @param {Object} event - browser mouse event object
	             * @param {Object} originPointer origin pointer
	             *  @param {Number} originPointer.x x position
	             *  @param {Number} originPointer.y y position
	             * @example
	             * imageEditor.on('mousedown', function(event, originPointer) {
	             *     console.log(event);
	             *     console.log(originPointer);
	             *     if (imageEditor.hasFilter('colorFilter')) {
	             *         imageEditor.applyFilter('colorFilter', {
	             *             x: parseInt(originPointer.x, 10),
	             *             y: parseInt(originPointer.y, 10)
	             *         });
	             *     }
	             * });
	             */
	            this.fire(events.MOUSE_DOWN, event, originPointer);
	        }

	        /**
	         * Add a 'addObject' command
	         * @param {Object} obj - Fabric object
	         * @private
	         */

	    }, {
	        key: '_pushAddObjectCommand',
	        value: function _pushAddObjectCommand(obj) {
	            var command = _command2.default.create(commands.ADD_OBJECT, this._graphics, obj);
	            this._invoker.pushUndoStack(command);
	        }

	        /**
	         * 'objectActivated' event handler
	         * @param {ObjectProps} props - object properties
	         * @private
	         */

	    }, {
	        key: '_onObjectActivated',
	        value: function _onObjectActivated(props) {
	            /**
	             * The event when object is selected(aka activated).
	             * @event ImageEditor#objectActivated
	             * @param {ObjectProps} objectProps - object properties
	             * @example
	             * imageEditor.on('objectActivated', function(props) {
	             *     console.log(props);
	             *     console.log(props.type);
	             *     console.log(props.id);
	             * });
	             */
	            this.fire(events.OBJECT_ACTIVATED, props);
	        }

	        /**
	         * 'objectMoved' event handler
	         * @param {ObjectProps} props - object properties
	         * @private
	         */

	    }, {
	        key: '_onObjectMoved',
	        value: function _onObjectMoved(props) {
	            /**
	             * The event when object is moved
	             * @event ImageEditor#objectMoved
	             * @param {ObjectProps} props - object properties
	             * @example
	             * imageEditor.on('objectMoved', function(props) {
	             *     console.log(props);
	             *     console.log(props.type);
	             * });
	             */
	            this.fire(events.OBJECT_MOVED, props);
	        }

	        /**
	         * 'objectScaled' event handler
	         * @param {ObjectProps} props - object properties
	         * @private
	         */

	    }, {
	        key: '_onObjectScaled',
	        value: function _onObjectScaled(props) {
	            /**
	             * The event when scale factor is changed
	             * @event ImageEditor#objectScaled
	             * @param {ObjectProps} props - object properties
	             * @example
	             * imageEditor.on('objectScaled', function(props) {
	             *     console.log(props);
	             *     console.log(props.type);
	             * });
	             */
	            this.fire(events.OBJECT_SCALED, props);
	        }

	        /**
	         * Get current drawing mode
	         * @returns {string}
	         * @example
	         * // Image editor drawing mode
	         * //
	         * //    NORMAL: 'NORMAL'
	         * //    CROPPER: 'CROPPER'
	         * //    FREE_DRAWING: 'FREE_DRAWING'
	         * //    LINE_DRAWING: 'LINE_DRAWING'
	         * //    TEXT: 'TEXT'
	         * //
	         * if (imageEditor.getDrawingMode() === 'FREE_DRAWING') {
	         *     imageEditor.stopDrawingMode();
	         * }
	         */

	    }, {
	        key: 'getDrawingMode',
	        value: function getDrawingMode() {
	            return this._graphics.getDrawingMode();
	        }

	        /**
	         * Clear all objects
	         * @returns {Promise}
	         * @example
	         * imageEditor.clearObjects();
	         */

	    }, {
	        key: 'clearObjects',
	        value: function clearObjects() {
	            return this.execute(commands.CLEAR_OBJECTS);
	        }

	        /**
	         * Deactivate all objects
	         * @example
	         * imageEditor.deactivateAll();
	         */

	    }, {
	        key: 'deactivateAll',
	        value: function deactivateAll() {
	            this._graphics.deactivateAll();
	            this._graphics.renderAll();
	        }

	        /**
	         * discard selction
	         * @example
	         * imageEditor.discardSelection();
	         */

	    }, {
	        key: 'discardSelection',
	        value: function discardSelection() {
	            this._graphics.discardSelection();
	        }

	        /**
	         * selectable status change
	         * @param {boolean} selectable - selctable status
	         * @example
	         * imageEditor.changeSelectableAll(false); // or true
	         */

	    }, {
	        key: 'changeSelectableAll',
	        value: function changeSelectableAll(selectable) {
	            this._graphics.changeSelectableAll(selectable);
	        }

	        /**
	         * Invoke command
	         * @param {String} commandName - Command name
	         * @param {...*} args - Arguments for creating command
	         * @returns {Promise}
	         * @private
	         */

	    }, {
	        key: 'execute',
	        value: function execute(commandName) {
	            var _invoker;

	            for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
	                args[_key - 1] = arguments[_key];
	            }

	            // Inject an Graphics instance as first parameter
	            var theArgs = [this._graphics].concat(args);

	            return (_invoker = this._invoker).execute.apply(_invoker, [commandName].concat(theArgs));
	        }

	        /**
	         * Undo
	         * @returns {Promise}
	         * @example
	         * imageEditor.undo();
	         */

	    }, {
	        key: 'undo',
	        value: function undo() {
	            return this._invoker.undo();
	        }

	        /**
	         * Redo
	         * @returns {Promise}
	         * @example
	         * imageEditor.redo();
	         */

	    }, {
	        key: 'redo',
	        value: function redo() {
	            return this._invoker.redo();
	        }

	        /**
	         * Load image from file
	         * @param {File} imgFile - Image file
	         * @param {string} [imageName] - imageName
	         * @returns {Promise<SizeChange, ErrorMsg>}
	         * @example
	         * imageEditor.loadImageFromFile(file).then(result => {
	         *      console.log('old : ' + result.oldWidth + ', ' + result.oldHeight);
	         *      console.log('new : ' + result.newWidth + ', ' + result.newHeight);
	         * });
	         */

	    }, {
	        key: 'loadImageFromFile',
	        value: function loadImageFromFile(imgFile, imageName) {
	            if (!imgFile) {
	                return _promise2.default.reject(rejectMessages.invalidParameters);
	            }

	            var imgUrl = URL.createObjectURL(imgFile);
	            imageName = imageName || imgFile.name;

	            return this.loadImageFromURL(imgUrl, imageName).then(function (value) {
	                URL.revokeObjectURL(imgFile);

	                return value;
	            });
	        }

	        /**
	         * Load image from url
	         * @param {string} url - File url
	         * @param {string} imageName - imageName
	         * @returns {Promise<SizeChange, ErrorMsg>}
	         * @example
	         * imageEditor.loadImageFromURL('http://url/testImage.png', 'lena').then(result => {
	         *      console.log('old : ' + result.oldWidth + ', ' + result.oldHeight);
	         *      console.log('new : ' + result.newWidth + ', ' + result.newHeight);
	         * });
	         */

	    }, {
	        key: 'loadImageFromURL',
	        value: function loadImageFromURL(url, imageName) {
	            if (!imageName || !url) {
	                return _promise2.default.reject(rejectMessages.invalidParameters);
	            }

	            return this.execute(commands.LOAD_IMAGE, imageName, url);
	        }

	        /**
	         * Add image object on canvas
	         * @param {string} imgUrl - Image url to make object
	         * @returns {Promise<ObjectProps, ErrorMsg>}
	         * @example
	         * imageEditor.addImageObject('path/fileName.jpg').then(objectProps => {
	         *     console.log(ojectProps.id);
	         * });
	         */

	    }, {
	        key: 'addImageObject',
	        value: function addImageObject(imgUrl) {
	            if (!imgUrl) {
	                return _promise2.default.reject(rejectMessages.invalidParameters);
	            }

	            return this.execute(commands.ADD_IMAGE_OBJECT, imgUrl);
	        }

	        /**
	         * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first.
	         * @param {String} mode Can be one of <I>'CROPPER', 'FREE_DRAWING', 'LINE_DRAWING', 'TEXT', 'SHAPE'</I>
	         * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING'
	         *  @param {Number} [option.width] brush width
	         *  @param {String} [option.color] brush color
	         * @returns {boolean} true if success or false
	         * @example
	         * imageEditor.startDrawingMode('FREE_DRAWING', {
	         *      width: 10,
	         *      color: 'rgba(255,0,0,0.5)'
	         * });
	         */

	    }, {
	        key: 'startDrawingMode',
	        value: function startDrawingMode(mode, option) {
	            return this._graphics.startDrawingMode(mode, option);
	        }

	        /**
	         * Stop the current drawing mode and back to the 'NORMAL' mode
	         * @example
	         * imageEditor.stopDrawingMode();
	         */

	    }, {
	        key: 'stopDrawingMode',
	        value: function stopDrawingMode() {
	            this._graphics.stopDrawingMode();
	        }

	        /**
	         * Crop this image with rect
	         * @param {Object} rect crop rect
	         *  @param {Number} rect.left left position
	         *  @param {Number} rect.top top position
	         *  @param {Number} rect.width width
	         *  @param {Number} rect.height height
	         * @returns {Promise}
	         * @example
	         * imageEditor.crop(imageEditor.getCropzoneRect());
	         */

	    }, {
	        key: 'crop',
	        value: function crop(rect) {
	            var data = this._graphics.getCroppedImageData(rect);
	            if (!data) {
	                return _promise2.default.reject(rejectMessages.invalidParameters);
	            }

	            return this.loadImageFromURL(data.url, data.imageName);
	        }

	        /**
	         * Get the cropping rect
	         * @returns {Object}  {{left: number, top: number, width: number, height: number}} rect
	         */

	    }, {
	        key: 'getCropzoneRect',
	        value: function getCropzoneRect() {
	            return this._graphics.getCropzoneRect();
	        }

	        /**
	         * Flip
	         * @returns {Promise}
	         * @param {string} type - 'flipX' or 'flipY' or 'reset'
	         * @returns {Promise<FlipStatus, ErrorMsg>}
	         * @private
	         */

	    }, {
	        key: '_flip',
	        value: function _flip(type) {
	            return this.execute(commands.FLIP_IMAGE, type);
	        }

	        /**
	         * Flip x
	         * @returns {Promise<FlipStatus, ErrorMsg>}
	         * @example
	         * imageEditor.flipX().then((status => {
	         *     console.log('flipX: ', status.flipX);
	         *     console.log('flipY: ', status.flipY);
	         *     console.log('angle: ', status.angle);
	         * }).catch(message => {
	         *     console.log('error: ', message);
	         * });
	         */

	    }, {
	        key: 'flipX',
	        value: function flipX() {
	            return this._flip('flipX');
	        }

	        /**
	         * Flip y
	         * @returns {Promise<FlipStatus, ErrorMsg>}
	         * @example
	         * imageEditor.flipY().then(status => {
	         *     console.log('flipX: ', status.flipX);
	         *     console.log('flipY: ', status.flipY);
	         *     console.log('angle: ', status.angle);
	         * }).catch(message => {
	         *     console.log('error: ', message);
	         * });
	         */

	    }, {
	        key: 'flipY',
	        value: function flipY() {
	            return this._flip('flipY');
	        }

	        /**
	         * Reset flip
	         * @returns {Promise<FlipStatus, ErrorMsg>}
	         * @example
	         * imageEditor.resetFlip().then(status => {
	         *     console.log('flipX: ', status.flipX);
	         *     console.log('flipY: ', status.flipY);
	         *     console.log('angle: ', status.angle);
	         * }).catch(message => {
	         *     console.log('error: ', message);
	         * });;
	         */

	    }, {
	        key: 'resetFlip',
	        value: function resetFlip() {
	            return this._flip('reset');
	        }

	        /**
	         * @param {string} type - 'rotate' or 'setAngle'
	         * @param {number} angle - angle value (degree)
	         * @returns {Promise<RotateStatus, ErrorMsg>}
	         * @private
	         */

	    }, {
	        key: '_rotate',
	        value: function _rotate(type, angle) {
	            return this.execute(commands.ROTATE_IMAGE, type, angle);
	        }

	        /**
	         * Rotate image
	         * @returns {Promise}
	         * @param {number} angle - Additional angle to rotate image
	         * @returns {Promise<RotateStatus, ErrorMsg>}
	         * @example
	         * imageEditor.setAngle(10); // angle = 10
	         * imageEditor.rotate(10); // angle = 20
	         * imageEidtor.setAngle(5); // angle = 5
	         * imageEidtor.rotate(-95); // angle = -90
	         * imageEditor.rotate(10).then(status => {
	         *     console.log('angle: ', status.angle);
	         * })).catch(message => {
	         *     console.log('error: ', message);
	         * });
	         */

	    }, {
	        key: 'rotate',
	        value: function rotate(angle) {
	            return this._rotate('rotate', angle);
	        }

	        /**
	         * Set angle
	         * @param {number} angle - Angle of image
	         * @returns {Promise<RotateStatus, ErrorMsg>}
	         * @example
	         * imageEditor.setAngle(10); // angle = 10
	         * imageEditor.rotate(10); // angle = 20
	         * imageEidtor.setAngle(5); // angle = 5
	         * imageEidtor.rotate(50); // angle = 55
	         * imageEidtor.setAngle(-40); // angle = -40
	         * imageEditor.setAngle(10).then(status => {
	         *     console.log('angle: ', status.angle);
	         * })).catch(message => {
	         *     console.log('error: ', message);
	         * });
	         */

	    }, {
	        key: 'setAngle',
	        value: function setAngle(angle) {
	            return this._rotate('setAngle', angle);
	        }

	        /**
	         * Set drawing brush
	         * @param {Object} option brush option
	         *  @param {Number} option.width width
	         *  @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)'
	         * @example
	         * imageEditor.startDrawingMode('FREE_DRAWING');
	         * imageEditor.setBrush({
	         *     width: 12,
	         *     color: 'rgba(0, 0, 0, 0.5)'
	         * });
	         * imageEditor.setBrush({
	         *     width: 8,
	         *     color: 'FFFFFF'
	         * });
	         */

	    }, {
	        key: 'setBrush',
	        value: function setBrush(option) {
	            this._graphics.setBrush(option);
	        }

	        /**
	         * Set states of current drawing shape
	         * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
	         * @param {Object} [options] - Shape options
	         *      @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
	         *      @param {string} [options.stoke] - Shape outline color
	         *      @param {number} [options.strokeWidth] - Shape outline width
	         *      @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
	         *      @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
	         *      @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
	         *      @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
	         *      @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
	         * @example
	         * imageEditor.setDrawingShape('rect', {
	         *     fill: 'red',
	         *     width: 100,
	         *     height: 200
	         * });
	         * @example
	         * imageEditor.setDrawingShape('circle', {
	         *     fill: 'transparent',
	         *     stroke: 'blue',
	         *     strokeWidth: 3,
	         *     rx: 10,
	         *     ry: 100
	         * });
	         * @example
	         * imageEditor.setDrawingShape('triangle', { // When resizing, the shape keep the 1:1 ratio
	         *     width: 1,
	         *     height: 1,
	         *     isRegular: true
	         * });
	         * @example
	         * imageEditor.setDrawingShape('circle', { // When resizing, the shape keep the 1:1 ratio
	         *     rx: 10,
	         *     ry: 10,
	         *     isRegular: true
	         * });
	         */

	    }, {
	        key: 'setDrawingShape',
	        value: function setDrawingShape(type, options) {
	            this._graphics.setDrawingShape(type, options);
	        }

	        /**
	         * Add shape
	         * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
	         * @param {Object} options - Shape options
	         *      @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
	         *      @param {string} [options.stroke] - Shape outline color
	         *      @param {number} [options.strokeWidth] - Shape outline width
	         *      @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
	         *      @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
	         *      @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
	         *      @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
	         *      @param {number} [options.left] - Shape x position
	         *      @param {number} [options.top] - Shape y position
	         *      @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
	         * @returns {Promise<ObjectProps, ErrorMsg>}
	         * @example
	         * imageEditor.addShape('rect', {
	         *     fill: 'red',
	         *     stroke: 'blue',
	         *     strokeWidth: 3,
	         *     width: 100,
	         *     height: 200,
	         *     left: 10,
	         *     top: 10,
	         *     isRegular: true
	         * });
	         * @example
	         * imageEditor.addShape('circle', {
	         *     fill: 'red',
	         *     stroke: 'blue',
	         *     strokeWidth: 3,
	         *     rx: 10,
	         *     ry: 100,
	         *     isRegular: false
	         * }).then(objectProps => {
	         *     console.log(objectProps.id);
	         * });
	         */

	    }, {
	        key: 'addShape',
	        value: function addShape(type, options) {
	            options = options || {};

	            this._setPositions(options);

	            return this.execute(commands.ADD_SHAPE, type, options);
	        }

	        /**
	         * Change shape
	         * @param {number} id - object id
	         * @param {Object} options - Shape options
	         *      @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
	         *      @param {string} [options.stroke] - Shape outline color
	         *      @param {number} [options.strokeWidth] - Shape outline width
	         *      @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
	         *      @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
	         *      @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
	         *      @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
	         *      @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
	         * @returns {Promise}
	         * @example
	         * // call after selecting shape object on canvas
	         * imageEditor.changeShape(id, { // change rectagle or triangle
	         *     fill: 'red',
	         *     stroke: 'blue',
	         *     strokeWidth: 3,
	         *     width: 100,
	         *     height: 200
	         * });
	         * @example
	         * // call after selecting shape object on canvas
	         * imageEditor.changeShape(id, { // change circle
	         *     fill: 'red',
	         *     stroke: 'blue',
	         *     strokeWidth: 3,
	         *     rx: 10,
	         *     ry: 100
	         * });
	         */

	    }, {
	        key: 'changeShape',
	        value: function changeShape(id, options) {
	            return this.execute(commands.CHANGE_SHAPE, id, options);
	        }

	        /**
	         * Add text on image
	         * @param {string} text - Initial input text
	         * @param {Object} [options] Options for generating text
	         *     @param {Object} [options.styles] Initial styles
	         *         @param {string} [options.styles.fill] Color
	         *         @param {string} [options.styles.fontFamily] Font type for text
	         *         @param {number} [options.styles.fontSize] Size
	         *         @param {string} [options.styles.fontStyle] Type of inclination (normal / italic)
	         *         @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold)
	         *         @param {string} [options.styles.textAlign] Type of text align (left / center / right)
	         *         @param {string} [options.styles.textDecoraiton] Type of line (underline / line-throgh / overline)
	         *     @param {{x: number, y: number}} [options.position] - Initial position
	         * @returns {Promise}
	         * @example
	         * imageEditor.addText('init text');
	         * @example
	         * imageEditor.addText('init text', {
	         *     styles: {
	         *         fill: '#000',
	         *         fontSize: '20',
	         *         fontWeight: 'bold'
	         *     },
	         *     position: {
	         *         x: 10,
	         *         y: 10
	         *     }
	         * }).then(objectProps => {
	         *     console.log(objectProps.id);
	         * });
	         */

	    }, {
	        key: 'addText',
	        value: function addText(text, options) {
	            text = text || '';
	            options = options || {};

	            return this.execute(commands.ADD_TEXT, text, options);
	        }

	        /**
	         * Change contents of selected text object on image
	         * @param {number} id - object id
	         * @param {string} text - Changing text
	         * @returns {Promise<ObjectProps, ErrorMsg>}
	         * @example
	         * imageEditor.changeText(id, 'change text');
	         */

	    }, {
	        key: 'changeText',
	        value: function changeText(id, text) {
	            text = text || '';

	            return this.execute(commands.CHANGE_TEXT, id, text);
	        }

	        /**
	         * Set style
	         * @param {number} id - object id
	         * @param {Object} styleObj - text styles
	         *     @param {string} [styleObj.fill] Color
	         *     @param {string} [styleObj.fontFamily] Font type for text
	         *     @param {number} [styleObj.fontSize] Size
	         *     @param {string} [styleObj.fontStyle] Type of inclination (normal / italic)
	         *     @param {string} [styleObj.fontWeight] Type of thicker or thinner looking (normal / bold)
	         *     @param {string} [styleObj.textAlign] Type of text align (left / center / right)
	         *     @param {string} [styleObj.textDecoraiton] Type of line (underline / line-throgh / overline)
	         * @returns {Promise}
	         * @example
	         * imageEditor.changeTextStyle(id, {
	         *     fontStyle: 'italic'
	         * });
	         */

	    }, {
	        key: 'changeTextStyle',
	        value: function changeTextStyle(id, styleObj) {
	            return this.execute(commands.CHANGE_TEXT_STYLE, id, styleObj);
	        }

	        /**
	         * change text mode
	         * @param {string} type - change type
	         * @private
	         */

	    }, {
	        key: '_changeActivateMode',
	        value: function _changeActivateMode(type) {
	            if (type !== 'ICON' && this.getDrawingMode() !== type) {
	                this.startDrawingMode(type);
	            }
	        }

	        /**
	         * 'textChanged' event handler
	         * @param {Object} objectProps changed object properties
	         * @private
	         */

	    }, {
	        key: '_onTextChanged',
	        value: function _onTextChanged(objectProps) {
	            this.changeText(objectProps.id, objectProps.text);
	        }

	        /**
	         * 'iconCreateResize' event handler
	         * @param {Object} originPointer origin pointer
	         *  @param {Number} originPointer.x x position
	         *  @param {Number} originPointer.y y position
	         * @private
	         */

	    }, {
	        key: '_onIconCreateResize',
	        value: function _onIconCreateResize(originPointer) {
	            this.fire(events.ICON_CREATE_RESIZE, originPointer);
	        }

	        /**
	         * 'iconCreateEnd' event handler
	         * @param {Object} originPointer origin pointer
	         *  @param {Number} originPointer.x x position
	         *  @param {Number} originPointer.y y position
	         * @private
	         */

	    }, {
	        key: '_onIconCreateEnd',
	        value: function _onIconCreateEnd(originPointer) {
	            this.fire(events.ICON_CREATE_END, originPointer);
	        }

	        /**
	         * 'textEditing' event handler
	         * @private
	         */

	    }, {
	        key: '_onTextEditing',
	        value: function _onTextEditing() {
	            /**
	             * The event which starts to edit text object
	             * @event ImageEditor#textEditing
	             * @example
	             * imageEditor.on('textEditing', function() {
	             *     console.log('text editing');
	             * });
	             */
	            this.fire(events.TEXT_EDITING);
	        }

	        /**
	         * Mousedown event handler in case of 'TEXT' drawing mode
	         * @param {fabric.Event} event - Current mousedown event object
	         * @private
	         */

	    }, {
	        key: '_onAddText',
	        value: function _onAddText(event) {
	            /**
	             * The event when 'TEXT' drawing mode is enabled and click non-object area.
	             * @event ImageEditor#addText
	             * @param {Object} pos
	             *  @param {Object} pos.originPosition - Current position on origin canvas
	             *      @param {Number} pos.originPosition.x - x
	             *      @param {Number} pos.originPosition.y - y
	             *  @param {Object} pos.clientPosition - Current position on client area
	             *      @param {Number} pos.clientPosition.x - x
	             *      @param {Number} pos.clientPosition.y - y
	             * @example
	             * imageEditor.on('addText', function(pos) {
	             *     imageEditor.addText('Double Click', {
	             *         position: pos.originPosition
	             *     });
	             *     console.log('text position on canvas: ' + pos.originPosition);
	             *     console.log('text position on brwoser: ' + pos.clientPosition);
	             * });
	             */
	            this.fire(events.ADD_TEXT, {
	                originPosition: event.originPosition,
	                clientPosition: event.clientPosition
	            });
	        }

	        /**
	         * 'addObject' event handler
	         * @param {Object} objectProps added object properties
	         * @private
	         */

	    }, {
	        key: '_onAddObject',
	        value: function _onAddObject(objectProps) {
	            var obj = this._graphics.getObject(objectProps.id);
	            this._pushAddObjectCommand(obj);
	        }

	        /**
	         * 'addObjectAfter' event handler
	         * @param {Object} objectProps added object properties
	         * @private
	         */

	    }, {
	        key: '_onAddObjectAfter',
	        value: function _onAddObjectAfter(objectProps) {
	            this.fire(events.ADD_OBJECT_AFTER, objectProps);
	        }

	        /**
	         * 'selectionCleared' event handler
	         * @private
	         */

	    }, {
	        key: '_selectionCleared',
	        value: function _selectionCleared() {
	            this.fire(events.SELECTION_CLEARED);
	        }

	        /**
	         * 'selectionCreated' event handler
	         * @param {Object} eventTarget - Fabric object
	         * @private
	         */

	    }, {
	        key: '_selectionCreated',
	        value: function _selectionCreated(eventTarget) {
	            this.fire(events.SELECTION_CREATED, eventTarget);
	        }

	        /**
	         * Register custom icons
	         * @param {{iconType: string, pathValue: string}} infos - Infos to register icons
	         * @example
	         * imageEditor.registerIcons({
	         *     customIcon: 'M 0 0 L 20 20 L 10 10 Z',
	         *     customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z'
	         * });
	         */

	    }, {
	        key: 'registerIcons',
	        value: function registerIcons(infos) {
	            this._graphics.registerPaths(infos);
	        }

	        /**
	         * Change canvas cursor type
	         * @param {string} cursorType - cursor type
	         * @example
	         * imageEditor.changeCursor('crosshair');
	         */

	    }, {
	        key: 'changeCursor',
	        value: function changeCursor(cursorType) {
	            this._graphics.changeCursor(cursorType);
	        }

	        /**
	         * Add icon on canvas
	         * @param {string} type - Icon type ('arrow', 'cancel', custom icon name)
	         * @param {Object} options - Icon options
	         *      @param {string} [options.fill] - Icon foreground color
	         *      @param {string} [options.left] - Icon x position
	         *      @param {string} [options.top] - Icon y position
	         * @returns {Promise<ObjectProps, ErrorMsg>}
	         * @example
	         * imageEditor.addIcon('arrow'); // The position is center on canvas
	         * @example
	         * imageEditor.addIcon('arrow', {
	         *     left: 100,
	         *     top: 100
	         * }).then(objectProps => {
	         *     console.log(objectProps.id);
	         * });
	         */

	    }, {
	        key: 'addIcon',
	        value: function addIcon(type, options) {
	            options = options || {};

	            this._setPositions(options);

	            return this.execute(commands.ADD_ICON, type, options);
	        }

	        /**
	         * Change icon color
	         * @param {number} id - object id
	         * @param {string} color - Color for icon
	         * @returns {Promise}
	         * @example
	         * imageEditor.changeIconColor(id, '#000000');
	         */

	    }, {
	        key: 'changeIconColor',
	        value: function changeIconColor(id, color) {
	            return this.execute(commands.CHANGE_ICON_COLOR, id, color);
	        }

	        /**
	         * Remove an object or group by id
	         * @param {number} id - object id
	         * @returns {Promise}
	         * @example
	         * imageEditor.removeObject(id);
	         */

	    }, {
	        key: 'removeObject',
	        value: function removeObject(id) {
	            return this.execute(commands.REMOVE_OBJECT, id);
	        }

	        /**
	         * Whether it has the filter or not
	         * @param {string} type - Filter type
	         * @returns {boolean} true if it has the filter
	         */

	    }, {
	        key: 'hasFilter',
	        value: function hasFilter(type) {
	            return this._graphics.hasFilter(type);
	        }

	        /**
	         * Remove filter on canvas image
	         * @param {string} type - Filter type
	         * @returns {Promise<FilterResult, ErrorMsg>}
	         * @example
	         * imageEditor.removeFilter('Grayscale').then(obj => {
	         *     console.log('filterType: ', obj.type);
	         *     console.log('actType: ', obj.action);
	         * }).catch(message => {
	         *     console.log('error: ', message);
	         * });
	         */

	    }, {
	        key: 'removeFilter',
	        value: function removeFilter(type) {
	            return this.execute(commands.REMOVE_FILTER, type);
	        }

	        /**
	         * Apply filter on canvas image
	         * @param {string} type - Filter type
	         * @param {Object} options - Options to apply filter
	         *  @param {number} options.maskObjId - masking image object id
	         * @returns {Promise<FilterResult, ErrorMsg>}
	         * @example
	         * imageEditor.applyFilter('Grayscale');
	         * @example
	         * imageEditor.applyFilter('mask', {maskObjId: id}).then(obj => {
	         *     console.log('filterType: ', obj.type);
	         *     console.log('actType: ', obj.action);
	         * }).catch(message => {
	         *     console.log('error: ', message);
	         * });;
	         */

	    }, {
	        key: 'applyFilter',
	        value: function applyFilter(type, options) {
	            return this.execute(commands.APPLY_FILTER, type, options);
	        }

	        /**
	         * Get data url
	         * @param {string} type - A DOMString indicating the image format. The default type is image/png.
	         * @returns {string} A DOMString containing the requested data URI
	         * @example
	         * imgEl.src = imageEditor.toDataURL();
	         *
	         * imageEditor.loadImageFromURL(imageEditor.toDataURL(), 'FilterImage').then(() => {
	         *      imageEditor.addImageObject(imgUrl);
	         * });
	         */

	    }, {
	        key: 'toDataURL',
	        value: function toDataURL(type) {
	            return this._graphics.toDataURL(type);
	        }

	        /**
	         * Get image name
	         * @returns {string} image name
	         * @example
	         * console.log(imageEditor.getImageName());
	         */

	    }, {
	        key: 'getImageName',
	        value: function getImageName() {
	            return this._graphics.getImageName();
	        }

	        /**
	         * Clear undoStack
	         * @example
	         * imageEditor.clearUndoStack();
	         */

	    }, {
	        key: 'clearUndoStack',
	        value: function clearUndoStack() {
	            this._invoker.clearUndoStack();
	        }

	        /**
	         * Clear redoStack
	         * @example
	         * imageEditor.clearRedoStack();
	         */

	    }, {
	        key: 'clearRedoStack',
	        value: function clearRedoStack() {
	            this._invoker.clearRedoStack();
	        }

	        /**
	         * Whehter the undo stack is empty or not
	         * @returns {boolean}
	         * imageEditor.isEmptyUndoStack();
	         */

	    }, {
	        key: 'isEmptyUndoStack',
	        value: function isEmptyUndoStack() {
	            return this._invoker.isEmptyUndoStack();
	        }

	        /**
	         * Whehter the redo stack is empty or not
	         * @returns {boolean}
	         * imageEditor.isEmptyRedoStack();
	         */

	    }, {
	        key: 'isEmptyRedoStack',
	        value: function isEmptyRedoStack() {
	            return this._invoker.isEmptyRedoStack();
	        }

	        /**
	         * Resize canvas dimension
	         * @param {{width: number, height: number}} dimension - Max width & height
	         * @returns {Promise}
	         */

	    }, {
	        key: 'resizeCanvasDimension',
	        value: function resizeCanvasDimension(dimension) {
	            if (!dimension) {
	                return _promise2.default.reject(rejectMessages.invalidParameters);
	            }

	            return this.execute(commands.RESIZE_CANVAS_DIMENSION, dimension);
	        }

	        /**
	         * Destroy
	         */

	    }, {
	        key: 'destroy',
	        value: function destroy() {
	            var _this2 = this;

	            this.stopDrawingMode();
	            this._detachDomEvents();
	            this._graphics.destroy();
	            this._graphics = null;

	            forEach(this, function (value, key) {
	                _this2[key] = null;
	            }, this);
	        }

	        /**
	         * Set position
	         * @param {Object} options - Position options (left or top)
	         * @private
	         */

	    }, {
	        key: '_setPositions',
	        value: function _setPositions(options) {
	            var centerPosition = this._graphics.getCenter();

	            if (isUndefined(options.left)) {
	                options.left = centerPosition.left;
	            }

	            if (isUndefined(options.top)) {
	                options.top = centerPosition.top;
	            }
	        }

	        /**
	         * Set properties of active object
	         * @param {number} id - object id
	         * @param {Object} keyValue - key & value
	         * @returns {Promise}
	         * @example
	         * imageEditor.setObjectProperties(id, {
	         *     left:100,
	         *     top:100,
	         *     width: 200,
	         *     height: 200,
	         *     opacity: 0.5
	         * });
	         */

	    }, {
	        key: 'setObjectProperties',
	        value: function setObjectProperties(id, keyValue) {
	            return this.execute(commands.SET_OBJECT_PROPERTIES, id, keyValue);
	        }

	        /**
	         * Set properties of active object, Do not leave an invoke history.
	         * @param {number} id - object id
	         * @param {Object} keyValue - key & value
	         * @example
	         * imageEditor.setObjectPropertiesQuietly(id, {
	         *     left:100,
	         *     top:100,
	         *     width: 200,
	         *     height: 200,
	         *     opacity: 0.5
	         * });
	         */

	    }, {
	        key: 'setObjectPropertiesQuietly',
	        value: function setObjectPropertiesQuietly(id, keyValue) {
	            this._graphics.setObjectProperties(id, keyValue);
	        }

	        /**
	         * Get properties of active object corresponding key
	         * @param {number} id - object id
	         * @param {Array<string>|ObjectProps|string} keys - property's key
	         * @returns {ObjectProps} properties if id is valid or null
	         * @example
	         * var props = imageEditor.getObjectProperties(id, 'left');
	         * console.log(props);
	         * @example
	         * var props = imageEditor.getObjectProperties(id, ['left', 'top', 'width', 'height']);
	         * console.log(props);
	         * @example
	         * var props = imageEditor.getObjectProperties(id, {
	         *     left: null,
	         *     top: null,
	         *     width: null,
	         *     height: null,
	         *     opacity: null
	         * });
	         * console.log(props);
	         */

	    }, {
	        key: 'getObjectProperties',
	        value: function getObjectProperties(id, keys) {
	            var object = this._graphics.getObject(id);
	            if (!object) {
	                return null;
	            }

	            return this._graphics.getObjectProperties(id, keys);
	        }

	        /**
	         * Get the canvas size
	         * @returns {Object} {{width: number, height: number}} canvas size
	         * @example
	         * var canvasSize = imageEditor.getCanvasSize();
	         * console.log(canvasSize.width);
	         * console.height(canvasSize.height);
	         */

	    }, {
	        key: 'getCanvasSize',
	        value: function getCanvasSize() {
	            return this._graphics.getCanvasSize();
	        }

	        /**
	         * Get object position by originX, originY
	         * @param {number} id - object id
	         * @param {string} originX - can be 'left', 'center', 'right'
	         * @param {string} originY - can be 'top', 'center', 'bottom'
	         * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null
	         * @example
	         * var position = imageEditor.getObjectPosition(id, 'left', 'top');
	         * console.log(position);
	         */

	    }, {
	        key: 'getObjectPosition',
	        value: function getObjectPosition(id, originX, originY) {
	            return this._graphics.getObjectPosition(id, originX, originY);
	        }

	        /**
	         * Set object position  by originX, originY
	         * @param {number} id - object id
	         * @param {Object} posInfo - position object
	         *  @param {number} posInfo.x - x position
	         *  @param {number} posInfo.y - y position
	         *  @param {string} posInfo.originX - can be 'left', 'center', 'right'
	         *  @param {string} posInfo.originY - can be 'top', 'center', 'bottom'
	         * @returns {Promise}
	         * @example
	         * // align the object to 'left', 'top'
	         * imageEditor.setObjectPosition(id, {
	         *     x: 0,
	         *     y: 0,
	         *     originX: 'left',
	         *     originY: 'top'
	         * });
	         * @example
	         * // align the object to 'right', 'top'
	         * var canvasSize = imageEditor.getCanvasSize();
	         * imageEditor.setObjectPosition(id, {
	         *     x: canvasSize.width,
	         *     y: 0,
	         *     originX: 'right',
	         *     originY: 'top'
	         * });
	         * @example
	         * // align the object to 'left', 'bottom'
	         * var canvasSize = imageEditor.getCanvasSize();
	         * imageEditor.setObjectPosition(id, {
	         *     x: 0,
	         *     y: canvasSize.height,
	         *     originX: 'left',
	         *     originY: 'bottom'
	         * });
	         * @example
	         * // align the object to 'right', 'bottom'
	         * var canvasSize = imageEditor.getCanvasSize();
	         * imageEditor.setObjectPosition(id, {
	         *     x: canvasSize.width,
	         *     y: canvasSize.height,
	         *     originX: 'right',
	         *     originY: 'bottom'
	         * });
	         */

	    }, {
	        key: 'setObjectPosition',
	        value: function setObjectPosition(id, posInfo) {
	            return this.execute(commands.SET_OBJECT_POSITION, id, posInfo);
	        }
	    }]);

	    return ImageEditor;
	}();

	_action2.default.mixin(ImageEditor);
	CustomEvents.mixin(ImageEditor);

	module.exports = ImageEditor;

/***/ }),
/* 3 */
/***/ (function(module, exports) {

	module.exports = __WEBPACK_EXTERNAL_MODULE_3__;

/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {

	__webpack_require__(5);
	__webpack_require__(6);
	__webpack_require__(50);
	__webpack_require__(54);
	module.exports = __webpack_require__(14).Promise;

/***/ }),
/* 5 */
/***/ (function(module, exports) {



/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';
	var $at  = __webpack_require__(7)(true);

	// 21.1.3.27 String.prototype[@@iterator]()
	__webpack_require__(10)(String, 'String', function(iterated){
	  this._t = String(iterated); // target
	  this._i = 0;                // next index
	// 21.1.5.2.1 %StringIteratorPrototype%.next()
	}, function(){
	  var O     = this._t
	    , index = this._i
	    , point;
	  if(index >= O.length)return {value: undefined, done: true};
	  point = $at(O, index);
	  this._i += point.length;
	  return {value: point, done: false};
	});

/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {

	var toInteger = __webpack_require__(8)
	  , defined   = __webpack_require__(9);
	// true  -> String#at
	// false -> String#codePointAt
	module.exports = function(TO_STRING){
	  return function(that, pos){
	    var s = String(defined(that))
	      , i = toInteger(pos)
	      , l = s.length
	      , a, b;
	    if(i < 0 || i >= l)return TO_STRING ? '' : undefined;
	    a = s.charCodeAt(i);
	    return a < 0xd800 || a > 0xdbff || i + 1 === l || (b = s.charCodeAt(i + 1)) < 0xdc00 || b > 0xdfff
	      ? TO_STRING ? s.charAt(i) : a
	      : TO_STRING ? s.slice(i, i + 2) : (a - 0xd800 << 10) + (b - 0xdc00) + 0x10000;
	  };
	};

/***/ }),
/* 8 */
/***/ (function(module, exports) {

	// 7.1.4 ToInteger
	var ceil  = Math.ceil
	  , floor = Math.floor;
	module.exports = function(it){
	  return isNaN(it = +it) ? 0 : (it > 0 ? floor : ceil)(it);
	};

/***/ }),
/* 9 */
/***/ (function(module, exports) {

	// 7.2.1 RequireObjectCoercible(argument)
	module.exports = function(it){
	  if(it == undefined)throw TypeError("Can't call method on  " + it);
	  return it;
	};

/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';
	var LIBRARY        = __webpack_require__(11)
	  , $export        = __webpack_require__(12)
	  , redefine       = __webpack_require__(27)
	  , hide           = __webpack_require__(17)
	  , has            = __webpack_require__(28)
	  , Iterators      = __webpack_require__(29)
	  , $iterCreate    = __webpack_require__(30)
	  , setToStringTag = __webpack_require__(46)
	  , getPrototypeOf = __webpack_require__(48)
	  , ITERATOR       = __webpack_require__(47)('iterator')
	  , BUGGY          = !([].keys && 'next' in [].keys()) // Safari has buggy iterators w/o `next`
	  , FF_ITERATOR    = '@@iterator'
	  , KEYS           = 'keys'
	  , VALUES         = 'values';

	var returnThis = function(){ return this; };

	module.exports = function(Base, NAME, Constructor, next, DEFAULT, IS_SET, FORCED){
	  $iterCreate(Constructor, NAME, next);
	  var getMethod = function(kind){
	    if(!BUGGY && kind in proto)return proto[kind];
	    switch(kind){
	      case KEYS: return function keys(){ return new Constructor(this, kind); };
	      case VALUES: return function values(){ return new Constructor(this, kind); };
	    } return function entries(){ return new Constructor(this, kind); };
	  };
	  var TAG        = NAME + ' Iterator'
	    , DEF_VALUES = DEFAULT == VALUES
	    , VALUES_BUG = false
	    , proto      = Base.prototype
	    , $native    = proto[ITERATOR] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT]
	    , $default   = $native || getMethod(DEFAULT)
	    , $entries   = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined
	    , $anyNative = NAME == 'Array' ? proto.entries || $native : $native
	    , methods, key, IteratorPrototype;
	  // Fix native
	  if($anyNative){
	    IteratorPrototype = getPrototypeOf($anyNative.call(new Base));
	    if(IteratorPrototype !== Object.prototype){
	      // Set @@toStringTag to native iterators
	      setToStringTag(IteratorPrototype, TAG, true);
	      // fix for some old engines
	      if(!LIBRARY && !has(IteratorPrototype, ITERATOR))hide(IteratorPrototype, ITERATOR, returnThis);
	    }
	  }
	  // fix Array#{values, @@iterator}.name in V8 / FF
	  if(DEF_VALUES && $native && $native.name !== VALUES){
	    VALUES_BUG = true;
	    $default = function values(){ return $native.call(this); };
	  }
	  // Define iterator
	  if((!LIBRARY || FORCED) && (BUGGY || VALUES_BUG || !proto[ITERATOR])){
	    hide(proto, ITERATOR, $default);
	  }
	  // Plug for library
	  Iterators[NAME] = $default;
	  Iterators[TAG]  = returnThis;
	  if(DEFAULT){
	    methods = {
	      values:  DEF_VALUES ? $default : getMethod(VALUES),
	      keys:    IS_SET     ? $default : getMethod(KEYS),
	      entries: $entries
	    };
	    if(FORCED)for(key in methods){
	      if(!(key in proto))redefine(proto, key, methods[key]);
	    } else $export($export.P + $export.F * (BUGGY || VALUES_BUG), NAME, methods);
	  }
	  return methods;
	};

/***/ }),
/* 11 */
/***/ (function(module, exports) {

	module.exports = true;

/***/ }),
/* 12 */
/***/ (function(module, exports, __webpack_require__) {

	var global    = __webpack_require__(13)
	  , core      = __webpack_require__(14)
	  , ctx       = __webpack_require__(15)
	  , hide      = __webpack_require__(17)
	  , PROTOTYPE = 'prototype';

	var $export = function(type, name, source){
	  var IS_FORCED = type & $export.F
	    , IS_GLOBAL = type & $export.G
	    , IS_STATIC = type & $export.S
	    , IS_PROTO  = type & $export.P
	    , IS_BIND   = type & $export.B
	    , IS_WRAP   = type & $export.W
	    , exports   = IS_GLOBAL ? core : core[name] || (core[name] = {})
	    , expProto  = exports[PROTOTYPE]
	    , target    = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE]
	    , key, own, out;
	  if(IS_GLOBAL)source = name;
	  for(key in source){
	    // contains in native
	    own = !IS_FORCED && target && target[key] !== undefined;
	    if(own && key in exports)continue;
	    // export native or passed
	    out = own ? target[key] : source[key];
	    // prevent global pollution for namespaces
	    exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key]
	    // bind timers to global for call from export context
	    : IS_BIND && own ? ctx(out, global)
	    // wrap global constructors for prevent change them in library
	    : IS_WRAP && target[key] == out ? (function(C){
	      var F = function(a, b, c){
	        if(this instanceof C){
	          switch(arguments.length){
	            case 0: return new C;
	            case 1: return new C(a);
	            case 2: return new C(a, b);
	          } return new C(a, b, c);
	        } return C.apply(this, arguments);
	      };
	      F[PROTOTYPE] = C[PROTOTYPE];
	      return F;
	    // make static versions for prototype methods
	    })(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out;
	    // export proto methods to core.%CONSTRUCTOR%.methods.%NAME%
	    if(IS_PROTO){
	      (exports.virtual || (exports.virtual = {}))[key] = out;
	      // export proto methods to core.%CONSTRUCTOR%.prototype.%NAME%
	      if(type & $export.R && expProto && !expProto[key])hide(expProto, key, out);
	    }
	  }
	};
	// type bitmap
	$export.F = 1;   // forced
	$export.G = 2;   // global
	$export.S = 4;   // static
	$export.P = 8;   // proto
	$export.B = 16;  // bind
	$export.W = 32;  // wrap
	$export.U = 64;  // safe
	$export.R = 128; // real proto method for `library`
	module.exports = $export;

/***/ }),
/* 13 */
/***/ (function(module, exports) {

	// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
	var global = module.exports = typeof window != 'undefined' && window.Math == Math
	  ? window : typeof self != 'undefined' && self.Math == Math ? self : Function('return this')();
	if(typeof __g == 'number')__g = global; // eslint-disable-line no-undef

/***/ }),
/* 14 */
/***/ (function(module, exports) {

	var core = module.exports = {version: '2.4.0'};
	if(typeof __e == 'number')__e = core; // eslint-disable-line no-undef

/***/ }),
/* 15 */
/***/ (function(module, exports, __webpack_require__) {

	// optional / simple context binding
	var aFunction = __webpack_require__(16);
	module.exports = function(fn, that, length){
	  aFunction(fn);
	  if(that === undefined)return fn;
	  switch(length){
	    case 1: return function(a){
	      return fn.call(that, a);
	    };
	    case 2: return function(a, b){
	      return fn.call(that, a, b);
	    };
	    case 3: return function(a, b, c){
	      return fn.call(that, a, b, c);
	    };
	  }
	  return function(/* ...args */){
	    return fn.apply(that, arguments);
	  };
	};

/***/ }),
/* 16 */
/***/ (function(module, exports) {

	module.exports = function(it){
	  if(typeof it != 'function')throw TypeError(it + ' is not a function!');
	  return it;
	};

/***/ }),
/* 17 */
/***/ (function(module, exports, __webpack_require__) {

	var dP         = __webpack_require__(18)
	  , createDesc = __webpack_require__(26);
	module.exports = __webpack_require__(22) ? function(object, key, value){
	  return dP.f(object, key, createDesc(1, value));
	} : function(object, key, value){
	  object[key] = value;
	  return object;
	};

/***/ }),
/* 18 */
/***/ (function(module, exports, __webpack_require__) {

	var anObject       = __webpack_require__(19)
	  , IE8_DOM_DEFINE = __webpack_require__(21)
	  , toPrimitive    = __webpack_require__(25)
	  , dP             = Object.defineProperty;

	exports.f = __webpack_require__(22) ? Object.defineProperty : function defineProperty(O, P, Attributes){
	  anObject(O);
	  P = toPrimitive(P, true);
	  anObject(Attributes);
	  if(IE8_DOM_DEFINE)try {
	    return dP(O, P, Attributes);
	  } catch(e){ /* empty */ }
	  if('get' in Attributes || 'set' in Attributes)throw TypeError('Accessors not supported!');
	  if('value' in Attributes)O[P] = Attributes.value;
	  return O;
	};

/***/ }),
/* 19 */
/***/ (function(module, exports, __webpack_require__) {

	var isObject = __webpack_require__(20);
	module.exports = function(it){
	  if(!isObject(it))throw TypeError(it + ' is not an object!');
	  return it;
	};

/***/ }),
/* 20 */
/***/ (function(module, exports) {

	module.exports = function(it){
	  return typeof it === 'object' ? it !== null : typeof it === 'function';
	};

/***/ }),
/* 21 */
/***/ (function(module, exports, __webpack_require__) {

	module.exports = !__webpack_require__(22) && !__webpack_require__(23)(function(){
	  return Object.defineProperty(__webpack_require__(24)('div'), 'a', {get: function(){ return 7; }}).a != 7;
	});

/***/ }),
/* 22 */
/***/ (function(module, exports, __webpack_require__) {

	// Thank's IE8 for his funny defineProperty
	module.exports = !__webpack_require__(23)(function(){
	  return Object.defineProperty({}, 'a', {get: function(){ return 7; }}).a != 7;
	});

/***/ }),
/* 23 */
/***/ (function(module, exports) {

	module.exports = function(exec){
	  try {
	    return !!exec();
	  } catch(e){
	    return true;
	  }
	};

/***/ }),
/* 24 */
/***/ (function(module, exports, __webpack_require__) {

	var isObject = __webpack_require__(20)
	  , document = __webpack_require__(13).document
	  // in old IE typeof document.createElement is 'object'
	  , is = isObject(document) && isObject(document.createElement);
	module.exports = function(it){
	  return is ? document.createElement(it) : {};
	};

/***/ }),
/* 25 */
/***/ (function(module, exports, __webpack_require__) {

	// 7.1.1 ToPrimitive(input [, PreferredType])
	var isObject = __webpack_require__(20);
	// instead of the ES6 spec version, we didn't implement @@toPrimitive case
	// and the second argument - flag - preferred type is a string
	module.exports = function(it, S){
	  if(!isObject(it))return it;
	  var fn, val;
	  if(S && typeof (fn = it.toString) == 'function' && !isObject(val = fn.call(it)))return val;
	  if(typeof (fn = it.valueOf) == 'function' && !isObject(val = fn.call(it)))return val;
	  if(!S && typeof (fn = it.toString) == 'function' && !isObject(val = fn.call(it)))return val;
	  throw TypeError("Can't convert object to primitive value");
	};

/***/ }),
/* 26 */
/***/ (function(module, exports) {

	module.exports = function(bitmap, value){
	  return {
	    enumerable  : !(bitmap & 1),
	    configurable: !(bitmap & 2),
	    writable    : !(bitmap & 4),
	    value       : value
	  };
	};

/***/ }),
/* 27 */
/***/ (function(module, exports, __webpack_require__) {

	module.exports = __webpack_require__(17);

/***/ }),
/* 28 */
/***/ (function(module, exports) {

	var hasOwnProperty = {}.hasOwnProperty;
	module.exports = function(it, key){
	  return hasOwnProperty.call(it, key);
	};

/***/ }),
/* 29 */
/***/ (function(module, exports) {

	module.exports = {};

/***/ }),
/* 30 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';
	var create         = __webpack_require__(31)
	  , descriptor     = __webpack_require__(26)
	  , setToStringTag = __webpack_require__(46)
	  , IteratorPrototype = {};

	// 25.1.2.1.1 %IteratorPrototype%[@@iterator]()
	__webpack_require__(17)(IteratorPrototype, __webpack_require__(47)('iterator'), function(){ return this; });

	module.exports = function(Constructor, NAME, next){
	  Constructor.prototype = create(IteratorPrototype, {next: descriptor(1, next)});
	  setToStringTag(Constructor, NAME + ' Iterator');
	};

/***/ }),
/* 31 */
/***/ (function(module, exports, __webpack_require__) {

	// 19.1.2.2 / 15.2.3.5 Object.create(O [, Properties])
	var anObject    = __webpack_require__(19)
	  , dPs         = __webpack_require__(32)
	  , enumBugKeys = __webpack_require__(44)
	  , IE_PROTO    = __webpack_require__(41)('IE_PROTO')
	  , Empty       = function(){ /* empty */ }
	  , PROTOTYPE   = 'prototype';

	// Create object with fake `null` prototype: use iframe Object with cleared prototype
	var createDict = function(){
	  // Thrash, waste and sodomy: IE GC bug
	  var iframe = __webpack_require__(24)('iframe')
	    , i      = enumBugKeys.length
	    , lt     = '<'
	    , gt     = '>'
	    , iframeDocument;
	  iframe.style.display = 'none';
	  __webpack_require__(45).appendChild(iframe);
	  iframe.src = 'javascript:'; // eslint-disable-line no-script-url
	  // createDict = iframe.contentWindow.Object;
	  // html.removeChild(iframe);
	  iframeDocument = iframe.contentWindow.document;
	  iframeDocument.open();
	  iframeDocument.write(lt + 'script' + gt + 'document.F=Object' + lt + '/script' + gt);
	  iframeDocument.close();
	  createDict = iframeDocument.F;
	  while(i--)delete createDict[PROTOTYPE][enumBugKeys[i]];
	  return createDict();
	};

	module.exports = Object.create || function create(O, Properties){
	  var result;
	  if(O !== null){
	    Empty[PROTOTYPE] = anObject(O);
	    result = new Empty;
	    Empty[PROTOTYPE] = null;
	    // add "__proto__" for Object.getPrototypeOf polyfill
	    result[IE_PROTO] = O;
	  } else result = createDict();
	  return Properties === undefined ? result : dPs(result, Properties);
	};


/***/ }),
/* 32 */
/***/ (function(module, exports, __webpack_require__) {

	var dP       = __webpack_require__(18)
	  , anObject = __webpack_require__(19)
	  , getKeys  = __webpack_require__(33);

	module.exports = __webpack_require__(22) ? Object.defineProperties : function defineProperties(O, Properties){
	  anObject(O);
	  var keys   = getKeys(Properties)
	    , length = keys.length
	    , i = 0
	    , P;
	  while(length > i)dP.f(O, P = keys[i++], Properties[P]);
	  return O;
	};

/***/ }),
/* 33 */
/***/ (function(module, exports, __webpack_require__) {

	// 19.1.2.14 / 15.2.3.14 Object.keys(O)
	var $keys       = __webpack_require__(34)
	  , enumBugKeys = __webpack_require__(44);

	module.exports = Object.keys || function keys(O){
	  return $keys(O, enumBugKeys);
	};

/***/ }),
/* 34 */
/***/ (function(module, exports, __webpack_require__) {

	var has          = __webpack_require__(28)
	  , toIObject    = __webpack_require__(35)
	  , arrayIndexOf = __webpack_require__(38)(false)
	  , IE_PROTO     = __webpack_require__(41)('IE_PROTO');

	module.exports = function(object, names){
	  var O      = toIObject(object)
	    , i      = 0
	    , result = []
	    , key;
	  for(key in O)if(key != IE_PROTO)has(O, key) && result.push(key);
	  // Don't enum bug & hidden keys
	  while(names.length > i)if(has(O, key = names[i++])){
	    ~arrayIndexOf(result, key) || result.push(key);
	  }
	  return result;
	};

/***/ }),
/* 35 */
/***/ (function(module, exports, __webpack_require__) {

	// to indexed object, toObject with fallback for non-array-like ES3 strings
	var IObject = __webpack_require__(36)
	  , defined = __webpack_require__(9);
	module.exports = function(it){
	  return IObject(defined(it));
	};

/***/ }),
/* 36 */
/***/ (function(module, exports, __webpack_require__) {

	// fallback for non-array-like ES3 and non-enumerable old V8 strings
	var cof = __webpack_require__(37);
	module.exports = Object('z').propertyIsEnumerable(0) ? Object : function(it){
	  return cof(it) == 'String' ? it.split('') : Object(it);
	};

/***/ }),
/* 37 */
/***/ (function(module, exports) {

	var toString = {}.toString;

	module.exports = function(it){
	  return toString.call(it).slice(8, -1);
	};

/***/ }),
/* 38 */
/***/ (function(module, exports, __webpack_require__) {

	// false -> Array#indexOf
	// true  -> Array#includes
	var toIObject = __webpack_require__(35)
	  , toLength  = __webpack_require__(39)
	  , toIndex   = __webpack_require__(40);
	module.exports = function(IS_INCLUDES){
	  return function($this, el, fromIndex){
	    var O      = toIObject($this)
	      , length = toLength(O.length)
	      , index  = toIndex(fromIndex, length)
	      , value;
	    // Array#includes uses SameValueZero equality algorithm
	    if(IS_INCLUDES && el != el)while(length > index){
	      value = O[index++];
	      if(value != value)return true;
	    // Array#toIndex ignores holes, Array#includes - not
	    } else for(;length > index; index++)if(IS_INCLUDES || index in O){
	      if(O[index] === el)return IS_INCLUDES || index || 0;
	    } return !IS_INCLUDES && -1;
	  };
	};

/***/ }),
/* 39 */
/***/ (function(module, exports, __webpack_require__) {

	// 7.1.15 ToLength
	var toInteger = __webpack_require__(8)
	  , min       = Math.min;
	module.exports = function(it){
	  return it > 0 ? min(toInteger(it), 0x1fffffffffffff) : 0; // pow(2, 53) - 1 == 9007199254740991
	};

/***/ }),
/* 40 */
/***/ (function(module, exports, __webpack_require__) {

	var toInteger = __webpack_require__(8)
	  , max       = Math.max
	  , min       = Math.min;
	module.exports = function(index, length){
	  index = toInteger(index);
	  return index < 0 ? max(index + length, 0) : min(index, length);
	};

/***/ }),
/* 41 */
/***/ (function(module, exports, __webpack_require__) {

	var shared = __webpack_require__(42)('keys')
	  , uid    = __webpack_require__(43);
	module.exports = function(key){
	  return shared[key] || (shared[key] = uid(key));
	};

/***/ }),
/* 42 */
/***/ (function(module, exports, __webpack_require__) {

	var global = __webpack_require__(13)
	  , SHARED = '__core-js_shared__'
	  , store  = global[SHARED] || (global[SHARED] = {});
	module.exports = function(key){
	  return store[key] || (store[key] = {});
	};

/***/ }),
/* 43 */
/***/ (function(module, exports) {

	var id = 0
	  , px = Math.random();
	module.exports = function(key){
	  return 'Symbol('.concat(key === undefined ? '' : key, ')_', (++id + px).toString(36));
	};

/***/ }),
/* 44 */
/***/ (function(module, exports) {

	// IE 8- don't enum bug keys
	module.exports = (
	  'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf'
	).split(',');

/***/ }),
/* 45 */
/***/ (function(module, exports, __webpack_require__) {

	module.exports = __webpack_require__(13).document && document.documentElement;

/***/ }),
/* 46 */
/***/ (function(module, exports, __webpack_require__) {

	var def = __webpack_require__(18).f
	  , has = __webpack_require__(28)
	  , TAG = __webpack_require__(47)('toStringTag');

	module.exports = function(it, tag, stat){
	  if(it && !has(it = stat ? it : it.prototype, TAG))def(it, TAG, {configurable: true, value: tag});
	};

/***/ }),
/* 47 */
/***/ (function(module, exports, __webpack_require__) {

	var store      = __webpack_require__(42)('wks')
	  , uid        = __webpack_require__(43)
	  , Symbol     = __webpack_require__(13).Symbol
	  , USE_SYMBOL = typeof Symbol == 'function';

	var $exports = module.exports = function(name){
	  return store[name] || (store[name] =
	    USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : uid)('Symbol.' + name));
	};

	$exports.store = store;

/***/ }),
/* 48 */
/***/ (function(module, exports, __webpack_require__) {

	// 19.1.2.9 / 15.2.3.2 Object.getPrototypeOf(O)
	var has         = __webpack_require__(28)
	  , toObject    = __webpack_require__(49)
	  , IE_PROTO    = __webpack_require__(41)('IE_PROTO')
	  , ObjectProto = Object.prototype;

	module.exports = Object.getPrototypeOf || function(O){
	  O = toObject(O);
	  if(has(O, IE_PROTO))return O[IE_PROTO];
	  if(typeof O.constructor == 'function' && O instanceof O.constructor){
	    return O.constructor.prototype;
	  } return O instanceof Object ? ObjectProto : null;
	};

/***/ }),
/* 49 */
/***/ (function(module, exports, __webpack_require__) {

	// 7.1.13 ToObject(argument)
	var defined = __webpack_require__(9);
	module.exports = function(it){
	  return Object(defined(it));
	};

/***/ }),
/* 50 */
/***/ (function(module, exports, __webpack_require__) {

	__webpack_require__(51);
	var global        = __webpack_require__(13)
	  , hide          = __webpack_require__(17)
	  , Iterators     = __webpack_require__(29)
	  , TO_STRING_TAG = __webpack_require__(47)('toStringTag');

	for(var collections = ['NodeList', 'DOMTokenList', 'MediaList', 'StyleSheetList', 'CSSRuleList'], i = 0; i < 5; i++){
	  var NAME       = collections[i]
	    , Collection = global[NAME]
	    , proto      = Collection && Collection.prototype;
	  if(proto && !proto[TO_STRING_TAG])hide(proto, TO_STRING_TAG, NAME);
	  Iterators[NAME] = Iterators.Array;
	}

/***/ }),
/* 51 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';
	var addToUnscopables = __webpack_require__(52)
	  , step             = __webpack_require__(53)
	  , Iterators        = __webpack_require__(29)
	  , toIObject        = __webpack_require__(35);

	// 22.1.3.4 Array.prototype.entries()
	// 22.1.3.13 Array.prototype.keys()
	// 22.1.3.29 Array.prototype.values()
	// 22.1.3.30 Array.prototype[@@iterator]()
	module.exports = __webpack_require__(10)(Array, 'Array', function(iterated, kind){
	  this._t = toIObject(iterated); // target
	  this._i = 0;                   // next index
	  this._k = kind;                // kind
	// 22.1.5.2.1 %ArrayIteratorPrototype%.next()
	}, function(){
	  var O     = this._t
	    , kind  = this._k
	    , index = this._i++;
	  if(!O || index >= O.length){
	    this._t = undefined;
	    return step(1);
	  }
	  if(kind == 'keys'  )return step(0, index);
	  if(kind == 'values')return step(0, O[index]);
	  return step(0, [index, O[index]]);
	}, 'values');

	// argumentsList[@@iterator] is %ArrayProto_values% (9.4.4.6, 9.4.4.7)
	Iterators.Arguments = Iterators.Array;

	addToUnscopables('keys');
	addToUnscopables('values');
	addToUnscopables('entries');

/***/ }),
/* 52 */
/***/ (function(module, exports) {

	module.exports = function(){ /* empty */ };

/***/ }),
/* 53 */
/***/ (function(module, exports) {

	module.exports = function(done, value){
	  return {value: value, done: !!done};
	};

/***/ }),
/* 54 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';
	var LIBRARY            = __webpack_require__(11)
	  , global             = __webpack_require__(13)
	  , ctx                = __webpack_require__(15)
	  , classof            = __webpack_require__(55)
	  , $export            = __webpack_require__(12)
	  , isObject           = __webpack_require__(20)
	  , aFunction          = __webpack_require__(16)
	  , anInstance         = __webpack_require__(56)
	  , forOf              = __webpack_require__(57)
	  , speciesConstructor = __webpack_require__(61)
	  , task               = __webpack_require__(62).set
	  , microtask          = __webpack_require__(64)()
	  , PROMISE            = 'Promise'
	  , TypeError          = global.TypeError
	  , process            = global.process
	  , $Promise           = global[PROMISE]
	  , process            = global.process
	  , isNode             = classof(process) == 'process'
	  , empty              = function(){ /* empty */ }
	  , Internal, GenericPromiseCapability, Wrapper;

	var USE_NATIVE = !!function(){
	  try {
	    // correct subclassing with @@species support
	    var promise     = $Promise.resolve(1)
	      , FakePromise = (promise.constructor = {})[__webpack_require__(47)('species')] = function(exec){ exec(empty, empty); };
	    // unhandled rejections tracking support, NodeJS Promise without it fails @@species test
	    return (isNode || typeof PromiseRejectionEvent == 'function') && promise.then(empty) instanceof FakePromise;
	  } catch(e){ /* empty */ }
	}();

	// helpers
	var sameConstructor = function(a, b){
	  // with library wrapper special case
	  return a === b || a === $Promise && b === Wrapper;
	};
	var isThenable = function(it){
	  var then;
	  return isObject(it) && typeof (then = it.then) == 'function' ? then : false;
	};
	var newPromiseCapability = function(C){
	  return sameConstructor($Promise, C)
	    ? new PromiseCapability(C)
	    : new GenericPromiseCapability(C);
	};
	var PromiseCapability = GenericPromiseCapability = function(C){
	  var resolve, reject;
	  this.promise = new C(function($$resolve, $$reject){
	    if(resolve !== undefined || reject !== undefined)throw TypeError('Bad Promise constructor');
	    resolve = $$resolve;
	    reject  = $$reject;
	  });
	  this.resolve = aFunction(resolve);
	  this.reject  = aFunction(reject);
	};
	var perform = function(exec){
	  try {
	    exec();
	  } catch(e){
	    return {error: e};
	  }
	};
	var notify = function(promise, isReject){
	  if(promise._n)return;
	  promise._n = true;
	  var chain = promise._c;
	  microtask(function(){
	    var value = promise._v
	      , ok    = promise._s == 1
	      , i     = 0;
	    var run = function(reaction){
	      var handler = ok ? reaction.ok : reaction.fail
	        , resolve = reaction.resolve
	        , reject  = reaction.reject
	        , domain  = reaction.domain
	        , result, then;
	      try {
	        if(handler){
	          if(!ok){
	            if(promise._h == 2)onHandleUnhandled(promise);
	            promise._h = 1;
	          }
	          if(handler === true)result = value;
	          else {
	            if(domain)domain.enter();
	            result = handler(value);
	            if(domain)domain.exit();
	          }
	          if(result === reaction.promise){
	            reject(TypeError('Promise-chain cycle'));
	          } else if(then = isThenable(result)){
	            then.call(result, resolve, reject);
	          } else resolve(result);
	        } else reject(value);
	      } catch(e){
	        reject(e);
	      }
	    };
	    while(chain.length > i)run(chain[i++]); // variable length - can't use forEach
	    promise._c = [];
	    promise._n = false;
	    if(isReject && !promise._h)onUnhandled(promise);
	  });
	};
	var onUnhandled = function(promise){
	  task.call(global, function(){
	    var value = promise._v
	      , abrupt, handler, console;
	    if(isUnhandled(promise)){
	      abrupt = perform(function(){
	        if(isNode){
	          process.emit('unhandledRejection', value, promise);
	        } else if(handler = global.onunhandledrejection){
	          handler({promise: promise, reason: value});
	        } else if((console = global.console) && console.error){
	          console.error('Unhandled promise rejection', value);
	        }
	      });
	      // Browsers should not trigger `rejectionHandled` event if it was handled here, NodeJS - should
	      promise._h = isNode || isUnhandled(promise) ? 2 : 1;
	    } promise._a = undefined;
	    if(abrupt)throw abrupt.error;
	  });
	};
	var isUnhandled = function(promise){
	  if(promise._h == 1)return false;
	  var chain = promise._a || promise._c
	    , i     = 0
	    , reaction;
	  while(chain.length > i){
	    reaction = chain[i++];
	    if(reaction.fail || !isUnhandled(reaction.promise))return false;
	  } return true;
	};
	var onHandleUnhandled = function(promise){
	  task.call(global, function(){
	    var handler;
	    if(isNode){
	      process.emit('rejectionHandled', promise);
	    } else if(handler = global.onrejectionhandled){
	      handler({promise: promise, reason: promise._v});
	    }
	  });
	};
	var $reject = function(value){
	  var promise = this;
	  if(promise._d)return;
	  promise._d = true;
	  promise = promise._w || promise; // unwrap
	  promise._v = value;
	  promise._s = 2;
	  if(!promise._a)promise._a = promise._c.slice();
	  notify(promise, true);
	};
	var $resolve = function(value){
	  var promise = this
	    , then;
	  if(promise._d)return;
	  promise._d = true;
	  promise = promise._w || promise; // unwrap
	  try {
	    if(promise === value)throw TypeError("Promise can't be resolved itself");
	    if(then = isThenable(value)){
	      microtask(function(){
	        var wrapper = {_w: promise, _d: false}; // wrap
	        try {
	          then.call(value, ctx($resolve, wrapper, 1), ctx($reject, wrapper, 1));
	        } catch(e){
	          $reject.call(wrapper, e);
	        }
	      });
	    } else {
	      promise._v = value;
	      promise._s = 1;
	      notify(promise, false);
	    }
	  } catch(e){
	    $reject.call({_w: promise, _d: false}, e); // wrap
	  }
	};

	// constructor polyfill
	if(!USE_NATIVE){
	  // 25.4.3.1 Promise(executor)
	  $Promise = function Promise(executor){
	    anInstance(this, $Promise, PROMISE, '_h');
	    aFunction(executor);
	    Internal.call(this);
	    try {
	      executor(ctx($resolve, this, 1), ctx($reject, this, 1));
	    } catch(err){
	      $reject.call(this, err);
	    }
	  };
	  Internal = function Promise(executor){
	    this._c = [];             // <- awaiting reactions
	    this._a = undefined;      // <- checked in isUnhandled reactions
	    this._s = 0;              // <- state
	    this._d = false;          // <- done
	    this._v = undefined;      // <- value
	    this._h = 0;              // <- rejection state, 0 - default, 1 - handled, 2 - unhandled
	    this._n = false;          // <- notify
	  };
	  Internal.prototype = __webpack_require__(65)($Promise.prototype, {
	    // 25.4.5.3 Promise.prototype.then(onFulfilled, onRejected)
	    then: function then(onFulfilled, onRejected){
	      var reaction    = newPromiseCapability(speciesConstructor(this, $Promise));
	      reaction.ok     = typeof onFulfilled == 'function' ? onFulfilled : true;
	      reaction.fail   = typeof onRejected == 'function' && onRejected;
	      reaction.domain = isNode ? process.domain : undefined;
	      this._c.push(reaction);
	      if(this._a)this._a.push(reaction);
	      if(this._s)notify(this, false);
	      return reaction.promise;
	    },
	    // 25.4.5.1 Promise.prototype.catch(onRejected)
	    'catch': function(onRejected){
	      return this.then(undefined, onRejected);
	    }
	  });
	  PromiseCapability = function(){
	    var promise  = new Internal;
	    this.promise = promise;
	    this.resolve = ctx($resolve, promise, 1);
	    this.reject  = ctx($reject, promise, 1);
	  };
	}

	$export($export.G + $export.W + $export.F * !USE_NATIVE, {Promise: $Promise});
	__webpack_require__(46)($Promise, PROMISE);
	__webpack_require__(66)(PROMISE);
	Wrapper = __webpack_require__(14)[PROMISE];

	// statics
	$export($export.S + $export.F * !USE_NATIVE, PROMISE, {
	  // 25.4.4.5 Promise.reject(r)
	  reject: function reject(r){
	    var capability = newPromiseCapability(this)
	      , $$reject   = capability.reject;
	    $$reject(r);
	    return capability.promise;
	  }
	});
	$export($export.S + $export.F * (LIBRARY || !USE_NATIVE), PROMISE, {
	  // 25.4.4.6 Promise.resolve(x)
	  resolve: function resolve(x){
	    // instanceof instead of internal slot check because we should fix it without replacement native Promise core
	    if(x instanceof $Promise && sameConstructor(x.constructor, this))return x;
	    var capability = newPromiseCapability(this)
	      , $$resolve  = capability.resolve;
	    $$resolve(x);
	    return capability.promise;
	  }
	});
	$export($export.S + $export.F * !(USE_NATIVE && __webpack_require__(67)(function(iter){
	  $Promise.all(iter)['catch'](empty);
	})), PROMISE, {
	  // 25.4.4.1 Promise.all(iterable)
	  all: function all(iterable){
	    var C          = this
	      , capability = newPromiseCapability(C)
	      , resolve    = capability.resolve
	      , reject     = capability.reject;
	    var abrupt = perform(function(){
	      var values    = []
	        , index     = 0
	        , remaining = 1;
	      forOf(iterable, false, function(promise){
	        var $index        = index++
	          , alreadyCalled = false;
	        values.push(undefined);
	        remaining++;
	        C.resolve(promise).then(function(value){
	          if(alreadyCalled)return;
	          alreadyCalled  = true;
	          values[$index] = value;
	          --remaining || resolve(values);
	        }, reject);
	      });
	      --remaining || resolve(values);
	    });
	    if(abrupt)reject(abrupt.error);
	    return capability.promise;
	  },
	  // 25.4.4.4 Promise.race(iterable)
	  race: function race(iterable){
	    var C          = this
	      , capability = newPromiseCapability(C)
	      , reject     = capability.reject;
	    var abrupt = perform(function(){
	      forOf(iterable, false, function(promise){
	        C.resolve(promise).then(capability.resolve, reject);
	      });
	    });
	    if(abrupt)reject(abrupt.error);
	    return capability.promise;
	  }
	});

/***/ }),
/* 55 */
/***/ (function(module, exports, __webpack_require__) {

	// getting tag from 19.1.3.6 Object.prototype.toString()
	var cof = __webpack_require__(37)
	  , TAG = __webpack_require__(47)('toStringTag')
	  // ES3 wrong here
	  , ARG = cof(function(){ return arguments; }()) == 'Arguments';

	// fallback for IE11 Script Access Denied error
	var tryGet = function(it, key){
	  try {
	    return it[key];
	  } catch(e){ /* empty */ }
	};

	module.exports = function(it){
	  var O, T, B;
	  return it === undefined ? 'Undefined' : it === null ? 'Null'
	    // @@toStringTag case
	    : typeof (T = tryGet(O = Object(it), TAG)) == 'string' ? T
	    // builtinTag case
	    : ARG ? cof(O)
	    // ES3 arguments fallback
	    : (B = cof(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : B;
	};

/***/ }),
/* 56 */
/***/ (function(module, exports) {

	module.exports = function(it, Constructor, name, forbiddenField){
	  if(!(it instanceof Constructor) || (forbiddenField !== undefined && forbiddenField in it)){
	    throw TypeError(name + ': incorrect invocation!');
	  } return it;
	};

/***/ }),
/* 57 */
/***/ (function(module, exports, __webpack_require__) {

	var ctx         = __webpack_require__(15)
	  , call        = __webpack_require__(58)
	  , isArrayIter = __webpack_require__(59)
	  , anObject    = __webpack_require__(19)
	  , toLength    = __webpack_require__(39)
	  , getIterFn   = __webpack_require__(60)
	  , BREAK       = {}
	  , RETURN      = {};
	var exports = module.exports = function(iterable, entries, fn, that, ITERATOR){
	  var iterFn = ITERATOR ? function(){ return iterable; } : getIterFn(iterable)
	    , f      = ctx(fn, that, entries ? 2 : 1)
	    , index  = 0
	    , length, step, iterator, result;
	  if(typeof iterFn != 'function')throw TypeError(iterable + ' is not iterable!');
	  // fast case for arrays with default iterator
	  if(isArrayIter(iterFn))for(length = toLength(iterable.length); length > index; index++){
	    result = entries ? f(anObject(step = iterable[index])[0], step[1]) : f(iterable[index]);
	    if(result === BREAK || result === RETURN)return result;
	  } else for(iterator = iterFn.call(iterable); !(step = iterator.next()).done; ){
	    result = call(iterator, f, step.value, entries);
	    if(result === BREAK || result === RETURN)return result;
	  }
	};
	exports.BREAK  = BREAK;
	exports.RETURN = RETURN;

/***/ }),
/* 58 */
/***/ (function(module, exports, __webpack_require__) {

	// call something on iterator step with safe closing on error
	var anObject = __webpack_require__(19);
	module.exports = function(iterator, fn, value, entries){
	  try {
	    return entries ? fn(anObject(value)[0], value[1]) : fn(value);
	  // 7.4.6 IteratorClose(iterator, completion)
	  } catch(e){
	    var ret = iterator['return'];
	    if(ret !== undefined)anObject(ret.call(iterator));
	    throw e;
	  }
	};

/***/ }),
/* 59 */
/***/ (function(module, exports, __webpack_require__) {

	// check on default Array iterator
	var Iterators  = __webpack_require__(29)
	  , ITERATOR   = __webpack_require__(47)('iterator')
	  , ArrayProto = Array.prototype;

	module.exports = function(it){
	  return it !== undefined && (Iterators.Array === it || ArrayProto[ITERATOR] === it);
	};

/***/ }),
/* 60 */
/***/ (function(module, exports, __webpack_require__) {

	var classof   = __webpack_require__(55)
	  , ITERATOR  = __webpack_require__(47)('iterator')
	  , Iterators = __webpack_require__(29);
	module.exports = __webpack_require__(14).getIteratorMethod = function(it){
	  if(it != undefined)return it[ITERATOR]
	    || it['@@iterator']
	    || Iterators[classof(it)];
	};

/***/ }),
/* 61 */
/***/ (function(module, exports, __webpack_require__) {

	// 7.3.20 SpeciesConstructor(O, defaultConstructor)
	var anObject  = __webpack_require__(19)
	  , aFunction = __webpack_require__(16)
	  , SPECIES   = __webpack_require__(47)('species');
	module.exports = function(O, D){
	  var C = anObject(O).constructor, S;
	  return C === undefined || (S = anObject(C)[SPECIES]) == undefined ? D : aFunction(S);
	};

/***/ }),
/* 62 */
/***/ (function(module, exports, __webpack_require__) {

	var ctx                = __webpack_require__(15)
	  , invoke             = __webpack_require__(63)
	  , html               = __webpack_require__(45)
	  , cel                = __webpack_require__(24)
	  , global             = __webpack_require__(13)
	  , process            = global.process
	  , setTask            = global.setImmediate
	  , clearTask          = global.clearImmediate
	  , MessageChannel     = global.MessageChannel
	  , counter            = 0
	  , queue              = {}
	  , ONREADYSTATECHANGE = 'onreadystatechange'
	  , defer, channel, port;
	var run = function(){
	  var id = +this;
	  if(queue.hasOwnProperty(id)){
	    var fn = queue[id];
	    delete queue[id];
	    fn();
	  }
	};
	var listener = function(event){
	  run.call(event.data);
	};
	// Node.js 0.9+ & IE10+ has setImmediate, otherwise:
	if(!setTask || !clearTask){
	  setTask = function setImmediate(fn){
	    var args = [], i = 1;
	    while(arguments.length > i)args.push(arguments[i++]);
	    queue[++counter] = function(){
	      invoke(typeof fn == 'function' ? fn : Function(fn), args);
	    };
	    defer(counter);
	    return counter;
	  };
	  clearTask = function clearImmediate(id){
	    delete queue[id];
	  };
	  // Node.js 0.8-
	  if(__webpack_require__(37)(process) == 'process'){
	    defer = function(id){
	      process.nextTick(ctx(run, id, 1));
	    };
	  // Browsers with MessageChannel, includes WebWorkers
	  } else if(MessageChannel){
	    channel = new MessageChannel;
	    port    = channel.port2;
	    channel.port1.onmessage = listener;
	    defer = ctx(port.postMessage, port, 1);
	  // Browsers with postMessage, skip WebWorkers
	  // IE8 has postMessage, but it's sync & typeof its postMessage is 'object'
	  } else if(global.addEventListener && typeof postMessage == 'function' && !global.importScripts){
	    defer = function(id){
	      global.postMessage(id + '', '*');
	    };
	    global.addEventListener('message', listener, false);
	  // IE8-
	  } else if(ONREADYSTATECHANGE in cel('script')){
	    defer = function(id){
	      html.appendChild(cel('script'))[ONREADYSTATECHANGE] = function(){
	        html.removeChild(this);
	        run.call(id);
	      };
	    };
	  // Rest old browsers
	  } else {
	    defer = function(id){
	      setTimeout(ctx(run, id, 1), 0);
	    };
	  }
	}
	module.exports = {
	  set:   setTask,
	  clear: clearTask
	};

/***/ }),
/* 63 */
/***/ (function(module, exports) {

	// fast apply, http://jsperf.lnkit.com/fast-apply/5
	module.exports = function(fn, args, that){
	  var un = that === undefined;
	  switch(args.length){
	    case 0: return un ? fn()
	                      : fn.call(that);
	    case 1: return un ? fn(args[0])
	                      : fn.call(that, args[0]);
	    case 2: return un ? fn(args[0], args[1])
	                      : fn.call(that, args[0], args[1]);
	    case 3: return un ? fn(args[0], args[1], args[2])
	                      : fn.call(that, args[0], args[1], args[2]);
	    case 4: return un ? fn(args[0], args[1], args[2], args[3])
	                      : fn.call(that, args[0], args[1], args[2], args[3]);
	  } return              fn.apply(that, args);
	};

/***/ }),
/* 64 */
/***/ (function(module, exports, __webpack_require__) {

	var global    = __webpack_require__(13)
	  , macrotask = __webpack_require__(62).set
	  , Observer  = global.MutationObserver || global.WebKitMutationObserver
	  , process   = global.process
	  , Promise   = global.Promise
	  , isNode    = __webpack_require__(37)(process) == 'process';

	module.exports = function(){
	  var head, last, notify;

	  var flush = function(){
	    var parent, fn;
	    if(isNode && (parent = process.domain))parent.exit();
	    while(head){
	      fn   = head.fn;
	      head = head.next;
	      try {
	        fn();
	      } catch(e){
	        if(head)notify();
	        else last = undefined;
	        throw e;
	      }
	    } last = undefined;
	    if(parent)parent.enter();
	  };

	  // Node.js
	  if(isNode){
	    notify = function(){
	      process.nextTick(flush);
	    };
	  // browsers with MutationObserver
	  } else if(Observer){
	    var toggle = true
	      , node   = document.createTextNode('');
	    new Observer(flush).observe(node, {characterData: true}); // eslint-disable-line no-new
	    notify = function(){
	      node.data = toggle = !toggle;
	    };
	  // environments with maybe non-completely correct, but existent Promise
	  } else if(Promise && Promise.resolve){
	    var promise = Promise.resolve();
	    notify = function(){
	      promise.then(flush);
	    };
	  // for other environments - macrotask based on:
	  // - setImmediate
	  // - MessageChannel
	  // - window.postMessag
	  // - onreadystatechange
	  // - setTimeout
	  } else {
	    notify = function(){
	      // strange IE + webpack dev server bug - use .call(global)
	      macrotask.call(global, flush);
	    };
	  }

	  return function(fn){
	    var task = {fn: fn, next: undefined};
	    if(last)last.next = task;
	    if(!head){
	      head = task;
	      notify();
	    } last = task;
	  };
	};

/***/ }),
/* 65 */
/***/ (function(module, exports, __webpack_require__) {

	var hide = __webpack_require__(17);
	module.exports = function(target, src, safe){
	  for(var key in src){
	    if(safe && target[key])target[key] = src[key];
	    else hide(target, key, src[key]);
	  } return target;
	};

/***/ }),
/* 66 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';
	var global      = __webpack_require__(13)
	  , core        = __webpack_require__(14)
	  , dP          = __webpack_require__(18)
	  , DESCRIPTORS = __webpack_require__(22)
	  , SPECIES     = __webpack_require__(47)('species');

	module.exports = function(KEY){
	  var C = typeof core[KEY] == 'function' ? core[KEY] : global[KEY];
	  if(DESCRIPTORS && C && !C[SPECIES])dP.f(C, SPECIES, {
	    configurable: true,
	    get: function(){ return this; }
	  });
	};

/***/ }),
/* 67 */
/***/ (function(module, exports, __webpack_require__) {

	var ITERATOR     = __webpack_require__(47)('iterator')
	  , SAFE_CLOSING = false;

	try {
	  var riter = [7][ITERATOR]();
	  riter['return'] = function(){ SAFE_CLOSING = true; };
	  Array.from(riter, function(){ throw 2; });
	} catch(e){ /* empty */ }

	module.exports = function(exec, skipClosing){
	  if(!skipClosing && !SAFE_CLOSING)return false;
	  var safe = false;
	  try {
	    var arr  = [7]
	      , iter = arr[ITERATOR]();
	    iter.next = function(){ return {done: safe = true}; };
	    arr[ITERATOR] = function(){ return iter; };
	    exec(arr);
	  } catch(e){ /* empty */ }
	  return safe;
	};

/***/ }),
/* 68 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * @fileoverview Invoker - invoke commands
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      */


	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	var eventNames = _consts2.default.eventNames,
	    rejectMessages = _consts2.default.rejectMessages;
	var isFunction = _tuiCodeSnippet2.default.isFunction,
	    isString = _tuiCodeSnippet2.default.isString,
	    CustomEvents = _tuiCodeSnippet2.default.CustomEvents;

	/**
	 * Invoker
	 * @class
	 * @ignore
	 */

	var Invoker = function () {
	    function Invoker() {
	        _classCallCheck(this, Invoker);

	        /**
	         * Undo stack
	         * @type {Array.<Command>}
	         * @private
	         */
	        this._undoStack = [];

	        /**
	         * Redo stack
	         * @type {Array.<Command>}
	         * @private
	         */
	        this._redoStack = [];

	        /**
	         * Lock-flag for executing command
	         * @type {boolean}
	         * @private
	         */
	        this._isLocked = false;
	    }

	    /**
	     * Invoke command execution
	     * @param {Command} command - Command
	     * @returns {Promise}
	     * @private
	     */


	    _createClass(Invoker, [{
	        key: '_invokeExecution',
	        value: function _invokeExecution(command) {
	            var _this = this;

	            this.lock();

	            var args = command.args;

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

	            return command.execute.apply(command, args).then(function (value) {
	                _this.pushUndoStack(command);
	                _this.unlock();
	                if (isFunction(command.executeCallback)) {
	                    command.executeCallback(value);
	                }

	                return value;
	            })['catch'](function (message) {
	                _this.unlock();

	                return _promise2.default.reject(message);
	            });
	        }

	        /**
	         * Invoke command undo
	         * @param {Command} command - Command
	         * @returns {Promise}
	         * @private
	         */

	    }, {
	        key: '_invokeUndo',
	        value: function _invokeUndo(command) {
	            var _this2 = this;

	            this.lock();

	            var args = command.args;

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

	            return command.undo.apply(command, args).then(function (value) {
	                _this2.pushRedoStack(command);
	                _this2.unlock();
	                if (isFunction(command.undoCallback)) {
	                    command.undoCallback(value);
	                }

	                return value;
	            })['catch'](function (message) {
	                _this2.unlock();

	                return _promise2.default.reject(message);
	            });
	        }

	        /**
	         * fire REDO_STACK_CHANGED event
	         * @private
	         */

	    }, {
	        key: '_fireRedoStackChanged',
	        value: function _fireRedoStackChanged() {
	            this.fire(eventNames.REDO_STACK_CHANGED, this._redoStack.length);
	        }

	        /**
	         * fire UNDO_STACK_CHANGED event
	         * @private
	         */

	    }, {
	        key: '_fireUndoStackChanged',
	        value: function _fireUndoStackChanged() {
	            this.fire(eventNames.UNDO_STACK_CHANGED, this._undoStack.length);
	        }

	        /**
	         * Lock this invoker
	         */

	    }, {
	        key: 'lock',
	        value: function lock() {
	            this._isLocked = true;
	        }

	        /**
	         * Unlock this invoker
	         */

	    }, {
	        key: 'unlock',
	        value: function unlock() {
	            this._isLocked = false;
	        }

	        /**
	         * Invoke command
	         * Store the command to the undoStack
	         * Clear the redoStack
	         * @param {String} commandName - Command name
	         * @param {...*} args - Arguments for creating command
	         * @returns {Promise}
	         */

	    }, {
	        key: 'execute',
	        value: function execute() {
	            var _this3 = this;

	            if (this._isLocked) {
	                return _promise2.default.reject(rejectMessages.isLock);
	            }

	            for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
	                args[_key] = arguments[_key];
	            }

	            var command = args[0];

	            if (isString(command)) {
	                command = _command2.default.create.apply(_command2.default, args);
	            }

	            return this._invokeExecution(command).then(function (value) {
	                _this3.clearRedoStack();

	                return value;
	            });
	        }

	        /**
	         * Undo command
	         * @returns {Promise}
	         */

	    }, {
	        key: 'undo',
	        value: function undo() {
	            var command = this._undoStack.pop();
	            var promise = void 0;
	            var message = '';

	            if (command && this._isLocked) {
	                this.pushUndoStack(command, true);
	                command = null;
	            }
	            if (command) {
	                if (this.isEmptyUndoStack()) {
	                    this._fireUndoStackChanged();
	                }
	                promise = this._invokeUndo(command);
	            } else {
	                message = rejectMessages.undo;
	                if (this._isLocked) {
	                    message = message + ' Because ' + rejectMessages.isLock;
	                }
	                promise = _promise2.default.reject(message);
	            }

	            return promise;
	        }

	        /**
	         * Redo command
	         * @returns {Promise}
	         */

	    }, {
	        key: 'redo',
	        value: function redo() {
	            var command = this._redoStack.pop();
	            var promise = void 0;
	            var message = '';

	            if (command && this._isLocked) {
	                this.pushRedoStack(command, true);
	                command = null;
	            }
	            if (command) {
	                if (this.isEmptyRedoStack()) {
	                    this._fireRedoStackChanged();
	                }
	                promise = this._invokeExecution(command);
	            } else {
	                message = rejectMessages.redo;
	                if (this._isLocked) {
	                    message = message + ' Because ' + rejectMessages.isLock;
	                }
	                promise = _promise2.default.reject(message);
	            }

	            return promise;
	        }

	        /**
	         * Push undo stack
	         * @param {Command} command - command
	         * @param {boolean} [isSilent] - Fire event or not
	         */

	    }, {
	        key: 'pushUndoStack',
	        value: function pushUndoStack(command, isSilent) {
	            this._undoStack.push(command);
	            if (!isSilent) {
	                this._fireUndoStackChanged();
	            }
	        }

	        /**
	         * Push redo stack
	         * @param {Command} command - command
	         * @param {boolean} [isSilent] - Fire event or not
	         */

	    }, {
	        key: 'pushRedoStack',
	        value: function pushRedoStack(command, isSilent) {
	            this._redoStack.push(command);
	            if (!isSilent) {
	                this._fireRedoStackChanged();
	            }
	        }

	        /**
	         * Return whether the redoStack is empty
	         * @returns {boolean}
	         */

	    }, {
	        key: 'isEmptyRedoStack',
	        value: function isEmptyRedoStack() {
	            return this._redoStack.length === 0;
	        }

	        /**
	         * Return whether the undoStack is empty
	         * @returns {boolean}
	         */

	    }, {
	        key: 'isEmptyUndoStack',
	        value: function isEmptyUndoStack() {
	            return this._undoStack.length === 0;
	        }

	        /**
	         * Clear undoStack
	         */

	    }, {
	        key: 'clearUndoStack',
	        value: function clearUndoStack() {
	            if (!this.isEmptyUndoStack()) {
	                this._undoStack = [];
	                this._fireUndoStackChanged();
	            }
	        }

	        /**
	         * Clear redoStack
	         */

	    }, {
	        key: 'clearRedoStack',
	        value: function clearRedoStack() {
	            if (!this.isEmptyRedoStack()) {
	                this._redoStack = [];
	                this._fireRedoStackChanged();
	            }
	        }
	    }]);

	    return Invoker;
	}();

	CustomEvents.mixin(Invoker);
	module.exports = Invoker;

/***/ }),
/* 69 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(70);

	var _command2 = _interopRequireDefault(_command);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var commands = {};

	/**
	 * Create a command
	 * @param {string} name - Command name
	 * @param {...*} args - Arguments for creating command
	 * @returns {Command}
	 * @ignore
	 */
	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Command factory
	 */
	function create(name) {
	    var actions = commands[name];
	    if (actions) {
	        for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
	            args[_key - 1] = arguments[_key];
	        }

	        return new _command2.default(actions, args);
	    }

	    return null;
	}

	/**
	 * Register a command with name as a key
	 * @param {Object} command - {name:{string}, execute: {function}, undo: {function}}
	 * @param {string} command.name - command name
	 * @param {function} command.execute - executable function
	 * @param {function} command.undo - undo function
	 * @ignore
	 */
	function register(command) {
	    commands[command.name] = command;
	}

	module.exports = {
	    create: create,
	    register: register
	};

/***/ }),
/* 70 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * @fileoverview Command interface
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      */


	var _errorMessage = __webpack_require__(71);

	var _errorMessage2 = _interopRequireDefault(_errorMessage);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	var createMessage = _errorMessage2.default.create;
	var errorTypes = _errorMessage2.default.types;

	/**
	 * Command class
	 * @class
	 * @param {{name:function, execute: function, undo: function,
	 *          executeCallback: function, undoCallback: function}} actions - Command actions
	 * @param {Array} args - passing arguments on execute, undo
	 * @ignore
	 */

	var Command = function () {
	  function Command(actions, args) {
	    _classCallCheck(this, Command);

	    /**
	     * command name
	     * @type {string}
	     */
	    this.name = actions.name;

	    /**
	     * arguments
	     * @type {Array}
	     */
	    this.args = args;

	    /**
	     * Execute function
	     * @type {function}
	     */
	    this.execute = actions.execute;

	    /**
	     * Undo function
	     * @type {function}
	     */
	    this.undo = actions.undo;

	    /**
	     * executeCallback
	     * @type {function}
	     */
	    this.executeCallback = actions.executeCallback || null;

	    /**
	     * undoCallback
	     * @type {function}
	     */
	    this.undoCallback = actions.undoCallback || null;

	    /**
	     * data for undo
	     * @type {Object}
	     */
	    this.undoData = {};
	  }

	  /**
	   * Execute action
	   * @param {Object.<string, Component>} compMap - Components injection
	   * @abstract
	   */


	  _createClass(Command, [{
	    key: 'execute',
	    value: function execute() {
	      throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'execute'));
	    }

	    /**
	     * Undo action
	     * @param {Object.<string, Component>} compMap - Components injection
	     * @abstract
	     */

	  }, {
	    key: 'undo',
	    value: function undo() {
	      throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'undo'));
	    }

	    /**
	     * Attach execute callabck
	     * @param {function} callback - Callback after execution
	     * @returns {Command} this
	     */

	  }, {
	    key: 'setExecuteCallback',
	    value: function setExecuteCallback(callback) {
	      this.executeCallback = callback;

	      return this;
	    }

	    /**
	     * Attach undo callback
	     * @param {function} callback - Callback after undo
	     * @returns {Command} this
	     */

	  }, {
	    key: 'setUndoCallback',
	    value: function setUndoCallback(callback) {
	      this.undoCallback = callback;

	      return this;
	    }
	  }]);

	  return Command;
	}();

	module.exports = Command;

/***/ }),
/* 71 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _util = __webpack_require__(72);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Error-message factory
	 */
	var types = (0, _util.keyMirror)('UN_IMPLEMENTATION', 'NO_COMPONENT_NAME');
	var messages = {
	    UN_IMPLEMENTATION: 'Should implement a method: ',
	    NO_COMPONENT_NAME: 'Should set a component name'
	};
	var map = {
	    UN_IMPLEMENTATION: function UN_IMPLEMENTATION(methodName) {
	        return messages.UN_IMPLEMENTATION + methodName;
	    },
	    NO_COMPONENT_NAME: function NO_COMPONENT_NAME() {
	        return messages.NO_COMPONENT_NAME;
	    }
	};

	module.exports = {
	    types: _tuiCodeSnippet2.default.extend({}, types),

	    create: function create(type) {
	        type = type.toLowerCase();
	        var func = map[type];

	        for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
	            args[_key - 1] = arguments[_key];
	        }

	        return func.apply(undefined, args);
	    }
	};

/***/ }),
/* 72 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _tuiCodeSnippet = __webpack_require__(3);

	var min = Math.min,
	    max = Math.max; /**
	                     * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                     * @fileoverview Util
	                     */

	var hostnameSent = false;

	module.exports = {
	    /**
	     * Clamp value
	     * @param {number} value - Value
	     * @param {number} minValue - Minimum value
	     * @param {number} maxValue - Maximum value
	     * @returns {number} clamped value
	     */
	    clamp: function clamp(value, minValue, maxValue) {
	        var temp = void 0;
	        if (minValue > maxValue) {
	            temp = minValue;
	            minValue = maxValue;
	            maxValue = temp;
	        }

	        return max(minValue, min(value, maxValue));
	    },


	    /**
	     * Make key-value object from arguments
	     * @returns {object.<string, string>}
	     */
	    keyMirror: function keyMirror() {
	        var obj = {};

	        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
	            args[_key] = arguments[_key];
	        }

	        (0, _tuiCodeSnippet.forEach)(args, function (key) {
	            obj[key] = key;
	        });

	        return obj;
	    },


	    /**
	     * Make CSSText
	     * @param {Object} styleObj - Style info object
	     * @returns {string} Connected string of style
	     */
	    makeStyleText: function makeStyleText(styleObj) {
	        var styleStr = '';

	        (0, _tuiCodeSnippet.forEach)(styleObj, function (value, prop) {
	            styleStr += prop + ': ' + value + ';';
	        });

	        return styleStr;
	    },


	    /**
	     * Get object's properties
	     * @param {Object} obj - object
	     * @param {Array} keys - keys
	     * @returns {Object} properties object
	     */
	    getProperties: function getProperties(obj, keys) {
	        var props = {};
	        var length = keys.length;

	        var i = 0;
	        var key = void 0;

	        for (i = 0; i < length; i += 1) {
	            key = keys[i];
	            props[key] = obj[key];
	        }

	        return props;
	    },


	    /**
	     * ParseInt simpliment
	     * @param {number} value - Value
	     * @returns {number}
	     */
	    toInteger: function toInteger(value) {
	        return parseInt(value, 10);
	    },


	    /**
	     * String to camelcase string
	     * @param {string} targetString - change target
	     * @returns {string}
	     * @private
	     */
	    toCamelCase: function toCamelCase(targetString) {
	        return targetString.replace(/-([a-z])/g, function ($0, $1) {
	            return $1.toUpperCase();
	        });
	    },


	    /**
	     * Check browser file api support
	     * @returns {boolean}
	     * @private
	     */
	    isSupportFileApi: function isSupportFileApi() {
	        return !!(window.File && window.FileList && window.FileReader);
	    },


	    /**
	     * hex to rgb
	     * @param {string} color - hex color
	     * @param {string} alpha - color alpha value
	     * @returns {string} rgb expression
	     */
	    getRgb: function getRgb(color, alpha) {
	        if (color.length === 4) {
	            color = '' + color + color.slice(1, 4);
	        }
	        var r = parseInt(color.slice(1, 3), 16);
	        var g = parseInt(color.slice(3, 5), 16);
	        var b = parseInt(color.slice(5, 7), 16);
	        var a = alpha || 1;

	        return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')';
	    },


	    /**
	     * send hostname
	     */
	    sendHostName: function sendHostName() {
	        var _location = location,
	            hostname = _location.hostname;

	        if (hostnameSent) {
	            return;
	        }
	        hostnameSent = true;

	        (0, _tuiCodeSnippet.imagePing)('https://www.google-analytics.com/collect', {
	            v: 1,
	            t: 'event',
	            tid: 'UA-115377265-9',
	            cid: hostname,
	            dp: hostname,
	            dh: 'image-editor'
	        });
	    },


	    /**
	     * Apply css resource
	     * @param {string} styleBuffer - serialized css text
	     * @param {string} tagId - style tag id
	     */
	    styleLoad: function styleLoad(styleBuffer, tagId) {
	        var _document$getElements = document.getElementsByTagName('head'),
	            head = _document$getElements[0];

	        var linkElement = document.createElement('link');
	        var styleData = encodeURIComponent(styleBuffer);
	        if (tagId) {
	            linkElement.id = tagId;
	            // linkElement.id = 'tui-image-editor-theme-style';
	        }
	        linkElement.setAttribute('rel', 'stylesheet');
	        linkElement.setAttribute('type', 'text/css');
	        linkElement.setAttribute('href', 'data:text/css;charset=UTF-8,' + styleData);
	        head.appendChild(linkElement);
	    },


	    /**
	     * Get selector
	     * @param {HTMLElement} targetElement - target element
	     * @returns {Function} selector
	     */
	    getSelector: function getSelector(targetElement) {
	        return function (str) {
	            return targetElement.querySelector(str);
	        };
	    },


	    /**
	     * Change base64 to blob
	     * @param {String} data - base64 string data
	     * @returns {Blob} Blob Data
	     */
	    base64ToBlob: function base64ToBlob(data) {
	        var rImageType = /data:(image\/.+);base64,/;
	        var mimeString = '';
	        var raw = void 0,
	            uInt8Array = void 0,
	            i = void 0;

	        raw = data.replace(rImageType, function (header, imageType) {
	            mimeString = imageType;

	            return '';
	        });

	        raw = atob(raw);
	        var rawLength = raw.length;
	        uInt8Array = new Uint8Array(rawLength); // eslint-disable-line

	        for (i = 0; i < rawLength; i += 1) {
	            uInt8Array[i] = raw.charCodeAt(i);
	        }

	        return new Blob([uInt8Array], { type: mimeString });
	    }
	};

/***/ }),
/* 73 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _util = __webpack_require__(72);

	var _util2 = _interopRequireDefault(_util);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	module.exports = {
	    /**
	     * Component names
	     * @type {Object.<string, string>}
	     */
	    componentNames: _util2.default.keyMirror('IMAGE_LOADER', 'CROPPER', 'FLIP', 'ROTATION', 'FREE_DRAWING', 'LINE', 'TEXT', 'ICON', 'FILTER', 'SHAPE'),

	    /**
	     * Command names
	     * @type {Object.<string, string>}
	     */
	    commandNames: {
	        'CLEAR_OBJECTS': 'clearObjects',
	        'LOAD_IMAGE': 'loadImage',
	        'FLIP_IMAGE': 'flip',
	        'ROTATE_IMAGE': 'rotate',
	        'ADD_OBJECT': 'addObject',
	        'REMOVE_OBJECT': 'removeObject',
	        'APPLY_FILTER': 'applyFilter',
	        'REMOVE_FILTER': 'removeFilter',
	        'ADD_ICON': 'addIcon',
	        'CHANGE_ICON_COLOR': 'changeIconColor',
	        'ADD_SHAPE': 'addShape',
	        'CHANGE_SHAPE': 'changeShape',
	        'ADD_TEXT': 'addText',
	        'CHANGE_TEXT': 'changeText',
	        'CHANGE_TEXT_STYLE': 'changeTextStyle',
	        'ADD_IMAGE_OBJECT': 'addImageObject',
	        'RESIZE_CANVAS_DIMENSION': 'resizeCanvasDimension',
	        'SET_OBJECT_PROPERTIES': 'setObjectProperties',
	        'SET_OBJECT_POSITION': 'setObjectPosition'
	    },

	    /**
	     * Event names
	     * @type {Object.<string, string>}
	     */
	    eventNames: {
	        OBJECT_ACTIVATED: 'objectActivated',
	        OBJECT_MOVED: 'objectMoved',
	        OBJECT_SCALED: 'objectScaled',
	        OBJECT_CREATED: 'objectCreated',
	        TEXT_EDITING: 'textEditing',
	        TEXT_CHANGED: 'textChanged',
	        ICON_CREATE_RESIZE: 'iconCreateResize',
	        ICON_CREATE_END: 'iconCreateEnd',
	        ADD_TEXT: 'addText',
	        ADD_OBJECT: 'addObject',
	        ADD_OBJECT_AFTER: 'addObjectAfter',
	        MOUSE_DOWN: 'mousedown',
	        MOUSE_UP: 'mouseup',
	        MOUSE_MOVE: 'mousemove',
	        // UNDO/REDO Events
	        REDO_STACK_CHANGED: 'redoStackChanged',
	        UNDO_STACK_CHANGED: 'undoStackChanged',
	        SELECTION_CLEARED: 'selectionCleared',
	        SELECTION_CREATED: 'selectionCreated'
	    },

	    /**
	     * Editor states
	     * @type {Object.<string, string>}
	     */
	    drawingModes: _util2.default.keyMirror('NORMAL', 'CROPPER', 'FREE_DRAWING', 'LINE_DRAWING', 'TEXT', 'SHAPE'),

	    /**
	     * Shortcut key values
	     * @type {Object.<string, number>}
	     */
	    keyCodes: {
	        Z: 90,
	        Y: 89,
	        SHIFT: 16,
	        BACKSPACE: 8,
	        DEL: 46
	    },

	    /**
	     * Fabric object options
	     * @type {Object.<string, Object>}
	     */
	    fObjectOptions: {
	        SELECTION_STYLE: {
	            borderColor: 'red',
	            cornerColor: 'green',
	            cornerSize: 10,
	            originX: 'center',
	            originY: 'center',
	            transparentCorners: false
	        }
	    },

	    /**
	     * Promise reject messages
	     * @type {Object.<string, string>}
	     */
	    rejectMessages: {
	        flip: 'The flipX and flipY setting values are not changed.',
	        rotation: 'The current angle is same the old angle.',
	        loadImage: 'The background image is empty.',
	        isLock: 'The executing command state is locked.',
	        undo: 'The promise of undo command is reject.',
	        redo: 'The promise of redo command is reject.',
	        invalidDrawingMode: 'This operation is not supported in the drawing mode',
	        invalidParameters: 'Invalid parameters',
	        noActiveObject: 'There is no active object.',
	        unsupportedType: 'Unsupported object type',
	        noObject: 'The object is not in canvas.',
	        addedObject: 'The object is already added.'
	    },

	    /**
	     * Default icon menu svg path
	     * @type {Object.<string, string>}
	     */
	    defaultIconPath: {
	        'icon-arrow': 'M40 12V0l24 24-24 24V36H0V12h40z',
	        'icon-arrow-2': 'M49,32 H3 V22 h46 l-18,-18 h12 l23,23 L43,50 h-12 l18,-18  z ',
	        'icon-arrow-3': 'M43.349998,27 L17.354,53 H1.949999 l25.996,-26 L1.949999,1 h15.404 L43.349998,27  z ',
	        'icon-star': 'M35,54.557999 l-19.912001,10.468 l3.804,-22.172001 l-16.108,-15.7 l22.26,-3.236 L35,3.746 l9.956,20.172001 l22.26,3.236 l-16.108,15.7 l3.804,22.172001  z ',
	        'icon-star-2': 'M17,31.212 l-7.194,4.08 l-4.728,-6.83 l-8.234,0.524 l-1.328,-8.226 l-7.644,-3.14 l2.338,-7.992 l-5.54,-6.18 l5.54,-6.176 l-2.338,-7.994 l7.644,-3.138 l1.328,-8.226 l8.234,0.522 l4.728,-6.83 L17,-24.312 l7.194,-4.08 l4.728,6.83 l8.234,-0.522 l1.328,8.226 l7.644,3.14 l-2.338,7.992 l5.54,6.178 l-5.54,6.178 l2.338,7.992 l-7.644,3.14 l-1.328,8.226 l-8.234,-0.524 l-4.728,6.83  z ',
	        'icon-polygon': 'M3,31 L19,3 h32 l16,28 l-16,28 H19  z ',
	        'icon-location': 'M24 62C8 45.503 0 32.837 0 24 0 10.745 10.745 0 24 0s24 10.745 24 24c0 8.837-8 21.503-24 38zm0-28c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10z',
	        'icon-heart': 'M49.994999,91.349998 l-6.96,-6.333 C18.324001,62.606995 2.01,47.829002 2.01,29.690998 C2.01,14.912998 13.619999,3.299999 28.401001,3.299999 c8.349,0 16.362,5.859 21.594,12 c5.229,-6.141 13.242001,-12 21.591,-12 c14.778,0 26.390999,11.61 26.390999,26.390999 c0,18.138 -16.314001,32.916 -41.025002,55.374001 l-6.96,6.285  z ',
	        'icon-bubble': 'M44 48L34 58V48H12C5.373 48 0 42.627 0 36V12C0 5.373 5.373 0 12 0h40c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12h-8z'
	    },

	    defaultRotateRangeValus: {
	        realTimeEvent: true,
	        min: -360,
	        max: 360,
	        value: 0
	    },

	    defaultDrawRangeValus: {
	        min: 5,
	        max: 30,
	        value: 12
	    },

	    defaultShapeStrokeValus: {
	        realTimeEvent: false,
	        min: 2,
	        max: 300,
	        value: 3
	    },

	    defaultTextRangeValus: {
	        realTimeEvent: true,
	        min: 10,
	        max: 100,
	        value: 50
	    },

	    defaultFilterRangeValus: {
	        tintOpacityRange: {
	            min: 0,
	            max: 1,
	            value: 0.7
	        },
	        removewhiteThresholdRange: {
	            min: 0,
	            max: 255,
	            value: 60
	        },
	        removewhiteDistanceRange: {
	            min: 0,
	            max: 255,
	            value: 10
	        },
	        gradientTransparencyRange: {
	            min: 0,
	            max: 255,
	            value: 100
	        },
	        brightnessRange: {
	            min: -255,
	            max: 255,
	            value: 100
	        },
	        noiseRange: {
	            min: 0,
	            max: 1000,
	            value: 100
	        },
	        pixelateRange: {
	            min: 2,
	            max: 20,
	            value: 4
	        },
	        colorfilterThresholeRange: {
	            min: 0,
	            max: 255,
	            value: 45
	        }
	    }
	}; /**
	    * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	    * @fileoverview Constants
	    */

/***/ }),
/* 74 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _util = __webpack_require__(72);

	var _util2 = _interopRequireDefault(_util);

	var _mainContainer = __webpack_require__(75);

	var _mainContainer2 = _interopRequireDefault(_mainContainer);

	var _controls = __webpack_require__(76);

	var _controls2 = _interopRequireDefault(_controls);

	var _theme = __webpack_require__(77);

	var _theme2 = _interopRequireDefault(_theme);

	var _shape = __webpack_require__(80);

	var _shape2 = _interopRequireDefault(_shape);

	var _crop = __webpack_require__(86);

	var _crop2 = _interopRequireDefault(_crop);

	var _flip = __webpack_require__(88);

	var _flip2 = _interopRequireDefault(_flip);

	var _rotate = __webpack_require__(90);

	var _rotate2 = _interopRequireDefault(_rotate);

	var _text = __webpack_require__(92);

	var _text2 = _interopRequireDefault(_text);

	var _mask = __webpack_require__(94);

	var _mask2 = _interopRequireDefault(_mask);

	var _icon = __webpack_require__(96);

	var _icon2 = _interopRequireDefault(_icon);

	var _draw = __webpack_require__(98);

	var _draw2 = _interopRequireDefault(_draw);

	var _filter = __webpack_require__(100);

	var _filter2 = _interopRequireDefault(_filter);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	var SUB_UI_COMPONENT = {
	    Shape: _shape2.default,
	    Crop: _crop2.default,
	    Flip: _flip2.default,
	    Rotate: _rotate2.default,
	    Text: _text2.default,
	    Mask: _mask2.default,
	    Icon: _icon2.default,
	    Draw: _draw2.default,
	    Filter: _filter2.default
	};

	var BI_EXPRESSION_MINSIZE_WHEN_TOP_POSITION = '1300';

	/**
	 * Ui class
	 * @class
	 * @param {string|jQuery|HTMLElement} element - Wrapper's element or selector
	 * @param {Object} [options] - Ui setting options
	 *   @param {number} option.loadImage - Init default load image
	 *   @param {number} option.initMenu - Init start menu
	 *   @param {Boolean} [option.menuBarPosition=bottom] - Let
	 *   @param {Boolean} [option.applyCropSelectionStyle=false] - Let
	 * @param {Objecdt} actions - ui action instance
	 * @ignore
	 */

	var Ui = function () {
	    function Ui(element, options, actions) {
	        _classCallCheck(this, Ui);

	        this.options = this._initializeOption(options);

	        this._actions = actions;
	        this.submenu = false;
	        this.imageSize = {};
	        this.uiSize = {};
	        this.theme = new _theme2.default(this.options.theme);

	        this._submenuChangeTransection = false;
	        this._selectedElement = null;
	        this._mainElement = null;
	        this._editorElementWrap = null;
	        this._editorElement = null;
	        this._menuElement = null;
	        this._subMenuElement = null;
	        this._makeUiElement(element);
	        this._setUiSize();

	        this._els = {
	            'undo': this._menuElement.querySelector('#tie-btn-undo'),
	            'redo': this._menuElement.querySelector('#tie-btn-redo'),
	            'reset': this._menuElement.querySelector('#tie-btn-reset'),
	            'delete': this._menuElement.querySelector('#tie-btn-delete'),
	            'deleteAll': this._menuElement.querySelector('#tie-btn-delete-all'),
	            'download': this._selectedElement.querySelectorAll('.tui-image-editor-download-btn'),
	            'load': this._selectedElement.querySelectorAll('.tui-image-editor-load-btn')
	        };

	        this._makeSubMenu();
	    }

	    /**
	     * Set Default Selection for includeUI
	     * @param {Object} option - imageEditor options
	     * @returns {Object} - extends selectionStyle option
	     */


	    _createClass(Ui, [{
	        key: 'setUiDefaultSelectionStyle',
	        value: function setUiDefaultSelectionStyle(option) {
	            return _tuiCodeSnippet2.default.extend({
	                applyCropSelectionStyle: true,
	                applyGroupSelectionStyle: true,
	                selectionStyle: {
	                    cornerStyle: 'circle',
	                    cornerSize: 20,
	                    cornerColor: '#fff',
	                    cornerStrokeColor: '#000',
	                    transparentCorners: false,
	                    lineWidth: 2,
	                    borderColor: '#fff'
	                }
	            }, option);
	        }

	        /**
	         * Change editor size
	         * @param {Object} resizeInfo - ui & image size info
	         *   @param {Object} resizeInfo.uiSize - image size dimension
	         *     @param {Number} resizeInfo.uiSize.width - ui width
	         *     @param {Number} resizeInfo.uiSize.height - ui height
	         *   @param {Object} resizeInfo.imageSize - image size dimension
	         *     @param {Number} resizeInfo.imageSize.oldWidth - old width
	         *     @param {Number} resizeInfo.imageSize.oldHeight - old height
	         *     @param {Number} resizeInfo.imageSize.newWidth - new width
	         *     @param {Number} resizeInfo.imageSize.newHeight - new height
	         */

	    }, {
	        key: 'resizeEditor',
	        value: function resizeEditor() {
	            var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
	                uiSize = _ref.uiSize,
	                _ref$imageSize = _ref.imageSize,
	                imageSize = _ref$imageSize === undefined ? this.imageSize : _ref$imageSize;

	            if (imageSize !== this.imageSize) {
	                this.imageSize = imageSize;
	            }
	            if (uiSize) {
	                this._setUiSize(uiSize);
	            }

	            var _getEditorDimension2 = this._getEditorDimension(),
	                width = _getEditorDimension2.width,
	                height = _getEditorDimension2.height;

	            var editorElementStyle = this._editorElement.style;
	            var menuBarPosition = this.options.menuBarPosition;


	            editorElementStyle.height = height + 'px';
	            editorElementStyle.width = width + 'px';

	            var _getEditorPosition2 = this._getEditorPosition(menuBarPosition),
	                top = _getEditorPosition2.top,
	                bottom = _getEditorPosition2.bottom,
	                left = _getEditorPosition2.left,
	                right = _getEditorPosition2.right;

	            this._editorElementWrap.style.bottom = bottom + 'px';
	            this._editorElementWrap.style.top = top + 'px';
	            this._editorElementWrap.style.left = left + 'px';
	            this._editorElementWrap.style.width = 'calc(100% - ' + right + 'px)';
	            var selectElementClassList = this._selectedElement.classList;

	            if (menuBarPosition === 'top' && this._selectedElement.offsetWidth < BI_EXPRESSION_MINSIZE_WHEN_TOP_POSITION) {
	                selectElementClassList.add('tui-image-editor-top-optimization');
	            } else {
	                selectElementClassList.remove('tui-image-editor-top-optimization');
	            }
	        }

	        /**
	         * Change undo button status
	         * @param {Boolean} enableStatus - enabled status
	         */

	    }, {
	        key: 'changeUndoButtonStatus',
	        value: function changeUndoButtonStatus(enableStatus) {
	            if (enableStatus) {
	                this._els.undo.classList.add('enabled');
	            } else {
	                this._els.undo.classList.remove('enabled');
	            }
	        }

	        /**
	         * Change redo button status
	         * @param {Boolean} enableStatus - enabled status
	         */

	    }, {
	        key: 'changeRedoButtonStatus',
	        value: function changeRedoButtonStatus(enableStatus) {
	            if (enableStatus) {
	                this._els.redo.classList.add('enabled');
	            } else {
	                this._els.redo.classList.remove('enabled');
	            }
	        }

	        /**
	         * Change reset button status
	         * @param {Boolean} enableStatus - enabled status
	         */

	    }, {
	        key: 'changeResetButtonStatus',
	        value: function changeResetButtonStatus(enableStatus) {
	            if (enableStatus) {
	                this._els.reset.classList.add('enabled');
	            } else {
	                this._els.reset.classList.remove('enabled');
	            }
	        }

	        /**
	         * Change delete-all button status
	         * @param {Boolean} enableStatus - enabled status
	         */

	    }, {
	        key: 'changeDeleteAllButtonEnabled',
	        value: function changeDeleteAllButtonEnabled(enableStatus) {
	            if (enableStatus) {
	                this._els.deleteAll.classList.add('enabled');
	            } else {
	                this._els.deleteAll.classList.remove('enabled');
	            }
	        }

	        /**
	         * Change delete button status
	         * @param {Boolean} enableStatus - enabled status
	         */

	    }, {
	        key: 'changeDeleteButtonEnabled',
	        value: function changeDeleteButtonEnabled(enableStatus) {
	            if (enableStatus) {
	                this._els['delete'].classList.add('enabled');
	            } else {
	                this._els['delete'].classList.remove('enabled');
	            }
	        }

	        /**
	         * Change delete button status
	         * @param {Object} [options] - Ui setting options
	         *   @param {number} option.loadImage - Init default load image
	         *   @param {number} option.initMenu - Init start menu
	         *   @param {Boolean} [option.menuBarPosition=bottom] - Let
	         *   @param {Boolean} [option.applyCropSelectionStyle=false] - Let
	         * @returns {Object} initialize option
	         * @private
	         */

	    }, {
	        key: '_initializeOption',
	        value: function _initializeOption(options) {
	            return _tuiCodeSnippet2.default.extend({
	                loadImage: {
	                    path: '',
	                    name: ''
	                },
	                menuIconPath: '',
	                menu: ['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'],
	                initMenu: false,
	                uiSize: {
	                    width: '100%',
	                    height: '100%'
	                },
	                menuBarPosition: 'bottom'
	            }, options);
	        }

	        /**
	         * Set ui container size
	         * @param {Object} uiSize - ui dimension
	         *   @param {number} width - width
	         *   @param {number} height - height
	         * @private
	         */

	    }, {
	        key: '_setUiSize',
	        value: function _setUiSize() {
	            var uiSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.uiSize;

	            var elementDimension = this._selectedElement.style;
	            elementDimension.width = uiSize.width;
	            elementDimension.height = uiSize.height;
	        }

	        /**
	         * Make submenu dom element
	         * @private
	         */

	    }, {
	        key: '_makeSubMenu',
	        value: function _makeSubMenu() {
	            var _this = this;

	            _tuiCodeSnippet2.default.forEach(this.options.menu, function (menuName) {
	                var SubComponentClass = SUB_UI_COMPONENT[menuName.replace(/^[a-z]/, function ($0) {
	                    return $0.toUpperCase();
	                })];

	                // make menu element
	                _this._makeMenuElement(menuName);

	                // menu btn element
	                _this._els[menuName] = _this._menuElement.querySelector('#tie-btn-' + menuName);

	                // submenu ui instance
	                _this[menuName] = new SubComponentClass(_this._subMenuElement, {
	                    iconStyle: _this.theme.getStyle('submenu.icon'),
	                    menuBarPosition: _this.options.menuBarPosition
	                });
	            });
	        }

	        /**
	         * Make primary ui dom element
	         * @param {string|jQuery|HTMLElement} element - Wrapper's element or selector
	         * @private
	         */

	    }, {
	        key: '_makeUiElement',
	        value: function _makeUiElement(element) {
	            var selectedElement = void 0;

	            window.snippet = _tuiCodeSnippet2.default;

	            if (element.jquery) {
	                selectedElement = element[0];
	            } else if (element.nodeType) {
	                selectedElement = element;
	            } else {
	                selectedElement = document.querySelector(element);
	            }
	            var selector = _util2.default.getSelector(selectedElement);

	            selectedElement.classList.add('tui-image-editor-container');
	            selectedElement.innerHTML = (0, _controls2.default)({
	                biImage: this.theme.getStyle('common.bi'),
	                iconStyle: this.theme.getStyle('menu.icon'),
	                loadButtonStyle: this.theme.getStyle('loadButton'),
	                downloadButtonStyle: this.theme.getStyle('downloadButton')
	            }) + (0, _mainContainer2.default)({
	                biImage: this.theme.getStyle('common.bi'),
	                commonStyle: this.theme.getStyle('common'),
	                headerStyle: this.theme.getStyle('header'),
	                loadButtonStyle: this.theme.getStyle('loadButton'),
	                downloadButtonStyle: this.theme.getStyle('downloadButton'),
	                submenuStyle: this.theme.getStyle('submenu')
	            });

	            this._selectedElement = selectedElement;
	            this._selectedElement.classList.add(this.options.menuBarPosition);

	            this._mainElement = selector('.tui-image-editor-main');
	            this._editorElementWrap = selector('.tui-image-editor-wrap');
	            this._editorElement = selector('.tui-image-editor');
	            this._menuElement = selector('.tui-image-editor-menu');
	            this._subMenuElement = selector('.tui-image-editor-submenu');
	        }

	        /**
	         * Make menu ui dom element
	         * @param {string} menuName - menu name
	         * @private
	         */

	    }, {
	        key: '_makeMenuElement',
	        value: function _makeMenuElement(menuName) {
	            var btnElement = document.createElement('li');

	            var _theme$getStyle = this.theme.getStyle('menu.icon'),
	                normal = _theme$getStyle.normal,
	                active = _theme$getStyle.active;

	            var menuItemHtml = '\n            <svg class="svg_ic-menu">\n                <use xlink:href="' + normal.path + '#' + normal.name + '-ic-' + menuName + '" class="normal"/>\n                <use xlink:href="' + active.path + '#' + active.name + '-ic-' + menuName + '" class="active"/>\n            </svg>\n        ';

	            btnElement.id = 'tie-btn-' + menuName;
	            btnElement.className = 'tui-image-editor-item';
	            btnElement.title = menuName;
	            btnElement.innerHTML = menuItemHtml;

	            this._menuElement.appendChild(btnElement);
	        }

	        /**
	         * Add help action event
	         * @param {string} helpName - help menu name
	         * @private
	         */

	    }, {
	        key: '_addHelpActionEvent',
	        value: function _addHelpActionEvent(helpName) {
	            var _this2 = this;

	            this._els[helpName].addEventListener('click', function () {
	                _this2._actions.main[helpName]();
	            });
	        }

	        /**
	         * Add download event
	         * @private
	         */

	    }, {
	        key: '_addDownloadEvent',
	        value: function _addDownloadEvent() {
	            var _this3 = this;

	            _tuiCodeSnippet2.default.forEach(this._els.download, function (element) {
	                element.addEventListener('click', function () {
	                    _this3._actions.main.download();
	                });
	            });
	        }

	        /**
	         * Add load event
	         * @private
	         */

	    }, {
	        key: '_addLoadEvent',
	        value: function _addLoadEvent() {
	            var _this4 = this;

	            _tuiCodeSnippet2.default.forEach(this._els.load, function (element) {
	                element.addEventListener('change', function (event) {
	                    _this4._actions.main.load(event.target.files[0]);
	                });
	            });
	        }

	        /**
	         * Add menu event
	         * @param {string} menuName - menu name
	         * @private
	         */

	    }, {
	        key: '_addMenuEvent',
	        value: function _addMenuEvent(menuName) {
	            var _this5 = this;

	            this._els[menuName].addEventListener('click', function () {
	                _this5.changeMenu(menuName);
	            });
	        }

	        /**
	         * Add menu event
	         * @param {string} menuName - menu name
	         * @private
	         */

	    }, {
	        key: '_addSubMenuEvent',
	        value: function _addSubMenuEvent(menuName) {
	            this[menuName].addEvent(this._actions[menuName]);
	        }

	        /**
	         * get editor area element
	         * @returns {HTMLElement} editor area html element
	         */

	    }, {
	        key: 'getEditorArea',
	        value: function getEditorArea() {
	            return this._editorElement;
	        }

	        /**
	         * Init canvas
	         */

	    }, {
	        key: 'initCanvas',
	        value: function initCanvas() {
	            var _this6 = this;

	            var loadImageInfo = this._getLoadImage();
	            if (loadImageInfo) {
	                this._actions.main.initLoadImage(loadImageInfo.path, loadImageInfo.name).then(function () {
	                    _this6._addHelpActionEvent('undo');
	                    _this6._addHelpActionEvent('redo');
	                    _this6._addHelpActionEvent('reset');
	                    _this6._addHelpActionEvent('delete');
	                    _this6._addHelpActionEvent('deleteAll');

	                    _this6._addDownloadEvent();
	                    _this6._addLoadEvent();

	                    _tuiCodeSnippet2.default.forEach(_this6.options.menu, function (menuName) {
	                        _this6._addMenuEvent(menuName);
	                        _this6._addSubMenuEvent(menuName);
	                    });
	                    _this6._initMenu();
	                });
	            }

	            var gridVisual = document.createElement('div');
	            gridVisual.className = 'tui-image-editor-grid-visual';
	            var grid = '<table>\n           <tr><td class="dot left-top"></td><td></td><td class="dot right-top"></td></tr>\n           <tr><td></td><td></td><td></td></tr>\n           <tr><td class="dot left-bottom"></td><td></td><td class="dot right-bottom"></td></tr>\n         </table>';
	            gridVisual.innerHTML = grid;
	            this._editorContainerElement = this._editorElement.querySelector('.tui-image-editor-canvas-container');
	            this._editorContainerElement.appendChild(gridVisual);
	        }

	        /**
	         * get editor area element
	         * @returns {Object} loadimage optionk
	         * @private
	         */

	    }, {
	        key: '_getLoadImage',
	        value: function _getLoadImage() {
	            return this.options.loadImage;
	        }

	        /**
	         * change menu
	         * @param {string} menuName - menu name
	         * @param {boolean} toggle - whether toogle or not
	         * @param {boolean} discardSelection - discard selection
	         */

	    }, {
	        key: 'changeMenu',
	        value: function changeMenu(menuName) {
	            var toggle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
	            var discardSelection = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;

	            if (!this._submenuChangeTransection) {
	                this._submenuChangeTransection = true;
	                this._changeMenu(menuName, toggle, discardSelection);
	                this._submenuChangeTransection = false;
	            }
	        }

	        /**
	         * change menu
	         * @param {string} menuName - menu name
	         * @param {boolean} toggle - whether toogle or not
	         * @param {boolean} discardSelection - discard selection
	         * @private
	         */

	    }, {
	        key: '_changeMenu',
	        value: function _changeMenu(menuName, toggle, discardSelection) {
	            if (this.submenu) {
	                this._els[this.submenu].classList.remove('active');
	                this._mainElement.classList.remove('tui-image-editor-menu-' + this.submenu);
	                if (discardSelection) {
	                    this._actions.main.discardSelection();
	                }
	                this._actions.main.changeSelectableAll(true);
	                this[this.submenu].changeStandbyMode();
	            }

	            if (this.submenu === menuName && toggle) {
	                this.submenu = null;
	            } else {
	                this._els[menuName].classList.add('active');
	                this._mainElement.classList.add('tui-image-editor-menu-' + menuName);
	                this.submenu = menuName;
	                this[this.submenu].changeStartMode();
	            }

	            this.resizeEditor();
	        }

	        /**
	         * Init menu
	         * @private
	         */

	    }, {
	        key: '_initMenu',
	        value: function _initMenu() {
	            if (this.options.initMenu) {
	                var evt = document.createEvent('MouseEvents');
	                evt.initEvent('click', true, false);
	                this._els[this.options.initMenu].dispatchEvent(evt);
	                if (this.icon) {
	                    this.icon.registDefaultIcon();
	                }
	            }
	        }

	        /**
	         * Get editor dimension
	         * @returns {Object} - width & height of editor
	         * @private
	         */

	    }, {
	        key: '_getEditorDimension',
	        value: function _getEditorDimension() {
	            var maxHeight = parseFloat(this._editorContainerElement.style.maxHeight);
	            var height = this.imageSize.newHeight > maxHeight ? maxHeight : this.imageSize.newHeight;

	            var maxWidth = parseFloat(this._editorContainerElement.style.maxWidth);
	            var width = this.imageSize.newWidth > maxWidth ? maxWidth : this.imageSize.newWidth;

	            return {
	                width: width,
	                height: height
	            };
	        }

	        /**
	         * Get editor position
	         * @param {string} menuBarPosition - top or right or bottom or left
	         * @returns {Object} - positions (top, right, bottom, left)
	         * @private
	         */

	    }, {
	        key: '_getEditorPosition',
	        value: function _getEditorPosition(menuBarPosition) {
	            var bottom = 0;
	            var top = 0;
	            var left = 0;
	            var right = 0;

	            if (this.submenu) {
	                switch (menuBarPosition) {
	                    case 'bottom':
	                        bottom += 150;
	                        break;
	                    case 'top':
	                        top += 150;
	                        break;
	                    case 'left':
	                        left += 248;
	                        right += 248;
	                        break;
	                    case 'right':
	                        right += 248;
	                        break;
	                    default:
	                        break;
	                }
	            }

	            return {
	                top: top,
	                bottom: bottom,
	                left: left,
	                right: right
	            };
	        }
	    }]);

	    return Ui;
	}();

	exports.default = Ui;

/***/ }),
/* 75 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var biImage = _ref.biImage,
	        commonStyle = _ref.commonStyle,
	        headerStyle = _ref.headerStyle,
	        loadButtonStyle = _ref.loadButtonStyle,
	        downloadButtonStyle = _ref.downloadButtonStyle,
	        submenuStyle = _ref.submenuStyle;
	    return "\n    <div class=\"tui-image-editor-main-container\" style=\"" + commonStyle + "\">\n        <div class=\"tui-image-editor-header\" style=\"" + headerStyle + "\">\n            <div class=\"tui-image-editor-header-logo\">\n                <img src=\"" + biImage + "\" />\n            </div>\n            <div class=\"tui-image-editor-header-buttons\">\n                <button style=\"" + loadButtonStyle + "\">\n                    Load\n                    <input type=\"file\" class=\"tui-image-editor-load-btn\" />\n                </button>\n                <button class=\"tui-image-editor-download-btn\" style=\"" + downloadButtonStyle + "\">\n                    Download\n                </button>\n            </div>\n        </div>\n        <div class=\"tui-image-editor-main\">\n            <div class=\"tui-image-editor-submenu\" style=\"" + submenuStyle + "\">\n            </div>\n            <div class=\"tui-image-editor-wrap\">\n                <div class=\"tui-image-editor-size-wrap\">\n                    <div class=\"tui-image-editor-align-wrap\">\n                        <div class=\"tui-image-editor\"></div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n";
	};

/***/ }),
/* 76 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var biImage = _ref.biImage,
	        _ref$iconStyle = _ref.iconStyle,
	        normal = _ref$iconStyle.normal,
	        active = _ref$iconStyle.active,
	        loadButtonStyle = _ref.loadButtonStyle,
	        downloadButtonStyle = _ref.downloadButtonStyle;
	    return "\n    <div class=\"tui-image-editor-controls\">\n        <div class=\"tui-image-editor-controls-logo\">\n            <img src=\"" + biImage + "\" />\n        </div>\n        <ul class=\"tui-image-editor-menu\">\n            <li id=\"tie-btn-undo\" class=\"tui-image-editor-item\" title=\"undo\">\n                <svg class=\"svg_ic-menu\">\n                    <use xlink:href=\"" + active.path + "#" + active.name + "-ic-undo\" class=\"enabled\"/>\n                    <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-undo\" class=\"normal\"/>\n                </svg>\n            </li>\n            <li id=\"tie-btn-redo\" class=\"tui-image-editor-item\" title=\"redo\">\n                <svg class=\"svg_ic-menu\">\n                    <use xlink:href=\"" + active.path + "#" + active.name + "-ic-redo\" class=\"enabled\"/>\n                    <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-redo\" class=\"normal\"/>\n                </svg>\n            </li>\n            <li id=\"tie-btn-reset\" class=\"tui-image-editor-item\" title=\"reset\">\n                <svg class=\"svg_ic-menu\">\n                    <use xlink:href=\"" + active.path + "#" + active.name + "-ic-reset\" class=\"enabled\"/>\n                    <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-reset\" class=\"normal\"/>\n                </svg>\n            </li>\n            <li class=\"tui-image-editor-item\">\n                <div class=\"tui-image-editor-icpartition\"></div>\n            </li>\n            <li id=\"tie-btn-delete\" class=\"tui-image-editor-item\" title=\"delete\">\n                <svg class=\"svg_ic-menu\">\n                    <use xlink:href=\"" + active.path + "#" + active.name + "-ic-delete\" class=\"enabled\"/>\n                    <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-delete\" class=\"normal\"/>\n                </svg>\n            </li>\n            <li id=\"tie-btn-delete-all\" class=\"tui-image-editor-item\" title=\"delete-all\">\n                <svg class=\"svg_ic-menu\">\n                    <use xlink:href=\"" + active.path + "#" + active.name + "-ic-delete-all\" class=\"enabled\"/>\n                    <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-delete-all\" class=\"normal\"/>\n                </svg>\n            </li>\n            <li class=\"tui-image-editor-item\">\n                <div class=\"tui-image-editor-icpartition\"></div>\n            </li>\n        </ul>\n\n        <div class=\"tui-image-editor-controls-buttons\">\n            <button style=\"" + loadButtonStyle + "\">\n                Load\n                <input type=\"file\" class=\"tui-image-editor-load-btn\" />\n            </button>\n            <button class=\"tui-image-editor-download-btn\" style=\"" + downloadButtonStyle + "\">\n                Download\n            </button>\n        </div>\n    </div>\n";
	};

/***/ }),
/* 77 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _tuiCodeSnippet = __webpack_require__(3);

	var _util = __webpack_require__(72);

	var _style = __webpack_require__(78);

	var _style2 = _interopRequireDefault(_style);

	var _standard = __webpack_require__(79);

	var _standard2 = _interopRequireDefault(_standard);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	/**
	 * Theme manager
	 * @class
	 * @param {Object} customTheme - custom theme
	 * @ignore
	 */
	var Theme = function () {
	    function Theme(customTheme) {
	        _classCallCheck(this, Theme);

	        this.styles = this._changeToObject((0, _tuiCodeSnippet.extend)(_standard2.default, customTheme));
	        (0, _util.styleLoad)(this._styleMaker());
	    }

	    /**
	     * Get a Style cssText or StyleObject
	     * @param {string} type - style type
	     * @returns {string|object} - cssText or StyleObject
	     */


	    _createClass(Theme, [{
	        key: 'getStyle',
	        value: function getStyle(type) {
	            // eslint-disable-line
	            var result = null;
	            var firstProperty = type.replace(/\..+$/, '');
	            var option = this.styles[type];
	            switch (type) {
	                case 'common.bi':
	                    result = this.styles[type].image;
	                    break;
	                case 'menu.icon':
	                case 'submenu.icon':
	                    result = {
	                        active: this.styles[firstProperty + '.activeIcon'],
	                        normal: this.styles[firstProperty + '.normalIcon']
	                    };
	                    break;
	                case 'submenu.label':
	                    result = {
	                        active: this._makeCssText(this.styles[firstProperty + '.activeLabel']),
	                        normal: this._makeCssText(this.styles[firstProperty + '.normalLabel'])
	                    };
	                    break;
	                case 'submenu.partition':
	                    result = {
	                        vertical: this._makeCssText((0, _tuiCodeSnippet.extend)({}, option, { borderLeft: '1px solid ' + option.color })),
	                        horizontal: this._makeCssText((0, _tuiCodeSnippet.extend)({}, option, { borderBottom: '1px solid ' + option.color }))
	                    };
	                    break;
	                case 'range.pointer':
	                case 'range.bar':
	                case 'range.subbar':
	                    option.backgroundColor = option.color;
	                    result = this._makeCssText(option);
	                    break;
	                default:
	                    result = this._makeCssText(option);
	                    break;
	            }

	            return result;
	        }

	        /**
	         * Make css resource
	         * @returns {string} - serialized css text
	         * @private
	         */

	    }, {
	        key: '_styleMaker',
	        value: function _styleMaker() {
	            var submenuLabelStyle = this.getStyle('submenu.label');
	            var submenuPartitionStyle = this.getStyle('submenu.partition');

	            return (0, _style2.default)({
	                subMenuLabelActive: submenuLabelStyle.active,
	                subMenuLabelNormal: submenuLabelStyle.normal,
	                submenuPartitionVertical: submenuPartitionStyle.vertical,
	                submenuPartitionHorizontal: submenuPartitionStyle.horizontal,
	                biSize: this.getStyle('common.bisize'),
	                subMenuRangeTitle: this.getStyle('range.title'),
	                submenuRangePointer: this.getStyle('range.pointer'),
	                submenuRangeBar: this.getStyle('range.bar'),
	                submenuRangeSubbar: this.getStyle('range.subbar'),
	                submenuRangeValue: this.getStyle('range.value'),
	                submenuColorpickerTitle: this.getStyle('colorpicker.title'),
	                submenuColorpickerButton: this.getStyle('colorpicker.button'),
	                submenuCheckbox: this.getStyle('checkbox'),
	                menuIconSize: this.getStyle('menu.iconSize'),
	                submenuIconSize: this.getStyle('submenu.iconSize')
	            });
	        }

	        /**
	         * Change to low dimensional object.
	         * @param {object} styleOptions - style object of user interface
	         * @returns {object} low level object for style apply
	         * @private
	         */

	    }, {
	        key: '_changeToObject',
	        value: function _changeToObject(styleOptions) {
	            var styleObject = {};
	            (0, _tuiCodeSnippet.forEach)(styleOptions, function (value, key) {
	                var keyExplode = key.match(/^(.+)\.([a-z]+)$/i);
	                var property = keyExplode[1],
	                    subProperty = keyExplode[2];


	                if (!styleObject[property]) {
	                    styleObject[property] = {};
	                }
	                styleObject[property][subProperty] = value;
	            });

	            return styleObject;
	        }

	        /**
	         * Style object to Csstext serialize
	         * @param {object} styleObject - style object
	         * @returns {string} - css text string
	         * @private
	         */

	    }, {
	        key: '_makeCssText',
	        value: function _makeCssText(styleObject) {
	            var _this = this;

	            var converterStack = [];

	            (0, _tuiCodeSnippet.forEach)(styleObject, function (value, key) {
	                if (['backgroundImage'].indexOf(key) > -1 && value !== 'none') {
	                    value = 'url(' + value + ')';
	                }
	                converterStack.push(_this._toUnderScore(key) + ': ' + value);
	            });

	            return converterStack.join(';');
	        }

	        /**
	         * Camel key string to Underscore string
	         * @param {string} targetString - change target
	         * @returns {string}
	         * @private
	         */

	    }, {
	        key: '_toUnderScore',
	        value: function _toUnderScore(targetString) {
	            return targetString.replace(/([A-Z])/g, function ($0, $1) {
	                return '-' + $1.toLowerCase();
	            });
	        }
	    }]);

	    return Theme;
	}();

	exports.default = Theme;

/***/ }),
/* 78 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var subMenuLabelActive = _ref.subMenuLabelActive,
	        subMenuLabelNormal = _ref.subMenuLabelNormal,
	        subMenuRangeTitle = _ref.subMenuRangeTitle,
	        submenuPartitionVertical = _ref.submenuPartitionVertical,
	        submenuPartitionHorizontal = _ref.submenuPartitionHorizontal,
	        submenuCheckbox = _ref.submenuCheckbox,
	        submenuRangePointer = _ref.submenuRangePointer,
	        submenuRangeValue = _ref.submenuRangeValue,
	        submenuColorpickerTitle = _ref.submenuColorpickerTitle,
	        submenuColorpickerButton = _ref.submenuColorpickerButton,
	        submenuRangeBar = _ref.submenuRangeBar,
	        submenuRangeSubbar = _ref.submenuRangeSubbar,
	        submenuIconSize = _ref.submenuIconSize,
	        menuIconSize = _ref.menuIconSize,
	        biSize = _ref.biSize;
	    return "\n    #tie-icon-add-button.icon-bubble .tui-image-editor-button[data-icontype=\"icon-bubble\"] label,\n    #tie-icon-add-button.icon-heart .tui-image-editor-button[data-icontype=\"icon-heart\"] label,\n    #tie-icon-add-button.icon-location .tui-image-editor-button[data-icontype=\"icon-location\"] label,\n    #tie-icon-add-button.icon-polygon .tui-image-editor-button[data-icontype=\"icon-polygon\"] label,\n    #tie-icon-add-button.icon-star .tui-image-editor-button[data-icontype=\"icon-star\"] label,\n    #tie-icon-add-button.icon-arrow-3 .tui-image-editor-button[data-icontype=\"icon-arrow-3\"] label,\n    #tie-icon-add-button.icon-arrow-2 .tui-image-editor-button[data-icontype=\"icon-arrow-2\"] label,\n    #tie-icon-add-button.icon-arrow .tui-image-editor-button[data-icontype=\"icon-arrow\"] label,\n    #tie-icon-add-button.icon-bubble .tui-image-editor-button[data-icontype=\"icon-bubble\"] label,\n    #tie-draw-line-select-button.line .tui-image-editor-button.line label,\n    #tie-draw-line-select-button.free .tui-image-editor-button.free label,\n    #tie-flip-button.flipX .tui-image-editor-button.flipX label,\n    #tie-flip-button.flipY .tui-image-editor-button.flipY label,\n    #tie-flip-button.resetFlip .tui-image-editor-button.resetFlip label,\n    #tie-crop-button .tui-image-editor-button.apply.active label,\n    #tie-shape-button.rect .tui-image-editor-button.rect label,\n    #tie-shape-button.circle .tui-image-editor-button.circle label,\n    #tie-shape-button.triangle .tui-image-editor-button.triangle label,\n    #tie-text-effect-button .tui-image-editor-button.active label,\n    #tie-text-align-button.left .tui-image-editor-button.left label,\n    #tie-text-align-button.center .tui-image-editor-button.center label,\n    #tie-text-align-button.right .tui-image-editor-button.right label,\n    #tie-mask-apply.apply.active .tui-image-editor-button.apply label,\n    .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button:hover > label,\n    .tui-image-editor-container .tui-image-editor-checkbox input + label {\n        " + subMenuLabelActive + "\n    }\n    .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button > label,\n    .tui-image-editor-container .tui-image-editor-range-wrap.tui-image-editor-newline.short label {\n        " + subMenuLabelNormal + "\n    }\n    .tui-image-editor-container .tui-image-editor-range-wrap label {\n        " + subMenuRangeTitle + "\n    }\n    .tui-image-editor-container .tui-image-editor-partition > div {\n        " + submenuPartitionVertical + "\n    }\n    .tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-partition > div,\n    .tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-partition > div {\n        " + submenuPartitionHorizontal + "\n    }\n    .tui-image-editor-container .tui-image-editor-checkbox input + label:before {\n        " + submenuCheckbox + "\n    }\n    .tui-image-editor-container .tui-image-editor-virtual-range-pointer {\n        " + submenuRangePointer + "\n    }\n    .tui-image-editor-container .tui-image-editor-virtual-range-bar {\n        " + submenuRangeBar + "\n    }\n    .tui-image-editor-container .tui-image-editor-virtual-range-subbar {\n        " + submenuRangeSubbar + "\n    }\n    .tui-image-editor-container .tui-image-editor-range-value {\n        " + submenuRangeValue + "\n    }\n    .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button .color-picker-value + label {\n        " + submenuColorpickerTitle + "\n    }\n    .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button .color-picker-value {\n        " + submenuColorpickerButton + "\n    }\n    .tui-image-editor-container .svg_ic-menu {\n        " + menuIconSize + "\n    }\n    .tui-image-editor-container .svg_ic-submenu {\n        " + submenuIconSize + "\n    }\n    .tui-image-editor-container .tui-image-editor-controls-logo > img,\n    .tui-image-editor-container .tui-image-editor-header-logo > img {\n        " + biSize + "\n    }\n\n";
	};

/***/ }),
/* 79 */
/***/ (function(module, exports) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	  value: true
	});
	/**
	 * @fileoverview The standard theme
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 */

	/**
	 * Full configuration for theme.<br>
	 * @typedef {object} themeConfig
	 * @property {string} common.bi.image - Brand icon image
	 * @property {string} common.bisize.width - Icon image width
	 * @property {string} common.bisize.height - Icon Image Height
	 * @property {string} common.backgroundImage - Background image
	 * @property {string} common.backgroundColor - Background color
	 * @property {string} common.border - Full area border style
	 * @property {string} header.backgroundImage - header area background
	 * @property {string} header.backgroundColor - header area background color
	 * @property {string} header.border - header area border style
	 * @property {string} loadButton.backgroundColor - load button background color
	 * @property {string} loadButton.border - load button border style
	 * @property {string} loadButton.color - load button foreground color
	 * @property {string} loadButton.fontFamily - load button font type
	 * @property {string} loadButton.fontSize - load button font size
	 * @property {string} downloadButton.backgroundColor - download button background color
	 * @property {string} downloadButton.border - download button border style
	 * @property {string} downloadButton.color - download button foreground color
	 * @property {string} downloadButton.fontFamily - download button font type
	 * @property {string} downloadButton.fontSize - download button font size
	 * @property {string} menu.normalIcon.path - Menu default icon svg bundle file path
	 * @property {string} menu.normalIcon.name - Menu default icon svg bundle name
	 * @property {string} menu.activeIcon.path - Menu active icon svg bundle file path
	 * @property {string} menu.activeIcon.name - Menu active icon svg bundle name
	 * @property {string} menu.iconSize.width - Menu icon Size Width
	 * @property {string} menu.iconSize.height - Menu Icon Size Height
	 * @property {string} submenu.backgroundColor - Sub-menu area background color
	 * @property {string} submenu.partition.color - Submenu partition line color
	 * @property {string} submenu.normalIcon.path - Submenu default icon svg bundle file path
	 * @property {string} submenu.normalIcon.name - Submenu default icon svg bundle name
	 * @property {string} submenu.activeIcon.path - Submenu active icon svg bundle file path
	 * @property {string} submenu.activeIcon.name - Submenu active icon svg bundle name
	 * @property {string} submenu.iconSize.width - Submenu icon Size Width
	 * @property {string} submenu.iconSize.height - Submenu Icon Size Height
	 * @property {string} submenu.normalLabel.color - Submenu default label color
	 * @property {string} submenu.normalLabel.fontWeight - Sub Menu Default Label Font Thickness
	 * @property {string} submenu.activeLabel.color - Submenu active label color
	 * @property {string} submenu.activeLabel.fontWeight - Submenu active label Font thickness
	 * @property {string} checkbox.border - Checkbox border style
	 * @property {string} checkbox.backgroundColor - Checkbox background color
	 * @property {string} range.pointer.color - range control pointer color
	 * @property {string} range.bar.color - range control bar color
	 * @property {string} range.subbar.color - range control subbar color
	 * @property {string} range.value.color - range number box font color
	 * @property {string} range.value.fontWeight - range number box font thickness
	 * @property {string} range.value.fontSize - range number box font size
	 * @property {string} range.value.border - range number box border style
	 * @property {string} range.value.backgroundColor - range number box background color
	 * @property {string} range.title.color - range title font color
	 * @property {string} range.title.fontWeight - range title font weight
	 * @property {string} colorpicker.button.border - colorpicker button border style
	 * @property {string} colorpicker.title.color - colorpicker button title font color
	 * @example
	 // default keys and styles
	 var customTheme = {
	    'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
	    'common.bisize.width': '251px',
	    'common.bisize.height': '21px',
	    'common.backgroundImage': 'none',
	    'common.backgroundColor': '#1e1e1e',
	    'common.border': '0px',

	    // header
	    'header.backgroundImage': 'none',
	    'header.backgroundColor': 'transparent',
	    'header.border': '0px',

	    // load button
	    'loadButton.backgroundColor': '#fff',
	    'loadButton.border': '1px solid #ddd',
	    'loadButton.color': '#222',
	    'loadButton.fontFamily': 'NotoSans, sans-serif',
	    'loadButton.fontSize': '12px',

	    // download button
	    'downloadButton.backgroundColor': '#fdba3b',
	    'downloadButton.border': '1px solid #fdba3b',
	    'downloadButton.color': '#fff',
	    'downloadButton.fontFamily': 'NotoSans, sans-serif',
	    'downloadButton.fontSize': '12px',

	    // main icons
	    'menu.normalIcon.path': '../dist/svg/icon-b.svg',
	    'menu.normalIcon.name': 'icon-b',
	    'menu.activeIcon.path': '../dist/svg/icon-a.svg',
	    'menu.activeIcon.name': 'icon-a',
	    'menu.iconSize.width': '24px',
	    'menu.iconSize.height': '24px',

	    // submenu primary color
	    'submenu.backgroundColor': '#1e1e1e',
	    'submenu.partition.color': '#858585',

	    // submenu icons
	    'submenu.normalIcon.path': '../dist/svg/icon-a.svg',
	    'submenu.normalIcon.name': 'icon-a',
	    'submenu.activeIcon.path': '../dist/svg/icon-c.svg',
	    'submenu.activeIcon.name': 'icon-c',
	    'submenu.iconSize.width': '32px',
	    'submenu.iconSize.height': '32px',

	    // submenu labels
	    'submenu.normalLabel.color': '#858585',
	    'submenu.normalLabel.fontWeight': 'lighter',
	    'submenu.activeLabel.color': '#fff',
	    'submenu.activeLabel.fontWeight': 'lighter',

	    // checkbox style
	    'checkbox.border': '1px solid #ccc',
	    'checkbox.backgroundColor': '#fff',

	    // rango style
	    'range.pointer.color': '#fff',
	    'range.bar.color': '#666',
	    'range.subbar.color': '#d1d1d1',
	    'range.value.color': '#fff',
	    'range.value.fontWeight': 'lighter',
	    'range.value.fontSize': '11px',
	    'range.value.border': '1px solid #353535',
	    'range.value.backgroundColor': '#151515',
	    'range.title.color': '#fff',
	    'range.title.fontWeight': 'lighter',

	    // colorpicker style
	    'colorpicker.button.border': '1px solid #1e1e1e',
	    'colorpicker.title.color': '#fff'
	};
	 */
	exports.default = {
	  'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png',
	  'common.bisize.width': '251px',
	  'common.bisize.height': '21px',
	  'common.backgroundImage': 'none',
	  'common.backgroundColor': '#1e1e1e',
	  'common.border': '0px',

	  // header
	  'header.backgroundImage': 'none',
	  'header.backgroundColor': 'transparent',
	  'header.border': '0px',

	  // load button
	  'loadButton.backgroundColor': '#fff',
	  'loadButton.border': '1px solid #ddd',
	  'loadButton.color': '#222',
	  'loadButton.fontFamily': 'NotoSans, sans-serif',
	  'loadButton.fontSize': '12px',

	  // download button
	  'downloadButton.backgroundColor': '#fdba3b',
	  'downloadButton.border': '1px solid #fdba3b',
	  'downloadButton.color': '#fff',
	  'downloadButton.fontFamily': 'NotoSans, sans-serif',
	  'downloadButton.fontSize': '12px',

	  // main icons
	  'menu.normalIcon.path': 'icon-b.svg',
	  'menu.normalIcon.name': 'icon-b',
	  'menu.activeIcon.path': 'icon-a.svg',
	  'menu.activeIcon.name': 'icon-a',
	  'menu.iconSize.width': '24px',
	  'menu.iconSize.height': '24px',

	  // submenu primary color
	  'submenu.backgroundColor': 'transparent',
	  'submenu.partition.color': '#858585',

	  // submenu icons
	  'submenu.normalIcon.path': 'icon-a.svg',
	  'submenu.normalIcon.name': 'icon-a',
	  'submenu.activeIcon.path': 'icon-c.svg',
	  'submenu.activeIcon.name': 'icon-c',
	  'submenu.iconSize.width': '32px',
	  'submenu.iconSize.height': '32px',

	  // submenu labels
	  'submenu.normalLabel.color': '#858585',
	  'submenu.normalLabel.fontWeight': 'lighter',
	  'submenu.activeLabel.color': '#fff',
	  'submenu.activeLabel.fontWeight': 'lighter',

	  // checkbox style
	  'checkbox.border': '1px solid #ccc',
	  'checkbox.backgroundColor': '#fff',

	  // rango style
	  'range.pointer.color': '#fff',
	  'range.bar.color': '#666',
	  'range.subbar.color': '#d1d1d1',
	  'range.value.color': '#fff',
	  'range.value.fontWeight': 'lighter',
	  'range.value.fontSize': '11px',
	  'range.value.border': '1px solid #353535',
	  'range.value.backgroundColor': '#151515',
	  'range.title.color': '#fff',
	  'range.title.fontWeight': 'lighter',

	  // colorpicker style
	  'colorpicker.button.border': '1px solid #1e1e1e',
	  'colorpicker.title.color': '#fff'
	};

/***/ }),
/* 80 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _colorpicker = __webpack_require__(81);

	var _colorpicker2 = _interopRequireDefault(_colorpicker);

	var _range = __webpack_require__(83);

	var _range2 = _interopRequireDefault(_range);

	var _submenuBase = __webpack_require__(84);

	var _submenuBase2 = _interopRequireDefault(_submenuBase);

	var _shape = __webpack_require__(85);

	var _shape2 = _interopRequireDefault(_shape);

	var _util = __webpack_require__(72);

	var _consts = __webpack_require__(73);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

	var SHAPE_DEFAULT_OPTION = {
	    stroke: '#ffbb3b',
	    fill: '',
	    strokeWidth: 3
	};

	/**
	 * Shape ui class
	 * @class
	 * @ignore
	 */

	var Shape = function (_Submenu) {
	    _inherits(Shape, _Submenu);

	    function Shape(subMenuElement, _ref) {
	        var iconStyle = _ref.iconStyle,
	            menuBarPosition = _ref.menuBarPosition;

	        _classCallCheck(this, Shape);

	        var _this = _possibleConstructorReturn(this, (Shape.__proto__ || Object.getPrototypeOf(Shape)).call(this, subMenuElement, {
	            name: 'shape',
	            iconStyle: iconStyle,
	            menuBarPosition: menuBarPosition,
	            templateHtml: _shape2.default
	        }));

	        _this.type = null;
	        _this.options = SHAPE_DEFAULT_OPTION;

	        _this._els = {
	            shapeSelectButton: _this.selector('#tie-shape-button'),
	            shapeColorButton: _this.selector('#tie-shape-color-button'),
	            strokeRange: new _range2.default(_this.selector('#tie-stroke-range'), _consts.defaultShapeStrokeValus),
	            strokeRangeValue: _this.selector('#tie-stroke-range-value'),
	            fillColorpicker: new _colorpicker2.default(_this.selector('#tie-color-fill'), '', _this.toggleDirection),
	            strokeColorpicker: new _colorpicker2.default(_this.selector('#tie-color-stroke'), '#ffbb3b', _this.toggleDirection)
	        };
	        return _this;
	    }

	    /**
	     * Add event for shape
	     * @param {Object} actions - actions for shape
	     *   @param {Function} actions.changeShape - change shape mode
	     *   @param {Function} actions.setDrawingShape - set dreawing shape
	     */


	    _createClass(Shape, [{
	        key: 'addEvent',
	        value: function addEvent(actions) {
	            this.actions = actions;

	            this._els.shapeSelectButton.addEventListener('click', this._changeShapeHandler.bind(this));
	            this._els.strokeRange.on('change', this._changeStrokeRangeHandler.bind(this));
	            this._els.fillColorpicker.on('change', this._changeFillColorHandler.bind(this));
	            this._els.strokeColorpicker.on('change', this._changeStrokeColorHandler.bind(this));
	            this._els.strokeRangeValue.value = this._els.strokeRange.value;
	            this._els.strokeRangeValue.setAttribute('readonly', true);
	        }

	        /**
	         * Set Shape status
	         * @param {Object} options - options of shape status
	         *   @param {string} strokeWidth - stroke width
	         *   @param {string} strokeColor - stroke color
	         *   @param {string} fillColor - fill color
	         */

	    }, {
	        key: 'setShapeStatus',
	        value: function setShapeStatus(_ref2) {
	            var strokeWidth = _ref2.strokeWidth,
	                strokeColor = _ref2.strokeColor,
	                fillColor = _ref2.fillColor;

	            this._els.strokeRange.value = strokeWidth;
	            this._els.strokeRange.trigger('change');

	            this._els.strokeColorpicker.color = strokeColor;
	            this._els.fillColorpicker.color = fillColor;
	            this.options.stroke = strokeColor;
	            this.options.fill = fillColor;
	            this.options.strokeWidth = strokeWidth;
	        }

	        /**
	         * Executed when the menu starts.
	         */

	    }, {
	        key: 'changeStartMode',
	        value: function changeStartMode() {
	            this.actions.stopDrawingMode();
	        }

	        /**
	         * Returns the menu to its default state.
	         */

	    }, {
	        key: 'changeStandbyMode',
	        value: function changeStandbyMode() {
	            this.type = null;
	            this.actions.changeSelectableAll(true);
	            this._els.shapeSelectButton.classList.remove('circle');
	            this._els.shapeSelectButton.classList.remove('triangle');
	            this._els.shapeSelectButton.classList.remove('rect');
	        }

	        /**
	         * set range stroke max value
	         * @param {number} maxValue - expect max value for change
	         */

	    }, {
	        key: 'setMaxStrokeValue',
	        value: function setMaxStrokeValue(maxValue) {
	            var strokeMaxValue = maxValue;
	            if (strokeMaxValue <= 0) {
	                strokeMaxValue = _consts.defaultShapeStrokeValus.max;
	            }
	            this._els.strokeRange.max = strokeMaxValue;
	        }

	        /**
	         * Set stroke value
	         * @param {number} value - expect value for strokeRange change
	         */

	    }, {
	        key: 'setStrokeValue',
	        value: function setStrokeValue(value) {
	            this._els.strokeRange.value = value;
	            this._els.strokeRange.trigger('change');
	        }

	        /**
	         * Get stroke value
	         * @returns {number} - stroke range value
	         */

	    }, {
	        key: 'getStrokeValue',
	        value: function getStrokeValue() {
	            return this._els.strokeRange.value;
	        }

	        /**
	         * Change icon color
	         * @param {object} event - add button event object
	         * @private
	         */

	    }, {
	        key: '_changeShapeHandler',
	        value: function _changeShapeHandler(event) {
	            var button = event.target.closest('.tui-image-editor-button');
	            if (button) {
	                this.actions.stopDrawingMode();
	                this.actions.discardSelection();
	                var shapeType = this.getButtonType(button, ['circle', 'triangle', 'rect']);

	                if (this.type === shapeType) {
	                    this.changeStandbyMode();

	                    return;
	                }
	                this.changeStandbyMode();
	                this.type = shapeType;
	                event.currentTarget.classList.add(shapeType);
	                this.actions.changeSelectableAll(false);
	                this.actions.modeChange('shape');
	            }
	        }

	        /**
	         * Change stroke range
	         * @param {number} value - stroke range value
	         * @private
	         */

	    }, {
	        key: '_changeStrokeRangeHandler',
	        value: function _changeStrokeRangeHandler(value) {
	            this.options.strokeWidth = (0, _util.toInteger)(value);
	            this._els.strokeRangeValue.value = (0, _util.toInteger)(value);

	            this.actions.changeShape({
	                strokeWidth: value
	            });

	            this.actions.setDrawingShape(this.type, this.options);
	        }

	        /**
	         * Change shape color
	         * @param {string} color - fill color
	         * @private
	         */

	    }, {
	        key: '_changeFillColorHandler',
	        value: function _changeFillColorHandler(color) {
	            color = color || 'transparent';
	            this.options.fill = color;
	            this.actions.changeShape({
	                fill: color
	            });
	        }

	        /**
	         * Change shape stroke color
	         * @param {string} color - fill color
	         * @private
	         */

	    }, {
	        key: '_changeStrokeColorHandler',
	        value: function _changeStrokeColorHandler(color) {
	            color = color || 'transparent';
	            this.options.stroke = color;
	            this.actions.changeShape({
	                stroke: color
	            });
	        }
	    }]);

	    return Shape;
	}(_submenuBase2.default);

	exports.default = Shape;

/***/ }),
/* 81 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _util = __webpack_require__(72);

	var _tuiColorPicker = __webpack_require__(82);

	var _tuiColorPicker2 = _interopRequireDefault(_tuiColorPicker);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	var PICKER_COLOR = ['#000000', '#2a2a2a', '#545454', '#7e7e7e', '#a8a8a8', '#d2d2d2', '#ffffff', '', '#ff4040', '#ff6518', '#ffbb3b', '#03bd9e', '#00a9ff', '#515ce6', '#9e5fff', '#ff5583'];

	/**
	 * Colorpicker control class
	 * @class
	 * @ignore
	 */

	var Colorpicker = function () {
	    function Colorpicker(colorpickerElement) {
	        var defaultColor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '#7e7e7e';
	        var toggleDirection = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'up';

	        _classCallCheck(this, Colorpicker);

	        var title = colorpickerElement.getAttribute('title');

	        this._show = false;

	        this._toggleDirection = toggleDirection;
	        this._makePickerButtonElement(colorpickerElement, defaultColor);
	        this._makePickerLayerElement(colorpickerElement, title);
	        this._color = defaultColor;
	        this.picker = _tuiColorPicker2.default.create({
	            container: this.pickerElement,
	            preset: PICKER_COLOR,
	            color: defaultColor
	        });

	        this._addEvent(colorpickerElement);
	    }

	    /**
	     * Get color
	     * @returns {Number} color value
	     */


	    _createClass(Colorpicker, [{
	        key: '_changeColorElement',


	        /**
	         * Change color element
	         * @param {string} color color value
	         * #private
	         */
	        value: function _changeColorElement(color) {
	            if (color) {
	                this.colorElement.classList.remove('transparent');
	                this.colorElement.style.backgroundColor = color;
	            } else {
	                this.colorElement.style.backgroundColor = '#fff';
	                this.colorElement.classList.add('transparent');
	            }
	        }

	        /**
	         * Make picker button element
	         * @param {HTMLElement} colorpickerElement color picker element
	         * @param {string} defaultColor color value
	         * @private
	         */

	    }, {
	        key: '_makePickerButtonElement',
	        value: function _makePickerButtonElement(colorpickerElement, defaultColor) {
	            colorpickerElement.classList.add('tui-image-editor-button');

	            this.colorElement = document.createElement('div');
	            this.colorElement.className = 'color-picker-value';
	            if (defaultColor) {
	                this.colorElement.style.backgroundColor = defaultColor;
	            } else {
	                this.colorElement.classList.add('transparent');
	            }
	        }

	        /**
	         * Make picker layer element
	         * @param {HTMLElement} colorpickerElement color picker element
	         * @param {string} title picker title
	         * @private
	         */

	    }, {
	        key: '_makePickerLayerElement',
	        value: function _makePickerLayerElement(colorpickerElement, title) {
	            var label = document.createElement('label');
	            var triangle = document.createElement('div');

	            this.pickerControl = document.createElement('div');
	            this.pickerControl.className = 'color-picker-control';

	            this.pickerElement = document.createElement('div');
	            this.pickerElement.className = 'color-picker';

	            label.innerHTML = title;
	            triangle.className = 'triangle';

	            this.pickerControl.appendChild(this.pickerElement);
	            this.pickerControl.appendChild(triangle);

	            colorpickerElement.appendChild(this.pickerControl);
	            colorpickerElement.appendChild(this.colorElement);
	            colorpickerElement.appendChild(label);

	            this._setPickerControlPosition();
	        }

	        /**
	         * Add event
	         * @param {HTMLElement} colorpickerElement color picker element
	         * @private
	         */

	    }, {
	        key: '_addEvent',
	        value: function _addEvent(colorpickerElement) {
	            var _this = this;

	            this.picker.on('selectColor', function (value) {
	                _this._changeColorElement(value.color);
	                _this._color = value.color;
	                _this.fire('change', value.color);
	            });
	            colorpickerElement.addEventListener('click', function (event) {
	                _this._show = !_this._show;
	                _this.pickerControl.style.display = _this._show ? 'block' : 'none';
	                event.stopPropagation();
	            });
	            document.body.addEventListener('click', function () {
	                _this._show = false;
	                _this.pickerControl.style.display = 'none';
	            });
	        }

	        /**
	         * Set picker control position
	         * @private
	         */

	    }, {
	        key: '_setPickerControlPosition',
	        value: function _setPickerControlPosition() {
	            var controlStyle = this.pickerControl.style;
	            var left = (0, _util.toInteger)(window.getComputedStyle(this.pickerControl, null).width) / 2 - 20;
	            var top = ((0, _util.toInteger)(window.getComputedStyle(this.pickerControl, null).height) + 12) * -1;

	            if (this._toggleDirection === 'down') {
	                top = 30;
	            }

	            controlStyle.top = top + 'px';
	            controlStyle.left = '-' + left + 'px';
	        }
	    }, {
	        key: 'color',
	        get: function get() {
	            return this._color;
	        }

	        /**
	         * Set color
	         * @param {string} color color value
	         */
	        ,
	        set: function set(color) {
	            this._color = color;
	            this._changeColorElement(color);
	        }
	    }]);

	    return Colorpicker;
	}();

	_tuiCodeSnippet2.default.CustomEvents.mixin(Colorpicker);
	exports.default = Colorpicker;

/***/ }),
/* 82 */
/***/ (function(module, exports, __webpack_require__) {

	/*!
	 * Toast UI Colorpicker
	 * @version 2.2.0
	 * @author NHNEnt FE Development Team <dl_javascript@nhnent.com>
	 * @license MIT
	 */
	(function webpackUniversalModuleDefinition(root, factory) {
		if(true)
			module.exports = factory(__webpack_require__(3));
		else if(typeof define === 'function' && define.amd)
			define(["tui-code-snippet"], factory);
		else if(typeof exports === 'object')
			exports["colorPicker"] = factory(require("tui-code-snippet"));
		else
			root["tui"] = root["tui"] || {}, root["tui"]["colorPicker"] = factory((root["tui"] && root["tui"]["util"]));
	})(this, function(__WEBPACK_EXTERNAL_MODULE_8__) {
	return /******/ (function(modules) { // webpackBootstrap
	/******/ 	// The module cache
	/******/ 	var installedModules = {};

	/******/ 	// The require function
	/******/ 	function __webpack_require__(moduleId) {

	/******/ 		// Check if module is in cache
	/******/ 		if(installedModules[moduleId])
	/******/ 			return installedModules[moduleId].exports;

	/******/ 		// Create a new module (and put it into the cache)
	/******/ 		var module = installedModules[moduleId] = {
	/******/ 			exports: {},
	/******/ 			id: moduleId,
	/******/ 			loaded: false
	/******/ 		};

	/******/ 		// Execute the module function
	/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

	/******/ 		// Flag the module as loaded
	/******/ 		module.loaded = true;

	/******/ 		// Return the exports of the module
	/******/ 		return module.exports;
	/******/ 	}


	/******/ 	// expose the modules object (__webpack_modules__)
	/******/ 	__webpack_require__.m = modules;

	/******/ 	// expose the module cache
	/******/ 	__webpack_require__.c = installedModules;

	/******/ 	// __webpack_public_path__
	/******/ 	__webpack_require__.p = "dist";

	/******/ 	// Load entry module and return exports
	/******/ 	return __webpack_require__(0);
	/******/ })
	/************************************************************************/
	/******/ ([
	/* 0 */
	/***/ (function(module, exports, __webpack_require__) {

		__webpack_require__(1);
		module.exports = __webpack_require__(6);


	/***/ }),
	/* 1 */
	/***/ (function(module, exports) {

		// removed by extract-text-webpack-plugin

	/***/ }),
	/* 2 */,
	/* 3 */,
	/* 4 */,
	/* 5 */,
	/* 6 */
	/***/ (function(module, exports, __webpack_require__) {

		'use strict';

		var domutil = __webpack_require__(7);
		var domevent = __webpack_require__(9);
		var Collection = __webpack_require__(10);
		var View = __webpack_require__(11);
		var Drag = __webpack_require__(12);
		var create = __webpack_require__(13);
		var Palette = __webpack_require__(16);
		var Slider = __webpack_require__(18);
		var colorutil = __webpack_require__(14);
		var svgvml = __webpack_require__(19);

		var colorPicker = {
		    domutil: domutil,
		    domevent: domevent,
		    Collection: Collection,
		    View: View,
		    Drag: Drag,

		    create: create,
		    Palette: Palette,
		    Slider: Slider,
		    colorutil: colorutil,
		    svgvml: svgvml
		};

		module.exports = colorPicker;

	/***/ }),
	/* 7 */
	/***/ (function(module, exports, __webpack_require__) {

		/**
		 * @fileoverview Utility modules for manipulate DOM elements.
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var snippet = __webpack_require__(8);
		var domevent = __webpack_require__(9);
		var Collection = __webpack_require__(10);

		var util = snippet,
		    posKey = '_pos',
		    supportSelectStart = 'onselectstart' in document,
		    prevSelectStyle = '',
		    domutil,
		    userSelectProperty;

		var CSS_AUTO_REGEX = /^auto$|^$|%/;

		function trim(str) {
		    return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
		}

		domutil = {
		    /**
		     * Create DOM element and return it.
		     * @param {string} tagName Tag name to append.
		     * @param {HTMLElement} [container] HTML element will be parent to created element.
		     * if not supplied, will use **document.body**
		     * @param {string} [className] Design class names to appling created element.
		     * @returns {HTMLElement} HTML element created.
		     */
		    appendHTMLElement: function (tagName, container, className) {
		        var el;

		        className = className || '';

		        el = document.createElement(tagName);
		        el.className = className;

		        if (container) {
		            container.appendChild(el);
		        } else {
		            document.body.appendChild(el);
		        }

		        return el;
		    },

		    /**
		     * Remove element from parent node.
		     * @param {HTMLElement} el - element to remove.
		     */
		    remove: function (el) {
		        if (el && el.parentNode) {
		            el.parentNode.removeChild(el);
		        }
		    },

		    /**
		     * Get element by id
		     * @param {string} id element id attribute
		     * @returns {HTMLElement} element
		     */
		    get: function (id) {
		        return document.getElementById(id);
		    },

		    /**
		     * Check supplied element is matched selector.
		     * @param {HTMLElement} el - element to check
		     * @param {string} selector - selector string to check
		     * @returns {boolean} match?
		     */
		    _matcher: function (el, selector) {
		        var cssClassSelector = /^\./,
		            idSelector = /^#/;

		        if (cssClassSelector.test(selector)) {
		            return domutil.hasClass(el, selector.replace('.', ''));
		        } else if (idSelector.test(selector)) {
		            return el.id === selector.replace('#', '');
		        }

		        return el.nodeName.toLowerCase() === selector.toLowerCase();
		    },

		    /**
		     * Find DOM element by specific selectors.
		     * below three selector only supported.
		     *
		     * 1. css selector
		     * 2. id selector
		     * 3. nodeName selector
		     * @param {string} selector selector
		     * @param {(HTMLElement|string)} [root] You can assign root element to find. if not supplied, document.body will use.
		     * @param {boolean|function} [multiple=false] - set true then return all elements that meet condition, if set function then use it filter function.
		     * @returns {HTMLElement} HTML element finded.
		     */
		    find: function (selector, root, multiple) {
		        var result = [],
		            found = false,
		            isFirst = util.isUndefined(multiple) || multiple === false,
		            isFilter = util.isFunction(multiple);

		        if (util.isString(root)) {
		            root = domutil.get(root);
		        }

		        root = root || window.document.body;

		        function recurse(el, selector) {
		            var childNodes = el.childNodes,
		                i = 0,
		                len = childNodes.length,
		                cursor;

		            for (; i < len; i += 1) {
		                cursor = childNodes[i];

		                if (cursor.nodeName === '#text') {
		                    continue;
		                }

		                if (domutil._matcher(cursor, selector)) {
		                    if (isFilter && multiple(cursor) || !isFilter) {
		                        result.push(cursor);
		                    }

		                    if (isFirst) {
		                        found = true;
		                        break;
		                    }
		                } else if (cursor.childNodes.length > 0) {
		                    recurse(cursor, selector);
		                    if (found) {
		                        break;
		                    }
		                }
		            }
		        }

		        recurse(root, selector);

		        return isFirst ? result[0] || null : result;
		    },

		    /**
		     * Find parent element recursively.
		     * @param {HTMLElement} el - base element to start find.
		     * @param {string} selector - selector string for find
		     * @returns {HTMLElement} - element finded or undefined.
		     */
		    closest: function (el, selector) {
		        var parent = el.parentNode;

		        if (domutil._matcher(el, selector)) {
		            return el;
		        }

		        while (parent && parent !== window.document.body) {
		            if (domutil._matcher(parent, selector)) {
		                return parent;
		            }

		            parent = parent.parentNode;
		        }
		    },

		    /**
		     * Return texts inside element.
		     * @param {HTMLElement} el target element
		     * @returns {string} text inside node
		     */
		    text: function (el) {
		        var ret = '',
		            i = 0,
		            nodeType = el.nodeType;

		        if (nodeType) {
		            if (nodeType === 1 || nodeType === 9 || nodeType === 11) {
		                // nodes that available contain other nodes
		                if (typeof el.textContent === 'string') {
		                    return el.textContent;
		                }

		                for (el = el.firstChild; el; el = el.nextSibling) {
		                    ret += domutil.text(el);
		                }
		            } else if (nodeType === 3 || nodeType === 4) {
		                // TEXT, CDATA SECTION
		                return el.nodeValue;
		            }
		        } else {
		            for (; el[i]; i += 1) {
		                ret += domutil.text(el[i]);
		            }
		        }

		        return ret;
		    },

		    /**
		     * Set data attribute to target element
		     * @param {HTMLElement} el - element to set data attribute
		     * @param {string} key - key
		     * @param {string|number} data - data value
		     */
		    setData: function (el, key, data) {
		        if ('dataset' in el) {
		            el.dataset[key] = data;

		            return;
		        }

		        el.setAttribute('data-' + key, data);
		    },

		    /**
		     * Get data value from data-attribute
		     * @param {HTMLElement} el - target element
		     * @param {string} key - key
		     * @returns {string} value
		     */
		    getData: function (el, key) {
		        if ('dataset' in el) {
		            return el.dataset[key];
		        }

		        return el.getAttribute('data-' + key);
		    },

		    /**
		     * Check element has specific design class name.
		     * @param {HTMLElement} el target element
		     * @param {string} name css class
		     * @returns {boolean} return true when element has that css class name
		     */
		    hasClass: function (el, name) {
		        var className;

		        if (!util.isUndefined(el.classList)) {
		            return el.classList.contains(name);
		        }

		        className = domutil.getClass(el);

		        return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
		    },

		    /**
		     * Add design class to HTML element.
		     * @param {HTMLElement} el target element
		     * @param {string} name css class name
		     */
		    addClass: function (el, name) {
		        var className;

		        if (!util.isUndefined(el.classList)) {
		            util.forEachArray(name.split(' '), function (value) {
		                el.classList.add(value);
		            });
		        } else if (!domutil.hasClass(el, name)) {
		            className = domutil.getClass(el);
		            domutil.setClass(el, (className ? className + ' ' : '') + name);
		        }
		    },

		    /**
		     *
		     * Overwrite design class to HTML element.
		     * @param {HTMLElement} el target element
		     * @param {string} name css class name
		     */
		    setClass: function (el, name) {
		        if (util.isUndefined(el.className.baseVal)) {
		            el.className = name;
		        } else {
		            el.className.baseVal = name;
		        }
		    },

		    /**
		     * Elementì— cssClassì†ì„±ì„ ì œê±°í•˜ëŠ” ë©”ì„œë“œ
		     * Remove specific design class from HTML element.
		     * @param {HTMLElement} el target element
		     * @param {string} name class name to remove
		     */
		    removeClass: function (el, name) {
		        var removed = '';

		        if (!util.isUndefined(el.classList)) {
		            el.classList.remove(name);
		        } else {
		            removed = (' ' + domutil.getClass(el) + ' ').replace(' ' + name + ' ', ' ');
		            domutil.setClass(el, trim(removed));
		        }
		    },

		    /**
		     * Get HTML element's design classes.
		     * @param {HTMLElement} el target element
		     * @returns {string} element css class name
		     */
		    getClass: function (el) {
		        if (!el || !el.className) {
		            return '';
		        }

		        return util.isUndefined(el.className.baseVal) ? el.className : el.className.baseVal;
		    },

		    /**
		     * Get specific CSS style value from HTML element.
		     * @param {HTMLElement} el target element
		     * @param {string} style css attribute name
		     * @returns {(string|null)} css style value
		     */
		    getStyle: function (el, style) {
		        var value = el.style[style] || el.currentStyle && el.currentStyle[style],
		            css;

		        if ((!value || value === 'auto') && document.defaultView) {
		            css = document.defaultView.getComputedStyle(el, null);
		            value = css ? css[style] : null;
		        }

		        return value === 'auto' ? null : value;
		    },

		    /**
		     * get element's computed style values.
		     *
		     * in lower IE8. use polyfill function that return object. it has only one function 'getPropertyValue'
		     * @param {HTMLElement} el - element want to get style.
		     * @returns {object} virtual CSSStyleDeclaration object.
		     */
		    getComputedStyle: function (el) {
		        var defaultView = document.defaultView;

		        if (!defaultView || !defaultView.getComputedStyle) {
		            return {
		                getPropertyValue: function (prop) {
		                    var re = /(\-([a-z]){1})/g;
		                    if (prop === 'float') {
		                        prop = 'styleFloat';
		                    }

		                    if (re.test(prop)) {
		                        prop = prop.replace(re, function () {
		                            return arguments[2].toUpperCase();
		                        });
		                    }

		                    return el.currentStyle[prop] ? el.currentStyle[prop] : null;
		                }
		            };
		        }

		        return document.defaultView.getComputedStyle(el);
		    },

		    /**
		     * Set position CSS style.
		     * @param {HTMLElement} el target element
		     * @param {number} [x=0] left pixel value.
		     * @param {number} [y=0] top pixel value.
		     */
		    setPosition: function (el, x, y) {
		        x = util.isUndefined(x) ? 0 : x;
		        y = util.isUndefined(y) ? 0 : y;

		        el[posKey] = [x, y];

		        el.style.left = x + 'px';
		        el.style.top = y + 'px';
		    },

		    /**
		     * Get position from HTML element.
		     * @param {HTMLElement} el target element
		     * @param {boolean} [clear=false] clear cache before calculating position.
		     * @returns {number[]} point
		     */
		    getPosition: function (el, clear) {
		        var left, top, bound;

		        if (clear) {
		            el[posKey] = null;
		        }

		        if (el[posKey]) {
		            return el[posKey];
		        }

		        left = 0;
		        top = 0;

		        if ((CSS_AUTO_REGEX.test(el.style.left) || CSS_AUTO_REGEX.test(el.style.top)) && 'getBoundingClientRect' in el) {
		            // ì—˜ë¦¬ë¨¼íŠ¸ì˜ leftë˜ëŠ” topì´ 'auto'ì¼ ë•Œ ìˆ˜ë‹¨
		            bound = el.getBoundingClientRect();

		            left = bound.left;
		            top = bound.top;
		        } else {
		            left = parseFloat(el.style.left || 0);
		            top = parseFloat(el.style.top || 0);
		        }

		        return [left, top];
		    },

		    /**
		     * Return element's size
		     * @param {HTMLElement} el target element
		     * @returns {number[]} width, height
		     */
		    getSize: function (el) {
		        var bound,
		            width = domutil.getStyle(el, 'width'),
		            height = domutil.getStyle(el, 'height');

		        if ((CSS_AUTO_REGEX.test(width) || CSS_AUTO_REGEX.test(height)) && 'getBoundingClientRect' in el) {
		            bound = el.getBoundingClientRect();
		            width = bound.width;
		            height = bound.height;
		        } else {
		            width = parseFloat(width || 0);
		            height = parseFloat(height || 0);
		        }

		        return [width, height];
		    },

		    /**
		     * Check specific CSS style is available.
		     * @param {array} props property name to testing
		     * @returns {(string|boolean)} return true when property is available
		     * @example
		     * var props = ['transform', '-webkit-transform'];
		     * domutil.testProp(props);    // 'transform'
		     */
		    testProp: function (props) {
		        var style = document.documentElement.style,
		            i = 0,
		            len = props.length;

		        for (; i < len; i += 1) {
		            if (props[i] in style) {
		                return props[i];
		            }
		        }

		        return false;
		    },

		    /**
		     * Get form data
		     * @param {HTMLFormElement} formElement - form element to extract data
		     * @returns {object} form data
		     */
		    getFormData: function (formElement) {
		        var groupedByName = new Collection(function () {
		            return this.length;
		        }),
		            noDisabledFilter = function (el) {
		            return !el.disabled;
		        },
		            output = {};

		        groupedByName.add.apply(groupedByName, domutil.find('input', formElement, noDisabledFilter).concat(domutil.find('select', formElement, noDisabledFilter)).concat(domutil.find('textarea', formElement, noDisabledFilter)));

		        groupedByName = groupedByName.groupBy(function (el) {
		            return el && el.getAttribute('name') || '_other';
		        });

		        util.forEach(groupedByName, function (elements, name) {
		            if (name === '_other') {
		                return;
		            }

		            elements.each(function (el) {
		                var nodeName = el.nodeName.toLowerCase(),
		                    type = el.type,
		                    result = [];

		                if (type === 'radio') {
		                    result = [elements.find(function (el) {
		                        return el.checked;
		                    }).toArray().pop()];
		                } else if (type === 'checkbox') {
		                    result = elements.find(function (el) {
		                        return el.checked;
		                    }).toArray();
		                } else if (nodeName === 'select') {
		                    elements.find(function (el) {
		                        return !!el.childNodes.length;
		                    }).each(function (el) {
		                        result = result.concat(domutil.find('option', el, function (opt) {
		                            return opt.selected;
		                        }));
		                    });
		                } else {
		                    result = elements.find(function (el) {
		                        return el.value !== '';
		                    }).toArray();
		                }

		                result = util.map(result, function (el) {
		                    return el.value;
		                });

		                if (!result.length) {
		                    result = '';
		                } else if (result.length === 1) {
		                    result = result[0];
		                }

		                output[name] = result;
		            });
		        });

		        return output;
		    }
		};

		userSelectProperty = domutil.testProp(['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);

		/**
		 * Disable browser's text selection behaviors.
		 * @method
		 */
		domutil.disableTextSelection = function () {
		    if (supportSelectStart) {
		        return function () {
		            domevent.on(window, 'selectstart', domevent.preventDefault);
		        };
		    }

		    return function () {
		        var style = document.documentElement.style;
		        prevSelectStyle = style[userSelectProperty];
		        style[userSelectProperty] = 'none';
		    };
		}();

		/**
		 * Enable browser's text selection behaviors.
		 * @method
		 */
		domutil.enableTextSelection = function () {
		    if (supportSelectStart) {
		        return function () {
		            domevent.off(window, 'selectstart', domevent.preventDefault);
		        };
		    }

		    return function () {
		        document.documentElement.style[userSelectProperty] = prevSelectStyle;
		    };
		}();

		/**
		 * Disable browser's image drag behaviors.
		 */
		domutil.disableImageDrag = function () {
		    domevent.on(window, 'dragstart', domevent.preventDefault);
		};

		/**
		 * Enable browser's image drag behaviors.
		 */
		domutil.enableImageDrag = function () {
		    domevent.off(window, 'dragstart', domevent.preventDefault);
		};

		/**
		 * Replace matched property with template
		 * @param {string} template - String of template
		 * @param {Object} propObj - Properties
		 * @returns {string} Replaced template string
		 */
		domutil.applyTemplate = function (template, propObj) {
		    var newTemplate = template.replace(/\{\{(\w*)\}\}/g, function (value, prop) {
		        return propObj.hasOwnProperty(prop) ? propObj[prop] : '';
		    });

		    return newTemplate;
		};

		module.exports = domutil;

	/***/ }),
	/* 8 */
	/***/ (function(module, exports) {

		module.exports = __WEBPACK_EXTERNAL_MODULE_8__;

	/***/ }),
	/* 9 */
	/***/ (function(module, exports, __webpack_require__) {

		/**
		 * @fileoverview Utility module for handling DOM events.
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var snippet = __webpack_require__(8);

		var util = snippet,
		    browser = util.browser,
		    eventKey = '_evt',
		    DRAG = {
		    START: ['touchstart', 'mousedown'],
		    END: {
		        mousedown: 'mouseup',
		        touchstart: 'touchend',
		        pointerdown: 'touchend',
		        MSPointerDown: 'touchend'
		    },
		    MOVE: {
		        mousedown: 'mousemove',
		        touchstart: 'touchmove',
		        pointerdown: 'touchmove',
		        MSPointerDown: 'touchmove'
		    }
		};

		var domevent = {
		    /**
		     * Bind dom events.
		     * @param {HTMLElement} obj HTMLElement to bind events.
		     * @param {(string|object)} types Space splitted events names or eventName:handler object.
		     * @param {*} fn handler function or context for handler method.
		     * @param {*} [context] context object for handler method.
		     */
		    on: function (obj, types, fn, context) {
		        if (util.isString(types)) {
		            util.forEach(types.split(' '), function (type) {
		                domevent._on(obj, type, fn, context);
		            });

		            return;
		        }

		        util.forEachOwnProperties(types, function (handler, type) {
		            domevent._on(obj, type, handler, fn);
		        });
		    },

		    /**
		     * DOM event binding.
		     * @param {HTMLElement} obj HTMLElement to bind events.
		     * @param {String} type The name of events.
		     * @param {*} fn handler function
		     * @param {*} [context] context object for handler method.
		     * @private
		     */
		    _on: function (obj, type, fn, context) {
		        var id, handler, originHandler;

		        id = type + util.stamp(fn) + (context ? '_' + util.stamp(context) : '');

		        if (obj[eventKey] && obj[eventKey][id]) {
		            return;
		        }

		        handler = function (e) {
		            fn.call(context || obj, e || window.event);
		        };

		        originHandler = handler;

		        if ('addEventListener' in obj) {
		            if (type === 'mouseenter' || type === 'mouseleave') {
		                handler = function (e) {
		                    e = e || window.event;
		                    if (!domevent._checkMouse(obj, e)) {
		                        return;
		                    }
		                    originHandler(e);
		                };
		                obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
		            } else {
		                if (type === 'mousewheel') {
		                    obj.addEventListener('DOMMouseScroll', handler, false);
		                }

		                obj.addEventListener(type, handler, false);
		            }
		        } else if ('attachEvent' in obj) {
		            obj.attachEvent('on' + type, handler);
		        }

		        obj[eventKey] = obj[eventKey] || {};
		        obj[eventKey][id] = handler;
		    },

		    /**
		     * Unbind DOM Event handler.
		     * @param {HTMLElement} obj HTMLElement to unbind.
		     * @param {(string|object)} types Space splitted events names or eventName:handler object.
		     * @param {*} fn handler function or context for handler method.
		     * @param {*} [context] context object for handler method.
		     */
		    off: function (obj, types, fn, context) {
		        if (util.isString(types)) {
		            util.forEach(types.split(' '), function (type) {
		                domevent._off(obj, type, fn, context);
		            });

		            return;
		        }

		        util.forEachOwnProperties(types, function (handler, type) {
		            domevent._off(obj, type, handler, fn);
		        });
		    },

		    /**
		     * Unbind DOM event handler.
		     * @param {HTMLElement} obj HTMLElement to unbind.
		     * @param {String} type The name of event to unbind.
		     * @param {function()} fn Event handler that supplied when binding.
		     * @param {*} context context object that supplied when binding.
		     * @private
		     */
		    _off: function (obj, type, fn, context) {
		        var id = type + util.stamp(fn) + (context ? '_' + util.stamp(context) : ''),
		            handler = obj[eventKey] && obj[eventKey][id];

		        if (!handler) {
		            return;
		        }

		        if ('removeEventListener' in obj) {
		            if (type === 'mouseenter' || type === 'mouseleave') {
		                obj.removeEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
		            } else {
		                if (type === 'mousewheel') {
		                    obj.removeEventListener('DOMMouseScroll', handler, false);
		                }

		                obj.removeEventListener(type, handler, false);
		            }
		        } else if ('detachEvent' in obj) {
		            try {
		                obj.detachEvent('on' + type, handler);
		            } catch (e) {} //eslint-disable-line
		        }

		        delete obj[eventKey][id];

		        if (util.keys(obj[eventKey]).length) {
		            return;
		        }

		        // throw exception when deleting host object's property in below IE8
		        if (util.browser.msie && util.browser.version < 9) {
		            obj[eventKey] = null;

		            return;
		        }

		        delete obj[eventKey];
		    },

		    /**
		     * Bind DOM event. this event will unbind after invokes.
		     * @param {HTMLElement} obj HTMLElement to bind events.
		     * @param {(string|object)} types Space splitted events names or eventName:handler object.
		     * @param {*} fn handler function or context for handler method.
		     * @param {*} [context] context object for handler method.
		     */
		    once: function (obj, types, fn, context) {
		        var that = this;

		        if (util.isObject(types)) {
		            util.forEachOwnProperties(types, function (handler, type) {
		                domevent.once(obj, type, handler, fn);
		            });

		            return;
		        }

		        function onceHandler() {
		            fn.apply(context || obj, arguments);
		            that._off(obj, types, onceHandler, context);
		        }

		        domevent.on(obj, types, onceHandler, context);
		    },

		    /**
		     * Cancel event bubbling.
		     * @param {Event} e Event object.
		     */
		    stopPropagation: function (e) {
		        if (e.stopPropagation) {
		            e.stopPropagation();
		        } else {
		            e.cancelBubble = true;
		        }
		    },

		    /**
		     * Cancel browser default actions.
		     * @param {Event} e Event object.
		     */
		    preventDefault: function (e) {
		        if (e.preventDefault) {
		            e.preventDefault();
		        } else {
		            e.returnValue = false;
		        }
		    },

		    /**
		     * Syntatic sugar of stopPropagation and preventDefault
		     * @param {Event} e Event object.
		     */
		    stop: function (e) {
		        domevent.preventDefault(e);
		        domevent.stopPropagation(e);
		    },

		    /**
		     * Stop scroll events.
		     * @param {HTMLElement} el HTML element to prevent scroll.
		     */
		    disableScrollPropagation: function (el) {
		        domevent.on(el, 'mousewheel MozMousePixelScroll', domevent.stopPropagation);
		    },

		    /**
		     * Stop all events related with click.
		     * @param {HTMLElement} el HTML element to prevent all event related with click.
		     */
		    disableClickPropagation: function (el) {
		        domevent.on(el, DRAG.START.join(' ') + ' click dblclick', domevent.stopPropagation);
		    },

		    /**
		     * Get mouse position from mouse event.
		     *
		     * If supplied relatveElement parameter then return relative position based on element.
		     * @param {Event} mouseEvent Mouse event object
		     * @param {HTMLElement} relativeElement HTML element that calculate relative position.
		     * @returns {number[]} mouse position.
		     */
		    getMousePosition: function (mouseEvent, relativeElement) {
		        var rect;

		        if (!relativeElement) {
		            return [mouseEvent.clientX, mouseEvent.clientY];
		        }

		        rect = relativeElement.getBoundingClientRect();

		        return [mouseEvent.clientX - rect.left - relativeElement.clientLeft, mouseEvent.clientY - rect.top - relativeElement.clientTop];
		    },

		    /**
		     * Normalize mouse wheel event that different each browsers.
		     * @param {MouseEvent} e Mouse wheel event.
		     * @returns {Number} delta
		     */
		    getWheelDelta: function (e) {
		        var delta = 0;

		        if (e.wheelDelta) {
		            delta = e.wheelDelta / 120;
		        }

		        if (e.detail) {
		            delta = -e.detail / 3;
		        }

		        return delta;
		    },

		    /**
		     * prevent firing mouseleave event when mouse entered child elements.
		     * @param {HTMLElement} el HTML element
		     * @param {MouseEvent} e Mouse event
		     * @returns {Boolean} leave?
		     * @private
		     */
		    _checkMouse: function (el, e) {
		        var related = e.relatedTarget;

		        if (!related) {
		            return true;
		        }

		        try {
		            while (related && related !== el) {
		                related = related.parentNode;
		            }
		        } catch (err) {
		            return false;
		        }

		        return related !== el;
		    },

		    /**
		     * Trigger specific events to html element.
		     * @param {HTMLElement} obj HTMLElement
		     * @param {string} type Event type name
		     * @param {object} [eventData] Event data
		     */
		    trigger: function (obj, type, eventData) {
		        var rMouseEvent = /(mouse|click)/;
		        if (util.isUndefined(eventData) && rMouseEvent.exec(type)) {
		            eventData = domevent.mouseEvent(type);
		        }

		        if (obj.dispatchEvent) {
		            obj.dispatchEvent(eventData);
		        } else if (obj.fireEvent) {
		            obj.fireEvent('on' + type, eventData);
		        }
		    },

		    /**
		     * Create virtual mouse event.
		     *
		     * Tested at
		     *
		     * - IE7 ~ IE11
		     * - Chrome
		     * - Firefox
		     * - Safari
		     * @param {string} type Event type
		     * @param {object} [eventObj] Event data
		     * @returns {MouseEvent} Virtual mouse event.
		     */
		    mouseEvent: function (type, eventObj) {
		        var evt, e;

		        e = util.extend({
		            bubbles: true,
		            cancelable: type !== 'mousemove',
		            view: window,
		            wheelDelta: 0,
		            detail: 0,
		            screenX: 0,
		            screenY: 0,
		            clientX: 0,
		            clientY: 0,
		            ctrlKey: false,
		            altKey: false,
		            shiftKey: false,
		            metaKey: false,
		            button: 0,
		            relatedTarget: undefined // eslint-disable-line
		        }, eventObj);

		        // prevent throw error when inserting wheelDelta property to mouse event on below IE8
		        if (browser.msie && browser.version < 9) {
		            delete e.wheelDelta;
		        }

		        if (typeof document.createEvent === 'function') {
		            evt = document.createEvent('MouseEvents');
		            evt.initMouseEvent(type, e.bubbles, e.cancelable, e.view, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, document.body.parentNode);
		        } else if (document.createEventObject) {
		            evt = document.createEventObject();

		            util.forEach(e, function (value, propName) {
		                evt[propName] = value;
		            }, this);
		            evt.button = {
		                0: 1,
		                1: 4,
		                2: 2
		            }[evt.button] || evt.button;
		        }

		        return evt;
		    },

		    /**
		     * Normalize mouse event's button attributes.
		     *
		     * Can detect which button is clicked by this method.
		     *
		     * Meaning of return numbers
		     *
		     * - 0: primary mouse button
		     * - 1: wheel button or center button
		     * - 2: secondary mouse button
		     * @param {MouseEvent} mouseEvent - The mouse event object want to know.
		     * @returns {number} - The value of meaning which button is clicked?
		     */
		    getMouseButton: function (mouseEvent) {
		        var button,
		            primary = '0,1,3,5,7',
		            secondary = '2,6',
		            wheel = '4';

		        /* istanbul ignore else */
		        if (document.implementation.hasFeature('MouseEvents', '2.0')) {
		            return mouseEvent.button;
		        }

		        button = mouseEvent.button + '';
		        if (~primary.indexOf(button)) {
		            return 0;
		        } else if (~secondary.indexOf(button)) {
		            return 2;
		        } else if (~wheel.indexOf(button)) {
		            return 1;
		        }
		    }
		};

		module.exports = domevent;

	/***/ }),
	/* 10 */
	/***/ (function(module, exports, __webpack_require__) {

		/**
		 * @fileoverview Common collections.
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var snippet = __webpack_require__(8);

		var util = snippet,
		    forEachProp = util.forEachOwnProperties,
		    forEachArr = util.forEachArray,
		    isFunc = util.isFunction,
		    isObj = util.isObject;

		var aps = Array.prototype.slice;

		/**
		 * Common collection.
		 *
		 * It need function for get model's unique id.
		 *
		 * if the function is not supplied then it use default function {@link Collection#getItemID}
		 * @constructor
		 * @param {function} [getItemIDFn] function for get model's id.
		 * @ignore
		 */
		function Collection(getItemIDFn) {
		    /**
		     * @type {object.<string, *>}
		     */
		    this.items = {};

		    /**
		     * @type {number}
		     */
		    this.length = 0;

		    if (isFunc(getItemIDFn)) {
		        /**
		         * @type {function}
		         */
		        this.getItemID = getItemIDFn;
		    }
		}

		/**********
		 * static props
		 **********/

		/**
		 * Combind supplied function filters and condition.
		 * @param {...function} filters - function filters
		 * @returns {function} combined filter
		 */
		Collection.and = function (filters) {
		    var cnt;

		    filters = aps.call(arguments);
		    cnt = filters.length;

		    return function (item) {
		        var i = 0;

		        for (; i < cnt; i += 1) {
		            if (!filters[i].call(null, item)) {
		                return false;
		            }
		        }

		        return true;
		    };
		};

		/**
		 * Combine multiple function filters with OR clause.
		 * @param {...function} filters - function filters
		 * @returns {function} combined filter
		 */
		Collection.or = function (filters) {
		    var cnt;

		    filters = aps.call(arguments);
		    cnt = filters.length;

		    return function (item) {
		        var i = 1,
		            result = filters[0].call(null, item);

		        for (; i < cnt; i += 1) {
		            result = result || filters[i].call(null, item);
		        }

		        return result;
		    };
		};

		/**
		 * Merge several collections.
		 *
		 * You can\'t merge collections different _getEventID functions. Take case of use.
		 * @param {...Collection} collections collection arguments to merge
		 * @returns {Collection} merged collection.
		 */
		Collection.merge = function (collections) {
		    // eslint-disable-line
		    var cols = aps.call(arguments),
		        newItems = {},
		        merged = new Collection(cols[0].getItemID),
		        extend = util.extend;

		    forEachArr(cols, function (col) {
		        extend(newItems, col.items);
		    });

		    merged.items = newItems;
		    merged.length = util.keys(merged.items).length;

		    return merged;
		};

		/**********
		 * prototype props
		 **********/

		/**
		 * get model's unique id.
		 * @param {object} item model instance.
		 * @returns {number} model unique id.
		 */
		Collection.prototype.getItemID = function (item) {
		    return item._id + '';
		};

		/**
		 * add models.
		 * @param {...*} item models to add this collection.
		 */
		Collection.prototype.add = function (item) {
		    var id, ownItems;

		    if (arguments.length > 1) {
		        forEachArr(aps.call(arguments), function (o) {
		            this.add(o);
		        }, this);

		        return;
		    }

		    id = this.getItemID(item);
		    ownItems = this.items;

		    if (!ownItems[id]) {
		        this.length += 1;
		    }
		    ownItems[id] = item;
		};

		/**
		 * remove models.
		 * @param {...(object|string|number)} id model instance or unique id to delete.
		 * @returns {array} deleted model list.
		 */
		Collection.prototype.remove = function (id) {
		    var removed = [],
		        ownItems,
		        itemToRemove;

		    if (!this.length) {
		        return removed;
		    }

		    if (arguments.length > 1) {
		        removed = util.map(aps.call(arguments), function (id) {
		            return this.remove(id);
		        }, this);

		        return removed;
		    }

		    ownItems = this.items;

		    if (isObj(id)) {
		        id = this.getItemID(id);
		    }

		    if (!ownItems[id]) {
		        return removed;
		    }

		    this.length -= 1;
		    itemToRemove = ownItems[id];
		    delete ownItems[id];

		    return itemToRemove;
		};

		/**
		 * remove all models in collection.
		 */
		Collection.prototype.clear = function () {
		    this.items = {};
		    this.length = 0;
		};

		/**
		 * check collection has specific model.
		 * @param {(object|string|number|function)} id model instance or id or filter function to check
		 * @returns {boolean} is has model?
		 */
		Collection.prototype.has = function (id) {
		    var isFilter, has;

		    if (!this.length) {
		        return false;
		    }

		    isFilter = isFunc(id);
		    has = false;

		    if (isFilter) {
		        this.each(function (item) {
		            if (id(item) === true) {
		                has = true;

		                return false;
		            }

		            return true;
		        });
		    } else {
		        id = isObj(id) ? this.getItemID(id) : id;
		        has = util.isExisty(this.items[id]);
		    }

		    return has;
		};

		/**
		 * invoke callback when model exist in collection.
		 * @param {(string|number)} id model unique id.
		 * @param {function} fn the callback.
		 * @param {*} [context] callback context.
		 */
		Collection.prototype.doWhenHas = function (id, fn, context) {
		    var item = this.items[id];

		    if (!util.isExisty(item)) {
		        return;
		    }

		    fn.call(context || this, item);
		};

		/**
		 * Search model. and return new collection.
		 * @param {function} filter filter function.
		 * @returns {Collection} new collection with filtered models.
		 * @example
		 * collection.find(function(item) {
		 *     return item.edited === true;
		 * });
		 *
		 * function filter1(item) {
		 *     return item.edited === false;
		 * }
		 *
		 * function filter2(item) {
		 *     return item.disabled === false;
		 * }
		 *
		 * collection.find(Collection.and(filter1, filter2));
		 *
		 * collection.find(Collection.or(filter1, filter2));
		 */
		Collection.prototype.find = function (filter) {
		    var result = new Collection();

		    if (this.hasOwnProperty('getItemID')) {
		        result.getItemID = this.getItemID;
		    }

		    this.each(function (item) {
		        if (filter(item) === true) {
		            result.add(item);
		        }
		    });

		    return result;
		};

		/**
		 * Group element by specific key values.
		 *
		 * if key parameter is function then invoke it and use returned value.
		 * @param {(string|number|function|array)} key key property or getter function. if string[] supplied, create each collection before grouping.
		 * @param {function} [groupFunc] - function that return each group's key
		 * @returns {object.<string, Collection>} grouped object
		 * @example
		 *
		 * // pass `string`, `number`, `boolean` type value then group by property value.
		 * collection.groupBy('gender');    // group by 'gender' property value.
		 * collection.groupBy(50);          // group by '50' property value.
		 *
		 * // pass `function` then group by return value. each invocation `function` is called with `(item)`.
		 * collection.groupBy(function(item) {
		 *     if (item.score > 60) {
		 *         return 'pass';
		 *     }
		 *     return 'fail';
		 * });
		 *
		 * // pass `array` with first arguments then create each collection before grouping.
		 * collection.groupBy(['go', 'ruby', 'javascript']);
		 * // result: { 'go': empty Collection, 'ruby': empty Collection, 'javascript': empty Collection }
		 *
		 * // can pass `function` with `array` then group each elements.
		 * collection.groupBy(['go', 'ruby', 'javascript'], function(item) {
		 *     if (item.isFast) {
		 *         return 'go';
		 *     }
		 *
		 *     return item.name;
		 * });
		 */
		Collection.prototype.groupBy = function (key, groupFunc) {
		    var result = {},
		        collection,
		        baseValue,
		        keyIsFunc = isFunc(key),
		        getItemIDFn = this.getItemID;

		    if (util.isArray(key)) {
		        util.forEachArray(key, function (k) {
		            result[k + ''] = new Collection(getItemIDFn);
		        });

		        if (!groupFunc) {
		            return result;
		        }

		        key = groupFunc;
		        keyIsFunc = true;
		    }

		    this.each(function (item) {
		        if (keyIsFunc) {
		            baseValue = key(item);
		        } else {
		            baseValue = item[key];

		            if (isFunc(baseValue)) {
		                baseValue = baseValue.apply(item);
		            }
		        }

		        collection = result[baseValue];

		        if (!collection) {
		            collection = result[baseValue] = new Collection(getItemIDFn);
		        }

		        collection.add(item);
		    });

		    return result;
		};

		/**
		 * Return single item in collection.
		 *
		 * Returned item is inserted in this collection firstly.
		 * @returns {object} item.
		 */
		Collection.prototype.single = function () {
		    var result;

		    this.each(function (item) {
		        result = item;

		        return false;
		    }, this);

		    return result;
		};

		/**
		 * sort a basis of supplied compare function.
		 * @param {function} compareFunction compareFunction
		 * @returns {array} sorted array.
		 */
		Collection.prototype.sort = function (compareFunction) {
		    var arr = [];

		    this.each(function (item) {
		        arr.push(item);
		    });

		    if (isFunc(compareFunction)) {
		        arr = arr.sort(compareFunction);
		    }

		    return arr;
		};

		/**
		 * iterate each model element.
		 *
		 * when iteratee return false then break the loop.
		 * @param {function} iteratee iteratee(item, index, items)
		 * @param {*} [context] context
		 */
		Collection.prototype.each = function (iteratee, context) {
		    forEachProp(this.items, iteratee, context || this);
		};

		/**
		 * return new array with collection items.
		 * @returns {array} new array.
		 */
		Collection.prototype.toArray = function () {
		    if (!this.length) {
		        return [];
		    }

		    return util.map(this.items, function (item) {
		        return item;
		    });
		};

		module.exports = Collection;

	/***/ }),
	/* 11 */
	/***/ (function(module, exports, __webpack_require__) {

		/**
		 * @fileoverview The base class of views.
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var util = __webpack_require__(8);
		var domutil = __webpack_require__(7);
		var Collection = __webpack_require__(10);

		/**
		 * Base class of views.
		 *
		 * All views create own container element inside supplied container element.
		 * @constructor
		 * @param {options} options The object for describe view's specs.
		 * @param {HTMLElement} container Default container element for view. you can use this element for this.container syntax.
		 * @ignore
		 */
		function View(options, container) {
		    var id = util.stamp(this);

		    options = options || {};

		    if (util.isUndefined(container)) {
		        container = domutil.appendHTMLElement('div');
		    }

		    domutil.addClass(container, 'tui-view-' + id);

		    /**
		     * unique id
		     * @type {number}
		     */
		    this.id = id;

		    /**
		     * base element of view.
		     * @type {HTMLDIVElement}
		     */
		    this.container = container;

		    /**
		     * child views.
		     * @type {Collection}
		     */
		    this.childs = new Collection(function (view) {
		        return util.stamp(view);
		    });

		    /**
		     * parent view instance.
		     * @type {View}
		     */
		    this.parent = null;
		}

		/**
		 * Add child views.
		 * @param {View} view The view instance to add.
		 * @param {function} [fn] Function for invoke before add. parent view class is supplied first arguments.
		 */
		View.prototype.addChild = function (view, fn) {
		    if (fn) {
		        fn.call(view, this);
		    }
		    // add parent view
		    view.parent = this;

		    this.childs.add(view);
		};

		/**
		 * Remove added child view.
		 * @param {(number|View)} id View id or instance itself to remove.
		 * @param {function} [fn] Function for invoke before remove. parent view class is supplied first arguments.
		 */
		View.prototype.removeChild = function (id, fn) {
		    var view = util.isNumber(id) ? this.childs.items[id] : id;

		    id = util.stamp(view);

		    if (fn) {
		        fn.call(view, this);
		    }

		    this.childs.remove(id);
		};

		/**
		 * Render view recursively.
		 */
		View.prototype.render = function () {
		    this.childs.each(function (childView) {
		        childView.render();
		    });
		};

		/**
		 * Invoke function recursively.
		 * @param {function} fn - function to invoke child view recursively
		 * @param {boolean} [skipThis=false] - set true then skip invoke with this(root) view.
		 */
		View.prototype.recursive = function (fn, skipThis) {
		    if (!util.isFunction(fn)) {
		        return;
		    }

		    if (!skipThis) {
		        fn(this);
		    }

		    this.childs.each(function (childView) {
		        childView.recursive(fn);
		    });
		};

		/**
		 * Resize view recursively to parent.
		 */
		View.prototype.resize = function () {
		    var args = Array.prototype.slice.call(arguments),
		        parent = this.parent;

		    while (parent) {
		        if (util.isFunction(parent._onResize)) {
		            parent._onResize.apply(parent, args);
		        }

		        parent = parent.parent;
		    }
		};

		/**
		 * Invoking method before destroying.
		 */
		View.prototype._beforeDestroy = function () {};

		/**
		 * Clear properties
		 */
		View.prototype._destroy = function () {
		    this._beforeDestroy();
		    this.childs.clear();
		    this.container.innerHTML = '';

		    this.id = this.parent = this.childs = this.container = null;
		};

		/**
		 * Destroy child view recursively.
		 * @param {boolean} isChildView - Whether it is the child view or not
		 */
		View.prototype.destroy = function (isChildView) {
		    this.childs.each(function (childView) {
		        childView.destroy(true);
		        childView._destroy();
		    });

		    if (isChildView) {
		        return;
		    }

		    this._destroy();
		};

		/**
		 * Calculate view's container element bound.
		 * @returns {object} The bound of container element.
		 */
		View.prototype.getViewBound = function () {
		    var container = this.container,
		        position = domutil.getPosition(container),
		        size = domutil.getSize(container);

		    return {
		        x: position[0],
		        y: position[1],
		        width: size[0],
		        height: size[1]
		    };
		};

		module.exports = View;

	/***/ }),
	/* 12 */
	/***/ (function(module, exports, __webpack_require__) {

		/* WEBPACK VAR INJECTION */(function(global) {/**
		 * @fileoverview General drag handler
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var util = __webpack_require__(8);
		var domutil = __webpack_require__(7);
		var domevent = __webpack_require__(9);

		/**
		 * @constructor
		 * @mixes CustomEvents
		 * @param {object} options - options for drag handler
		 * @param {number} [options.distance=10] - distance in pixels after mouse must move before dragging should start
		 * @param {HTMLElement} container - container element to bind drag events
		 * @ignore
		 */
		function Drag(options, container) {
		    domevent.on(container, 'mousedown', this._onMouseDown, this);

		    this.options = util.extend({
		        distance: 10
		    }, options);

		    /**
		     * @type {HTMLElement}
		     */
		    this.container = container;

		    /**
		     * @type {boolean}
		     */
		    this._isMoved = false;

		    /**
		     * dragging distance in pixel between mousedown and firing dragStart events
		     * @type {number}
		     */
		    this._distance = 0;

		    /**
		     * @type {boolean}
		     */
		    this._dragStartFired = false;

		    /**
		     * @type {object}
		     */
		    this._dragStartEventData = null;
		}

		/**
		 * Destroy method.
		 */
		Drag.prototype.destroy = function () {
		    domevent.off(this.container, 'mousedown', this._onMouseDown, this);

		    this.options = this.container = this._isMoved = this._distance = this._dragStartFired = this._dragStartEventData = null;
		};

		/**
		 * Toggle events for mouse dragging.
		 * @param {boolean} toBind - bind events related with dragging when supplied "true"
		 */
		Drag.prototype._toggleDragEvent = function (toBind) {
		    var container = this.container,
		        domMethod,
		        method;

		    if (toBind) {
		        domMethod = 'on';
		        method = 'disable';
		    } else {
		        domMethod = 'off';
		        method = 'enable';
		    }

		    domutil[method + 'TextSelection'](container);
		    domutil[method + 'ImageDrag'](container);
		    domevent[domMethod](global.document, {
		        mousemove: this._onMouseMove,
		        mouseup: this._onMouseUp
		    }, this);
		};

		/**
		 * Normalize mouse event object.
		 * @param {MouseEvent} mouseEvent - mouse event object.
		 * @returns {object} normalized mouse event data.
		 */
		Drag.prototype._getEventData = function (mouseEvent) {
		    return {
		        target: mouseEvent.target || mouseEvent.srcElement,
		        originEvent: mouseEvent
		    };
		};

		/**
		 * MouseDown DOM event handler.
		 * @param {MouseEvent} mouseDownEvent MouseDown event object.
		 */
		Drag.prototype._onMouseDown = function (mouseDownEvent) {
		    // only primary button can start drag.
		    if (domevent.getMouseButton(mouseDownEvent) !== 0) {
		        return;
		    }

		    this._distance = 0;
		    this._dragStartFired = false;
		    this._dragStartEventData = this._getEventData(mouseDownEvent);

		    this._toggleDragEvent(true);
		};

		/**
		 * MouseMove DOM event handler.
		 * @emits Drag#drag
		 * @emits Drag#dragStart
		 * @param {MouseEvent} mouseMoveEvent MouseMove event object.
		 */
		Drag.prototype._onMouseMove = function (mouseMoveEvent) {
		    var distance = this.options.distance;
		    // prevent automatic scrolling.
		    domevent.preventDefault(mouseMoveEvent);

		    this._isMoved = true;

		    if (this._distance < distance) {
		        this._distance += 1;

		        return;
		    }

		    if (!this._dragStartFired) {
		        this._dragStartFired = true;

		        /**
		         * Drag starts events. cancelable.
		         * @event Drag#dragStart
		         * @type {object}
		         * @property {HTMLElement} target - target element in this event.
		         * @property {MouseEvent} originEvent - original mouse event object.
		         */
		        if (!this.invoke('dragStart', this._dragStartEventData)) {
		            this._toggleDragEvent(false);

		            return;
		        }
		    }

		    /**
		     * Events while dragging.
		     * @event Drag#drag
		     * @type {object}
		     * @property {HTMLElement} target - target element in this event.
		     * @property {MouseEvent} originEvent - original mouse event object.
		     */
		    this.fire('drag', this._getEventData(mouseMoveEvent));
		};

		/**
		 * MouseUp DOM event handler.
		 * @param {MouseEvent} mouseUpEvent MouseUp event object.
		 * @emits Drag#dragEnd
		 * @emits Drag#click
		 */
		Drag.prototype._onMouseUp = function (mouseUpEvent) {
		    this._toggleDragEvent(false);

		    // emit "click" event when not emitted drag event between mousedown and mouseup.
		    if (this._isMoved) {
		        this._isMoved = false;

		        /**
		         * Drag end events.
		         * @event Drag#dragEnd
		         * @type {MouseEvent}
		         * @property {HTMLElement} target - target element in this event.
		         * @property {MouseEvent} originEvent - original mouse event object.
		         */
		        this.fire('dragEnd', this._getEventData(mouseUpEvent));

		        return;
		    }

		    /**
		     * Click events.
		     * @event Drag#click
		     * @type {MouseEvent}
		     * @property {HTMLElement} target - target element in this event.
		     * @property {MouseEvent} originEvent - original mouse event object.
		     */
		    this.fire('click', this._getEventData(mouseUpEvent));
		};

		util.CustomEvents.mixin(Drag);

		module.exports = Drag;
		/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

	/***/ }),
	/* 13 */
	/***/ (function(module, exports, __webpack_require__) {

		/**
		 * @fileoverview ColorPicker factory module
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var util = __webpack_require__(8);
		var colorutil = __webpack_require__(14);
		var Layout = __webpack_require__(15);
		var Palette = __webpack_require__(16);
		var Slider = __webpack_require__(18);

		var hostnameSent = false;

		/**
		 * send hostname
		 * @ignore
		 */
		function sendHostname() {
		    var hostname = location.hostname;

		    if (hostnameSent) {
		        return;
		    }
		    hostnameSent = true;

		    util.imagePing('https://www.google-analytics.com/collect', {
		        v: 1,
		        t: 'event',
		        tid: 'UA-115377265-9',
		        cid: hostname,
		        dp: hostname,
		        dh: 'color-picker'
		    });
		}

		/**
		 * @constructor
		 * @mixes CustomEvents
		 * @param {object} options - options for colorpicker component
		 *  @param {HTMLDivElement} options.container - container element
		 *  @param {string} [options.color='#ffffff'] - default selected color
		 *  @param {string[]} [options.preset] - color preset for palette (use base16 palette if not supplied)
		 *  @param {string} [options.cssPrefix='tui-colorpicker-'] - css prefix text for each child elements
		 *  @param {string} [options.detailTxt='Detail'] - text for detail button.
		 *  @param {boolean} [options.usageStatistics=true] - Let us know the hostname. If you don't want to send the hostname, please set to false.
		 * @example
		 * var colorPicker = tui.colorPicker; // or require('tui-color-picker')
		 *
		 * colorPicker.create({
		 *   container: document.getElementById('color-picker')
		 * });
		 */
		function ColorPicker(options) {
		    var layout;

		    if (!(this instanceof ColorPicker)) {
		        return new ColorPicker(options);
		    }
		    /**
		     * Option object
		     * @type {object}
		     * @private
		     */
		    options = this.options = util.extend({
		        container: null,
		        color: '#f8f8f8',
		        preset: ['#181818', '#282828', '#383838', '#585858', '#b8b8b8', '#d8d8d8', '#e8e8e8', '#f8f8f8', '#ab4642', '#dc9656', '#f7ca88', '#a1b56c', '#86c1b9', '#7cafc2', '#ba8baf', '#a16946'],
		        cssPrefix: 'tui-colorpicker-',
		        detailTxt: 'Detail',
		        usageStatistics: true
		    }, options);

		    if (!options.container) {
		        throw new Error('ColorPicker(): need container option.');
		    }

		    /**********
		     * Create layout view
		     **********/

		    /**
		     * @type {Layout}
		     * @private
		     */
		    layout = this.layout = new Layout(options, options.container);

		    /**********
		     * Create palette view
		     **********/
		    this.palette = new Palette(options, layout.container);
		    this.palette.on({
		        '_selectColor': this._onSelectColorInPalette,
		        '_toggleSlider': this._onToggleSlider
		    }, this);

		    /**********
		     * Create slider view
		     **********/
		    this.slider = new Slider(options, layout.container);
		    this.slider.on('_selectColor', this._onSelectColorInSlider, this);

		    /**********
		     * Add child views
		     **********/
		    layout.addChild(this.palette);
		    layout.addChild(this.slider);

		    this.render(options.color);

		    if (options.usageStatistics) {
		        sendHostname();
		    }
		}

		/**
		 * Handler method for Palette#_selectColor event
		 * @private
		 * @fires ColorPicker#selectColor
		 * @param {object} selectColorEventData - event data
		 */
		ColorPicker.prototype._onSelectColorInPalette = function (selectColorEventData) {
		    var color = selectColorEventData.color,
		        opt = this.options;

		    if (!colorutil.isValidRGB(color) && color !== '') {
		        this.render();

		        return;
		    }

		    /**
		     * @event ColorPicker#selectColor
		     * @type {object}
		     * @property {string} color - selected color (hex string)
		     * @property {string} origin - flags for represent the source of event fires.
		     */
		    this.fire('selectColor', {
		        color: color,
		        origin: 'palette'
		    });

		    if (opt.color === color) {
		        return;
		    }

		    opt.color = color;
		    this.render(color);
		};

		/**
		 * Handler method for Palette#_toggleSlider event
		 * @private
		 */
		ColorPicker.prototype._onToggleSlider = function () {
		    this.slider.toggle(!this.slider.isVisible());
		};

		/**
		 * Handler method for Slider#_selectColor event
		 * @private
		 * @fires ColorPicker#selectColor
		 * @param {object} selectColorEventData - event data
		 */
		ColorPicker.prototype._onSelectColorInSlider = function (selectColorEventData) {
		    var color = selectColorEventData.color,
		        opt = this.options;

		    /**
		     * @event ColorPicker#selectColor
		     * @type {object}
		     * @property {string} color - selected color (hex string)
		     * @property {string} origin - flags for represent the source of event fires.
		     * @ignore
		     */
		    this.fire('selectColor', {
		        color: color,
		        origin: 'slider'
		    });

		    if (opt.color === color) {
		        return;
		    }

		    opt.color = color;
		    this.palette.render(color);
		};

		/**********
		 * PUBLIC API
		 **********/

		/**
		 * Set color to colorpicker instance.<br>
		 * The string parameter must be hex color value
		 * @param {string} hexStr - hex formatted color string
		 * @example
		 * colorPicker.setColor('#ffff00');
		 */
		ColorPicker.prototype.setColor = function (hexStr) {
		    if (!colorutil.isValidRGB(hexStr)) {
		        throw new Error('ColorPicker#setColor(): need valid hex string color value');
		    }

		    this.options.color = hexStr;
		    this.render(hexStr);
		};

		/**
		 * Get hex color string of current selected color in colorpicker instance.
		 * @returns {string} hex string formatted color
		 * @example
		 * colorPicker.setColor('#ffff00');
		 * colorPicker.getColor(); // '#ffff00';
		 */
		ColorPicker.prototype.getColor = function () {
		    return this.options.color;
		};

		/**
		 * Toggle colorpicker element. set true then reveal colorpicker view.
		 * @param {boolean} [isShow=false] - A flag to show
		 * @example
		 * colorPicker.toggle(false); // hide
		 * colorPicker.toggle(); // hide
		 * colorPicker.toggle(true); // show
		 */
		ColorPicker.prototype.toggle = function (isShow) {
		    this.layout.container.style.display = !!isShow ? 'block' : 'none';
		};

		/**
		 * Render colorpicker
		 * @param {string} [color] - selected color
		 * @ignore
		 */
		ColorPicker.prototype.render = function (color) {
		    this.layout.render(color || this.options.color);
		};

		/**
		 * Destroy colorpicker instance.
		 * @example
		 * colorPicker.destroy(); // DOM-element is removed
		 */
		ColorPicker.prototype.destroy = function () {
		    this.layout.destroy();
		    this.options.container.innerHTML = '';

		    this.layout = this.slider = this.palette = this.options = null;
		};

		util.CustomEvents.mixin(ColorPicker);

		module.exports = ColorPicker;

	/***/ }),
	/* 14 */
	/***/ (function(module, exports) {

		/**
		 * @fileoverview Utility methods to manipulate colors
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var hexRX = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i;

		var colorutil = {
		    /**
		     * pad left zero characters.
		     * @param {number} number number value to pad zero.
		     * @param {number} length pad length to want.
		     * @returns {string} padded string.
		     */
		    leadingZero: function (number, length) {
		        var zero = '',
		            i = 0;

		        if ((number + '').length > length) {
		            return number + '';
		        }

		        for (; i < length - 1; i += 1) {
		            zero += '0';
		        }

		        return (zero + number).slice(length * -1);
		    },

		    /**
		     * Check validate of hex string value is RGB
		     * @param {string} str - rgb hex string
		     * @returns {boolean} return true when supplied str is valid RGB hex string
		     */
		    isValidRGB: function (str) {
		        return hexRX.test(str);
		    },

		    // @license RGB <-> HSV conversion utilities based off of http://www.cs.rit.edu/~ncs/color/t_convert.html

		    /**
		     * Convert color hex string to rgb number array
		     * @param {string} hexStr - hex string
		     * @returns {number[]} rgb numbers
		     */
		    hexToRGB: function (hexStr) {
		        var r, g, b;

		        if (!colorutil.isValidRGB(hexStr)) {
		            return false;
		        }

		        hexStr = hexStr.substring(1);

		        r = parseInt(hexStr.substr(0, 2), 16);
		        g = parseInt(hexStr.substr(2, 2), 16);
		        b = parseInt(hexStr.substr(4, 2), 16);

		        return [r, g, b];
		    },

		    /**
		     * Convert rgb number to hex string
		     * @param {number} r - red
		     * @param {number} g - green
		     * @param {number} b - blue
		     * @returns {string|boolean} return false when supplied rgb number is not valid. otherwise, converted hex string
		     */
		    rgbToHEX: function (r, g, b) {
		        var hexStr = '#' + colorutil.leadingZero(r.toString(16), 2) + colorutil.leadingZero(g.toString(16), 2) + colorutil.leadingZero(b.toString(16), 2);

		        if (colorutil.isValidRGB(hexStr)) {
		            return hexStr;
		        }

		        return false;
		    },

		    /**
		     * Convert rgb number to HSV value
		     * @param {number} r - red
		     * @param {number} g - green
		     * @param {number} b - blue
		     * @returns {number[]} hsv value
		     */
		    rgbToHSV: function (r, g, b) {
		        var max, min, h, s, v, d;

		        r /= 255;
		        g /= 255;
		        b /= 255;
		        max = Math.max(r, g, b);
		        min = Math.min(r, g, b);
		        v = max;
		        d = max - min;
		        s = max === 0 ? 0 : d / max;

		        if (max === min) {
		            h = 0;
		        } else {
		            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;
		                // no default
		            }
		            h /= 6;
		        }

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

		    /**
		     * Convert HSV number to RGB
		     * @param {number} h - hue
		     * @param {number} s - saturation
		     * @param {number} v - value
		     * @returns {number[]} rgb value
		     */
		    hsvToRGB: function (h, s, v) {
		        var r, g, b;
		        var i;
		        var f, p, q, t;

		        h = Math.max(0, Math.min(360, h));
		        s = Math.max(0, Math.min(100, s));
		        v = Math.max(0, Math.min(100, v));

		        s /= 100;
		        v /= 100;

		        if (s === 0) {
		            // Achromatic (grey)
		            r = g = b = v;

		            return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
		        }

		        h /= 60; // sector 0 to 5
		        i = Math.floor(h);
		        f = h - i; // factorial part of h
		        p = v * (1 - s);
		        q = v * (1 - s * f);
		        t = v * (1 - s * (1 - f));

		        switch (i) {
		            case 0:
		                r = v;g = t;b = p;break;
		            case 1:
		                r = q;g = v;b = p;break;
		            case 2:
		                r = p;g = v;b = t;break;
		            case 3:
		                r = p;g = q;b = v;break;
		            case 4:
		                r = t;g = p;b = v;break;
		            default:
		                r = v;g = p;b = q;break;
		        }

		        return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
		    }
		};

		module.exports = colorutil;

	/***/ }),
	/* 15 */
	/***/ (function(module, exports, __webpack_require__) {

		/**
		 * @fileoverview ColorPicker layout module
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var util = __webpack_require__(8);
		var domutil = __webpack_require__(7);
		var View = __webpack_require__(11);

		/**
		 * @constructor
		 * @extends {View}
		 * @param {object} options - option object
		 *  @param {string} options.cssPrefix - css prefix for each child elements
		 * @param {HTMLDivElement} container - container
		 * @ignore
		 */
		function Layout(options, container) {
		    /**
		     * option object
		     * @type {object}
		     */
		    this.options = util.extend({
		        cssPrefix: 'tui-colorpicker-'
		    }, options);

		    container = domutil.appendHTMLElement('div', container, this.options.cssPrefix + 'container');

		    View.call(this, options, container);

		    this.render();
		}

		util.inherit(Layout, View);

		/**
		 * @override
		 * @param {string} [color] - selected color
		 */
		Layout.prototype.render = function (color) {
		    this.recursive(function (view) {
		        view.render(color);
		    }, true);
		};

		module.exports = Layout;

	/***/ }),
	/* 16 */
	/***/ (function(module, exports, __webpack_require__) {

		/**
		 * @fileoverview Color palette view
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var util = __webpack_require__(8);
		var domutil = __webpack_require__(7);
		var colorutil = __webpack_require__(14);
		var domevent = __webpack_require__(9);
		var View = __webpack_require__(11);
		var tmpl = __webpack_require__(17);

		/**
		 * @constructor
		 * @extends {View}
		 * @mixes CustomEvents
		 * @param {object} options - options for color palette view
		 *  @param {string[]} options.preset - color list
		 * @param {HTMLDivElement} container - container element
		 * @ignore
		 */
		function Palette(options, container) {
		    /**
		     * option object
		     * @type {object}
		     */
		    this.options = util.extend({
		        cssPrefix: 'tui-colorpicker-',
		        preset: ['#181818', '#282828', '#383838', '#585858', '#B8B8B8', '#D8D8D8', '#E8E8E8', '#F8F8F8', '#AB4642', '#DC9656', '#F7CA88', '#A1B56C', '#86C1B9', '#7CAFC2', '#BA8BAF', '#A16946'],
		        detailTxt: 'Detail'
		    }, options);

		    container = domutil.appendHTMLElement('div', container, this.options.cssPrefix + 'palette-container');

		    View.call(this, options, container);
		}

		util.inherit(Palette, View);

		/**
		 * Mouse click event handler
		 * @fires Palette#_selectColor
		 * @fires Palette#_toggleSlider
		 * @param {MouseEvent} clickEvent - mouse event object
		 */
		Palette.prototype._onClick = function (clickEvent) {
		    var options = this.options,
		        target = clickEvent.srcElement || clickEvent.target,
		        eventData = {};

		    if (domutil.hasClass(target, options.cssPrefix + 'palette-button')) {
		        eventData.color = target.value;

		        /**
		         * @event Palette#_selectColor
		         * @type {object}
		         * @property {string} color - selected color value
		         */
		        this.fire('_selectColor', eventData);

		        return;
		    }

		    if (domutil.hasClass(target, options.cssPrefix + 'palette-toggle-slider')) {
		        /**
		         * @event Palette#_toggleSlider
		         */
		        this.fire('_toggleSlider');
		    }
		};

		/**
		 * Textbox change event handler
		 * @fires Palette#_selectColor
		 * @param {Event} changeEvent - change event object
		 */
		Palette.prototype._onChange = function (changeEvent) {
		    var options = this.options,
		        target = changeEvent.srcElement || changeEvent.target,
		        eventData = {};

		    if (domutil.hasClass(target, options.cssPrefix + 'palette-hex')) {
		        eventData.color = target.value;

		        /**
		         * @event Palette#_selectColor
		         * @type {object}
		         * @property {string} color - selected color value
		         */
		        this.fire('_selectColor', eventData);
		    }
		};

		/**
		 * Invoke before destory
		 * @override
		 */
		Palette.prototype._beforeDestroy = function () {
		    this._toggleEvent(false);
		};

		/**
		 * Toggle view DOM events
		 * @param {boolean} [onOff=false] - true to bind event.
		 */
		Palette.prototype._toggleEvent = function (onOff) {
		    var options = this.options,
		        container = this.container,
		        method = domevent[!!onOff ? 'on' : 'off'],
		        hexTextBox;

		    method(container, 'click', this._onClick, this);

		    hexTextBox = domutil.find('.' + options.cssPrefix + 'palette-hex', container);

		    if (hexTextBox) {
		        method(hexTextBox, 'change', this._onChange, this);
		    }
		};

		/**
		 * Render palette
		 * @override
		 */
		Palette.prototype.render = function (color) {
		    var options = this.options,
		        html = '';

		    this._toggleEvent(false);

		    html = tmpl.layout.replace('{{colorList}}', util.map(options.preset, function (itemColor) {
		        var itemHtml = '';
		        var style = '';

		        if (colorutil.isValidRGB(itemColor)) {
		            style = domutil.applyTemplate(tmpl.itemStyle, { color: itemColor });
		        }

		        itemHtml = domutil.applyTemplate(tmpl.item, {
		            itemStyle: style,
		            itemClass: !itemColor ? ' ' + options.cssPrefix + 'color-transparent' : '',
		            color: itemColor,
		            cssPrefix: options.cssPrefix,
		            selected: itemColor === color ? ' ' + options.cssPrefix + 'selected' : ''
		        });

		        return itemHtml;
		    }).join(''));

		    html = domutil.applyTemplate(html, {
		        cssPrefix: options.cssPrefix,
		        detailTxt: options.detailTxt,
		        color: color
		    });

		    this.container.innerHTML = html;

		    this._toggleEvent(true);
		};

		util.CustomEvents.mixin(Palette);

		module.exports = Palette;

	/***/ }),
	/* 17 */
	/***/ (function(module, exports) {

		/**
		 * @fileoverview Palette view template
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var layout = ['<ul class="{{cssPrefix}}clearfix">{{colorList}}</ul>', '<div class="{{cssPrefix}}clearfix" style="overflow:hidden">', '<input type="button" class="{{cssPrefix}}palette-toggle-slider" value="{{detailTxt}}" />', '<input type="text" class="{{cssPrefix}}palette-hex" value="{{color}}" maxlength="7" />', '<span class="{{cssPrefix}}palette-preview" style="background-color:{{color}};color:{{color}}">{{color}}</span>', '</div>'].join('\n');

		var item = '<li><input class="{{cssPrefix}}palette-button{{selected}}{{itemClass}}" type="button" style="{{itemStyle}}" title="{{color}}" value="{{color}}" /></li>';
		var itemStyle = 'background-color:{{color}};color:{{color}}';

		module.exports = {
		    layout: layout,
		    item: item,
		    itemStyle: itemStyle
		};

	/***/ }),
	/* 18 */
	/***/ (function(module, exports, __webpack_require__) {

		/**
		 * @fileoverview Slider view
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var util = __webpack_require__(8);
		var domutil = __webpack_require__(7);
		var domevent = __webpack_require__(9);
		var svgvml = __webpack_require__(19);
		var colorutil = __webpack_require__(14);
		var View = __webpack_require__(11);
		var Drag = __webpack_require__(12);
		var tmpl = __webpack_require__(20);

		// Limitation position of point element inside of colorslider and hue bar
		// Minimum value can to be negative because that using color point of handle element is center point. not left, top point.
		var COLORSLIDER_POS_LIMIT_RANGE = [-7, 112];
		var HUEBAR_POS_LIMIT_RANGE = [-3, 115];
		var HUE_WHEEL_MAX = 359.99;

		/**
		 * @constructor
		 * @extends {View}
		 * @mixes CustomEvents
		 * @param {object} options - options for view
		 *  @param {string} options.cssPrefix - design css prefix
		 * @param {HTMLElement} container - container element
		 * @ignore
		 */
		function Slider(options, container) {
		    container = domutil.appendHTMLElement('div', container, options.cssPrefix + 'slider-container');
		    container.style.display = 'none';

		    View.call(this, options, container);

		    /**
		     * @type {object}
		     */
		    this.options = util.extend({
		        color: '#f8f8f8',
		        cssPrefix: 'tui-colorpicker-'
		    }, options);

		    /**
		     * Cache immutable data in click, drag events.
		     *
		     * (i.e. is event related with colorslider? or huebar?)
		     * @type {object}
		     * @property {boolean} isColorSlider
		     * @property {number[]} containerSize
		     */
		    this._dragDataCache = {};

		    /**
		     * Color slider handle element
		     * @type {SVG|VML}
		     */
		    this.sliderHandleElement = null;

		    /**
		     * hue bar handle element
		     * @type {SVG|VML}
		     */
		    this.huebarHandleElement = null;

		    /**
		     * Element that render base color in colorslider part
		     * @type {SVG|VML}
		     */
		    this.baseColorElement = null;

		    /**
		     * @type {Drag}
		     */
		    this.drag = new Drag({
		        distance: 0
		    }, container);

		    // bind drag events
		    this.drag.on({
		        'dragStart': this._onDragStart,
		        'drag': this._onDrag,
		        'dragEnd': this._onDragEnd,
		        'click': this._onClick
		    }, this);
		}

		util.inherit(Slider, View);

		/**
		 * @override
		 */
		Slider.prototype._beforeDestroy = function () {
		    this.drag.off();

		    this.drag = this.options = this._dragDataCache = this.sliderHandleElement = this.huebarHandleElement = this.baseColorElement = null;
		};

		/**
		 * Toggle slider view
		 * @param {boolean} onOff - set true then reveal slider view
		 */
		Slider.prototype.toggle = function (onOff) {
		    this.container.style.display = !!onOff ? 'block' : 'none';
		};

		/**
		 * Get slider display status
		 * @returns {boolean} return true when slider is visible
		 */
		Slider.prototype.isVisible = function () {
		    return this.container.style.display === 'block';
		};

		/**
		 * Render slider view
		 * @override
		 * @param {string} colorStr - hex string color from parent view (Layout)
		 */
		Slider.prototype.render = function (colorStr) {
		    var that = this,
		        container = that.container,
		        options = that.options,
		        html = tmpl.layout,
		        rgb,
		        hsv;

		    if (!colorutil.isValidRGB(colorStr)) {
		        return;
		    }

		    html = html.replace(/{{slider}}/, tmpl.slider);
		    html = html.replace(/{{huebar}}/, tmpl.huebar);
		    html = html.replace(/{{cssPrefix}}/g, options.cssPrefix);

		    that.container.innerHTML = html;

		    that.sliderHandleElement = domutil.find('.' + options.cssPrefix + 'slider-handle', container);
		    that.huebarHandleElement = domutil.find('.' + options.cssPrefix + 'huebar-handle', container);
		    that.baseColorElement = domutil.find('.' + options.cssPrefix + 'slider-basecolor', container);

		    rgb = colorutil.hexToRGB(colorStr);
		    hsv = colorutil.rgbToHSV.apply(null, rgb);

		    this.moveHue(hsv[0], true);
		    this.moveSaturationAndValue(hsv[1], hsv[2], true);
		};

		/**
		 * Move colorslider by newLeft(X), newTop(Y) value
		 * @private
		 * @param {number} newLeft - left pixel value to move handle
		 * @param {number} newTop - top pixel value to move handle
		 * @param {boolean} [silent=false] - set true then not fire custom event
		 */
		Slider.prototype._moveColorSliderHandle = function (newLeft, newTop, silent) {
		    var handle = this.sliderHandleElement,
		        handleColor;

		    // Check position limitation.
		    newTop = Math.max(COLORSLIDER_POS_LIMIT_RANGE[0], newTop);
		    newTop = Math.min(COLORSLIDER_POS_LIMIT_RANGE[1], newTop);
		    newLeft = Math.max(COLORSLIDER_POS_LIMIT_RANGE[0], newLeft);
		    newLeft = Math.min(COLORSLIDER_POS_LIMIT_RANGE[1], newLeft);

		    svgvml.setTranslateXY(handle, newLeft, newTop);

		    handleColor = newTop > 50 ? 'white' : 'black';
		    svgvml.setStrokeColor(handle, handleColor);

		    if (!silent) {
		        this.fire('_selectColor', {
		            color: colorutil.rgbToHEX.apply(null, this.getRGB())
		        });
		    }
		};

		/**
		 * Move colorslider by supplied saturation and values.
		 *
		 * The movement of color slider handle follow HSV cylinder model. {@link https://en.wikipedia.org/wiki/HSL_and_HSV}
		 * @param {number} saturation - the percent of saturation (0% ~ 100%)
		 * @param {number} value - the percent of saturation (0% ~ 100%)
		 * @param {boolean} [silent=false] - set true then not fire custom event
		 */
		Slider.prototype.moveSaturationAndValue = function (saturation, value, silent) {
		    var absMin, maxValue, newLeft, newTop;

		    saturation = saturation || 0;
		    value = value || 0;

		    absMin = Math.abs(COLORSLIDER_POS_LIMIT_RANGE[0]);
		    maxValue = COLORSLIDER_POS_LIMIT_RANGE[1];

		    // subtract absMin value because current color position is not left, top of handle element.
		    // The saturation. from left 0 to right 100
		    newLeft = saturation * maxValue / 100 - absMin;
		    // The Value. from top 100 to bottom 0. that why newTop subtract by maxValue.
		    newTop = maxValue - value * maxValue / 100 - absMin;

		    this._moveColorSliderHandle(newLeft, newTop, silent);
		};

		/**
		 * Move color slider handle to supplied position
		 *
		 * The number of X, Y must be related value from color slider container
		 * @private
		 * @param {number} x - the pixel value to move handle
		 * @param {number} y - the pixel value to move handle
		 */
		Slider.prototype._moveColorSliderByPosition = function (x, y) {
		    var offset = COLORSLIDER_POS_LIMIT_RANGE[0];
		    this._moveColorSliderHandle(x + offset, y + offset);
		};

		/**
		 * Get saturation and value value.
		 * @returns {number[]} saturation and value
		 */
		Slider.prototype.getSaturationAndValue = function () {
		    var absMin = Math.abs(COLORSLIDER_POS_LIMIT_RANGE[0]),
		        maxValue = absMin + COLORSLIDER_POS_LIMIT_RANGE[1],
		        position = svgvml.getTranslateXY(this.sliderHandleElement),
		        saturation,
		        value;

		    saturation = (position[1] + absMin) / maxValue * 100;
		    // The value of HSV color model is inverted. top 100 ~ bottom 0. so subtract by 100
		    value = 100 - (position[0] + absMin) / maxValue * 100;

		    return [saturation, value];
		};

		/**
		 * Move hue handle supplied pixel value
		 * @private
		 * @param {number} newTop - pixel to move hue handle
		 * @param {boolean} [silent=false] - set true then not fire custom event
		 */
		Slider.prototype._moveHueHandle = function (newTop, silent) {
		    var hueHandleElement = this.huebarHandleElement,
		        baseColorElement = this.baseColorElement,
		        newGradientColor,
		        hexStr;

		    newTop = Math.max(HUEBAR_POS_LIMIT_RANGE[0], newTop);
		    newTop = Math.min(HUEBAR_POS_LIMIT_RANGE[1], newTop);

		    svgvml.setTranslateY(hueHandleElement, newTop);

		    newGradientColor = colorutil.hsvToRGB(this.getHue(), 100, 100);
		    hexStr = colorutil.rgbToHEX.apply(null, newGradientColor);

		    svgvml.setGradientColorStop(baseColorElement, hexStr);

		    if (!silent) {
		        this.fire('_selectColor', {
		            color: colorutil.rgbToHEX.apply(null, this.getRGB())
		        });
		    }
		};

		/**
		 * Move hue bar handle by supplied degree
		 * @param {number} degree - (0 ~ 359.9 degree)
		 * @param {boolean} [silent=false] - set true then not fire custom event
		 */
		Slider.prototype.moveHue = function (degree, silent) {
		    var newTop = 0,
		        absMin,
		        maxValue;

		    absMin = Math.abs(HUEBAR_POS_LIMIT_RANGE[0]);
		    maxValue = absMin + HUEBAR_POS_LIMIT_RANGE[1];

		    degree = degree || 0;
		    newTop = maxValue * degree / HUE_WHEEL_MAX - absMin;

		    this._moveHueHandle(newTop, silent);
		};

		/**
		 * Move hue bar handle by supplied percent
		 * @private
		 * @param {number} y - pixel value to move hue handle
		 */
		Slider.prototype._moveHueByPosition = function (y) {
		    var offset = HUEBAR_POS_LIMIT_RANGE[0];

		    this._moveHueHandle(y + offset);
		};

		/**
		 * Get huebar handle position by color degree
		 * @returns {number} degree (0 ~ 359.9 degree)
		 */
		Slider.prototype.getHue = function () {
		    var handle = this.huebarHandleElement,
		        position = svgvml.getTranslateXY(handle),
		        absMin,
		        maxValue;

		    absMin = Math.abs(HUEBAR_POS_LIMIT_RANGE[0]);
		    maxValue = absMin + HUEBAR_POS_LIMIT_RANGE[1];

		    // maxValue : 359.99 = pos.y : x
		    return (position[0] + absMin) * HUE_WHEEL_MAX / maxValue;
		};

		/**
		 * Get HSV value from slider
		 * @returns {number[]} hsv values
		 */
		Slider.prototype.getHSV = function () {
		    var sv = this.getSaturationAndValue(),
		        h = this.getHue();

		    return [h].concat(sv);
		};

		/**
		 * Get RGB value from slider
		 * @returns {number[]} RGB value
		 */
		Slider.prototype.getRGB = function () {
		    return colorutil.hsvToRGB.apply(null, this.getHSV());
		};

		/**********
		 * Drag event handler
		 **********/

		/**
		 * Cache immutable data when dragging or click view
		 * @param {object} event - Click, DragStart event.
		 * @returns {object} cached data.
		 */
		Slider.prototype._prepareColorSliderForMouseEvent = function (event) {
		    var options = this.options,
		        sliderPart = domutil.closest(event.target, '.' + options.cssPrefix + 'slider-part'),
		        cache;

		    cache = this._dragDataCache = {
		        isColorSlider: domutil.hasClass(sliderPart, options.cssPrefix + 'slider-left'),
		        parentElement: sliderPart
		    };

		    return cache;
		};

		/**
		 * Click event handler
		 * @param {object} clickEvent - Click event from Drag module
		 */
		Slider.prototype._onClick = function (clickEvent) {
		    var cache = this._prepareColorSliderForMouseEvent(clickEvent),
		        mousePos = domevent.getMousePosition(clickEvent.originEvent, cache.parentElement);

		    if (cache.isColorSlider) {
		        this._moveColorSliderByPosition(mousePos[0], mousePos[1]);
		    } else {
		        this._moveHueByPosition(mousePos[1]);
		    }

		    this._dragDataCache = null;
		};

		/**
		 * DragStart event handler
		 * @param {object} dragStartEvent - dragStart event data from Drag#dragStart
		 */
		Slider.prototype._onDragStart = function (dragStartEvent) {
		    this._prepareColorSliderForMouseEvent(dragStartEvent);
		};

		/**
		 * Drag event handler
		 * @param {Drag#drag} dragEvent - drag event data
		 */
		Slider.prototype._onDrag = function (dragEvent) {
		    var cache = this._dragDataCache,
		        mousePos = domevent.getMousePosition(dragEvent.originEvent, cache.parentElement);

		    if (cache.isColorSlider) {
		        this._moveColorSliderByPosition(mousePos[0], mousePos[1]);
		    } else {
		        this._moveHueByPosition(mousePos[1]);
		    }
		};

		/**
		 * Drag#dragEnd event handler
		 */
		Slider.prototype._onDragEnd = function () {
		    this._dragDataCache = null;
		};

		util.CustomEvents.mixin(Slider);

		module.exports = Slider;

	/***/ }),
	/* 19 */
	/***/ (function(module, exports, __webpack_require__) {

		/**
		 * @fileoverview module for manipulate SVG or VML object
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var util = __webpack_require__(8);
		var PARSE_TRANSLATE_NUM_REGEX = /[\.\-0-9]+/g;
		var SVG_HUE_HANDLE_RIGHT_POS = -6;

		/* istanbul ignore next */
		var svgvml = {
		    /**
		     * Return true when browser is below IE8.
		     * @returns {boolean} is old browser?
		     */
		    isOldBrowser: function () {
		        var _isOldBrowser = svgvml._isOldBrowser;

		        if (!util.isExisty(_isOldBrowser)) {
		            svgvml._isOldBrowser = _isOldBrowser = util.browser.msie && util.browser.version < 9;
		        }

		        return _isOldBrowser;
		    },

		    /**
		     * Get translate transform value
		     * @param {SVG|VML} obj - svg or vml object that want to know translate x, y
		     * @returns {number[]} translated coordinates [x, y]
		     */
		    getTranslateXY: function (obj) {
		        var temp;

		        if (svgvml.isOldBrowser()) {
		            temp = obj.style;

		            return [parseFloat(temp.top), parseFloat(temp.left)];
		        }

		        temp = obj.getAttribute('transform');

		        if (!temp) {
		            return [0, 0];
		        }

		        temp = temp.match(PARSE_TRANSLATE_NUM_REGEX);

		        // need caution for difference of VML, SVG coordinates system.
		        // translate command need X coords in first parameter. but VML is use CSS coordinate system(top, left)
		        return [parseFloat(temp[1]), parseFloat(temp[0])];
		    },

		    /**
		     * Set translate transform value
		     * @param {SVG|VML} obj - SVG or VML object to setting translate transform.
		     * @param {number} x - translate X value
		     * @param {number} y - translate Y value
		     */
		    setTranslateXY: function (obj, x, y) {
		        if (svgvml.isOldBrowser()) {
		            obj.style.left = x + 'px';
		            obj.style.top = y + 'px';
		        } else {
		            obj.setAttribute('transform', 'translate(' + x + ',' + y + ')');
		        }
		    },

		    /**
		     * Set translate only Y value
		     * @param {SVG|VML} obj - SVG or VML object to setting translate transform.
		     * @param {number} y - translate Y value
		     */
		    setTranslateY: function (obj, y) {
		        if (svgvml.isOldBrowser()) {
		            obj.style.top = y + 'px';
		        } else {
		            obj.setAttribute('transform', 'translate(' + SVG_HUE_HANDLE_RIGHT_POS + ',' + y + ')');
		        }
		    },

		    /**
		     * Set stroke color to SVG or VML object
		     * @param {SVG|VML} obj - SVG or VML object to setting stroke color
		     * @param {string} colorStr - color string
		     */
		    setStrokeColor: function (obj, colorStr) {
		        if (svgvml.isOldBrowser()) {
		            obj.strokecolor = colorStr;
		        } else {
		            obj.setAttribute('stroke', colorStr);
		        }
		    },

		    /**
		     * Set gradient stop color to SVG, VML object.
		     * @param {SVG|VML} obj - SVG, VML object to applying gradient stop color
		     * @param {string} colorStr - color string
		     */
		    setGradientColorStop: function (obj, colorStr) {
		        if (svgvml.isOldBrowser()) {
		            obj.color = colorStr;
		        } else {
		            obj.setAttribute('stop-color', colorStr);
		        }
		    }

		};

		module.exports = svgvml;

	/***/ }),
	/* 20 */
	/***/ (function(module, exports, __webpack_require__) {

		/* WEBPACK VAR INJECTION */(function(global) {/**
		 * @fileoverview Slider template
		 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
		 */

		'use strict';

		var util = __webpack_require__(8);

		var layout = ['<div class="{{cssPrefix}}slider-left {{cssPrefix}}slider-part">{{slider}}</div>', '<div class="{{cssPrefix}}slider-right {{cssPrefix}}slider-part">{{huebar}}</div>'].join('\n');

		var SVGSlider = ['<svg class="{{cssPrefix}}svg {{cssPrefix}}svg-slider">', '<defs>', '<linearGradient id="{{cssPrefix}}svg-fill-color" x1="0%" y1="0%" x2="100%" y2="0%">', '<stop offset="0%" stop-color="rgb(255,255,255)" />', '<stop class="{{cssPrefix}}slider-basecolor" offset="100%" stop-color="rgb(255,0,0)" />', '</linearGradient>', '<linearGradient id="{{cssPrefix}}svn-fill-black" x1="0%" y1="0%" x2="0%" y2="100%">', '<stop offset="0%" style="stop-color:rgb(0,0,0);stop-opacity:0" />', '<stop offset="100%" style="stop-color:rgb(0,0,0);stop-opacity:1" />', '</linearGradient>', '</defs>', '<rect width="100%" height="100%" fill="url(#{{cssPrefix}}svg-fill-color)"></rect>', '<rect width="100%" height="100%" fill="url(#{{cssPrefix}}svn-fill-black)"></rect>', '<path transform="translate(0,0)" class="{{cssPrefix}}slider-handle" d="M0 7.5 L15 7.5 M7.5 15 L7.5 0 M2 7 a5.5 5.5 0 1 1 0 1 Z" stroke="black" stroke-width="0.75" fill="none" />', '</svg>'].join('\n');

		var VMLSlider = ['<div class="{{cssPrefix}}vml-slider">', '<v:rect strokecolor="none" class="{{cssPrefix}}vml {{cssPrefix}}vml-slider-bg">', '<v:fill class="{{cssPrefix}}vml {{cssPrefix}}slider-basecolor" type="gradient" method="none" color="#ff0000" color2="#fff" angle="90" />', '</v:rect>', '<v:rect strokecolor="#ccc" class="{{cssPrefix}}vml {{cssPrefix}}vml-slider-bg">', '<v:fill type="gradient" method="none" color="black" color2="white" o:opacity2="0%" class="{{cssPrefix}}vml" />', '</v:rect>', '<v:shape class="{{cssPrefix}}vml {{cssPrefix}}slider-handle" coordsize="1 1" style="width:1px;height:1px;"' + 'path="m 0,7 l 14,7 m 7,14 l 7,0 ar 12,12 2,2 z" filled="false" stroked="true" />', '</div>'].join('\n');

		var SVGHuebar = ['<svg class="{{cssPrefix}}svg {{cssPrefix}}svg-huebar">', '<defs>', '<linearGradient id="g" x1="0%" y1="0%" x2="0%" y2="100%">', '<stop offset="0%" stop-color="rgb(255,0,0)" />', '<stop offset="16.666%" stop-color="rgb(255,255,0)" />', '<stop offset="33.333%" stop-color="rgb(0,255,0)" />', '<stop offset="50%" stop-color="rgb(0,255,255)" />', '<stop offset="66.666%" stop-color="rgb(0,0,255)" />', '<stop offset="83.333%" stop-color="rgb(255,0,255)" />', '<stop offset="100%" stop-color="rgb(255,0,0)" />', '</linearGradient>', '</defs>', '<rect width="18px" height="100%" fill="url(#g)"></rect>', '<path transform="translate(-6,-3)" class="{{cssPrefix}}huebar-handle" d="M0 0 L4 4 L0 8 L0 0 Z" fill="black" stroke="none" />', '</svg>'].join('\n');

		var VMLHuebar = ['<div class="{{cssPrefix}}vml-huebar">', '<v:rect strokecolor="#ccc" class="{{cssPrefix}}vml {{cssPrefix}}vml-huebar-bg">', '<v:fill type="gradient" method="none" colors="' + '0% rgb(255,0,0), 16.666% rgb(255,255,0), 33.333% rgb(0,255,0), 50% rgb(0,255,255), 66.666% rgb(0,0,255), 83.333% rgb(255,0,255), 100% rgb(255,0,0)' + '" angle="180" class="{{cssPrefix}}vml" />', '</v:rect>', '<v:shape class="{{cssPrefix}}vml {{cssPrefix}}huebar-handle" coordsize="1 1" style="width:1px;height:1px;position:absolute;z-index:1;right:22px;top:-3px;"' + 'path="m 0,0 l 4,4 l 0,8 l 0,0 z" filled="true" fillcolor="black" stroked="false" />', '</div>'].join('\n');

		var isOldBrowser = util.browser.msie && util.browser.version < 9;

		if (isOldBrowser) {
		    global.document.namespaces.add('v', 'urn:schemas-microsoft-com:vml');
		}

		module.exports = {
		    layout: layout,
		    slider: isOldBrowser ? VMLSlider : SVGSlider,
		    huebar: isOldBrowser ? VMLHuebar : SVGHuebar
		};
		/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))

	/***/ })
	/******/ ])
	});
	;

/***/ }),
/* 83 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _util = __webpack_require__(72);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	/**
	 * Range control class
	 * @class
	 * @ignore
	 */
	var Range = function () {
	    function Range(rangeElement) {
	        var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

	        _classCallCheck(this, Range);

	        this._value = options.value || 0;
	        this.rangeElement = rangeElement;
	        this._drawRangeElement();

	        this.rangeWidth = (0, _util.toInteger)(window.getComputedStyle(rangeElement, null).width) - 12;
	        this._min = options.min || 0;
	        this._max = options.max || 100;
	        this._absMax = this._min * -1 + this._max;
	        this.realTimeEvent = options.realTimeEvent || false;

	        this._addClickEvent();
	        this._addDragEvent();
	        this.value = options.value;
	        this.trigger('change');
	    }

	    /**
	     * Set range max value and re position cursor
	     * @param {number} maxValue - max value
	     */


	    _createClass(Range, [{
	        key: 'trigger',


	        /**
	         * event tirigger
	         * @param {string} type - type
	         */
	        value: function trigger(type) {
	            this.fire(type, this._value);
	        }

	        /**
	         * Make range element
	         * @private
	         */

	    }, {
	        key: '_drawRangeElement',
	        value: function _drawRangeElement() {
	            this.rangeElement.classList.add('tui-image-editor-range');

	            this.bar = document.createElement('div');
	            this.bar.className = 'tui-image-editor-virtual-range-bar';

	            this.subbar = document.createElement('div');
	            this.subbar.className = 'tui-image-editor-virtual-range-subbar';

	            this.pointer = document.createElement('div');
	            this.pointer.className = 'tui-image-editor-virtual-range-pointer';

	            this.bar.appendChild(this.subbar);
	            this.bar.appendChild(this.pointer);
	            this.rangeElement.appendChild(this.bar);
	        }

	        /**
	         * Add Range click event
	         * @private
	         */

	    }, {
	        key: '_addClickEvent',
	        value: function _addClickEvent() {
	            var _this = this;

	            this.rangeElement.addEventListener('click', function (event) {
	                event.stopPropagation();
	                if (event.target.className !== 'tui-image-editor-range') {
	                    return;
	                }
	                var touchPx = event.offsetX;
	                var ratio = touchPx / _this.rangeWidth;
	                var value = _this._absMax * ratio + _this._min;
	                _this.pointer.style.left = ratio * _this.rangeWidth + 'px';
	                _this.subbar.style.right = (1 - ratio) * _this.rangeWidth + 'px';
	                _this._value = value;

	                _this.fire('change', value);
	            });
	        }

	        /**
	         * Add Range drag event
	         * @private
	         */

	    }, {
	        key: '_addDragEvent',
	        value: function _addDragEvent() {
	            var _this2 = this;

	            this.pointer.addEventListener('mousedown', function (event) {
	                _this2.firstPosition = event.screenX;
	                _this2.firstLeft = (0, _util.toInteger)(_this2.pointer.style.left) || 0;
	                _this2.dragEventHandler = {
	                    changeAngle: _this2._changeAngle.bind(_this2),
	                    stopChangingAngle: _this2._stopChangingAngle.bind(_this2)
	                };

	                document.addEventListener('mousemove', _this2.dragEventHandler.changeAngle);
	                document.addEventListener('mouseup', _this2.dragEventHandler.stopChangingAngle);
	            });
	        }

	        /**
	         * change angle event
	         * @param {object} event - change event
	         * @private
	         */

	    }, {
	        key: '_changeAngle',
	        value: function _changeAngle(event) {
	            var changePosition = event.screenX;
	            var diffPosition = changePosition - this.firstPosition;
	            var touchPx = this.firstLeft + diffPosition;
	            touchPx = touchPx > this.rangeWidth ? this.rangeWidth : touchPx;
	            touchPx = touchPx < 0 ? 0 : touchPx;

	            this.pointer.style.left = touchPx + 'px';
	            this.subbar.style.right = this.rangeWidth - touchPx + 'px';
	            var ratio = touchPx / this.rangeWidth;
	            var value = this._absMax * ratio + this._min;

	            this._value = value;

	            if (this.realTimeEvent) {
	                this.fire('change', value);
	            }
	        }

	        /**
	         * stop change angle event
	         * @private
	         */

	    }, {
	        key: '_stopChangingAngle',
	        value: function _stopChangingAngle() {
	            this.fire('change', this._value);
	            document.removeEventListener('mousemove', this.dragEventHandler.changeAngle);
	            document.removeEventListener('mouseup', this.dragEventHandler.stopChangingAngle);
	        }
	    }, {
	        key: 'max',
	        set: function set(maxValue) {
	            this._max = maxValue;
	            this._absMax = this._min * -1 + this._max;
	            this.value = this._value;
	        },
	        get: function get() {
	            return this._max;
	        }

	        /**
	         * Get range value
	         * @returns {Number} range value
	         */

	    }, {
	        key: 'value',
	        get: function get() {
	            return this._value;
	        }

	        /**
	         * Set range value
	         * @param {Number} value range value
	         * @param {Boolean} fire whether fire custom event or not
	         */
	        ,
	        set: function set(value) {
	            var absValue = value - this._min;
	            var leftPosition = absValue * this.rangeWidth / this._absMax;

	            if (this.rangeWidth < leftPosition) {
	                leftPosition = this.rangeWidth;
	            }

	            this.pointer.style.left = leftPosition + 'px';
	            this.subbar.style.right = this.rangeWidth - leftPosition + 'px';
	            this._value = value;
	        }
	    }]);

	    return Range;
	}();

	_tuiCodeSnippet2.default.CustomEvents.mixin(Range);
	exports.default = Range;

/***/ }),
/* 84 */
/***/ (function(module, exports) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	  value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	/**
	 * Submenu Base Class
	 * @class
	 * @ignore
	 */
	var Submenu = function () {
	  function Submenu(subMenuElement, _ref) {
	    var name = _ref.name,
	        iconStyle = _ref.iconStyle,
	        menuBarPosition = _ref.menuBarPosition,
	        templateHtml = _ref.templateHtml;

	    _classCallCheck(this, Submenu);

	    this.selector = function (str) {
	      return subMenuElement.querySelector(str);
	    };
	    this.menuBarPosition = menuBarPosition;
	    this.toggleDirection = menuBarPosition === 'top' ? 'down' : 'up';
	    this._makeSubMenuElement(subMenuElement, {
	      name: name,
	      iconStyle: iconStyle,
	      templateHtml: templateHtml
	    });
	  }

	  /**
	   * Get butten type
	   * @param {HTMLElement} button - event target element
	   * @param {array} buttonNames - Array of button names
	   * @returns {string} - button type
	   */


	  _createClass(Submenu, [{
	    key: 'getButtonType',
	    value: function getButtonType(button, buttonNames) {
	      return button.className.match(RegExp('(' + buttonNames.join('|') + ')'))[0];
	    }

	    /**
	     * Get butten type
	     * @param {HTMLElement} target - event target element
	     * @param {string} removeClass - remove class name
	     * @param {string} addClass - add class name
	     */

	  }, {
	    key: 'changeClass',
	    value: function changeClass(target, removeClass, addClass) {
	      target.classList.remove(removeClass);
	      target.classList.add(addClass);
	    }

	    /**
	     * Interface method whose implementation is optional.
	     * Returns the menu to its default state.
	     */

	  }, {
	    key: 'changeStandbyMode',
	    value: function changeStandbyMode() {}

	    /**
	     * Interface method whose implementation is optional.
	     * Executed when the menu starts.
	     */

	  }, {
	    key: 'changeStartMode',
	    value: function changeStartMode() {}

	    /**
	     * Make submenu dom element
	     * @param {HTMLElement} subMenuElement - subment dom element
	     * @param {Object} iconStyle -  icon style
	     * @private
	     */

	  }, {
	    key: '_makeSubMenuElement',
	    value: function _makeSubMenuElement(subMenuElement, _ref2) {
	      var name = _ref2.name,
	          iconStyle = _ref2.iconStyle,
	          templateHtml = _ref2.templateHtml;

	      var iconSubMenu = document.createElement('div');
	      iconSubMenu.className = 'tui-image-editor-menu-' + name;
	      iconSubMenu.innerHTML = templateHtml({ iconStyle: iconStyle });

	      subMenuElement.appendChild(iconSubMenu);
	    }
	  }]);

	  return Submenu;
	}();

	exports.default = Submenu;

/***/ }),
/* 85 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var _ref$iconStyle = _ref.iconStyle,
	        normal = _ref$iconStyle.normal,
	        active = _ref$iconStyle.active;
	    return "\n    <ul class=\"tui-image-editor-submenu-item\">\n        <li id=\"tie-shape-button\">\n            <div class=\"tui-image-editor-button rect\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-shape-rectangle\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-shape-rectangle\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label> Rectangle </label>\n            </div>\n            <div class=\"tui-image-editor-button circle\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-shape-circle\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-shape-circle\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label> Circle </label>\n            </div>\n            <div class=\"tui-image-editor-button triangle\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-shape-triangle\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-shape-triangle\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label> Triangle </label>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition\">\n            <div></div>\n        </li>\n        <li id=\"tie-shape-color-button\">\n            <div id=\"tie-color-fill\" title=\"fill\"></div>\n            <div id=\"tie-color-stroke\" title=\"stroke\"></div>\n        </li>\n        <li class=\"tui-image-editor-partition only-left-right\">\n            <div></div>\n        </li>\n        <li class=\"tui-image-editor-newline tui-image-editor-range-wrap\">\n            <label class=\"range\">Stroke</label>\n            <div id=\"tie-stroke-range\"></div>\n            <input id=\"tie-stroke-range-value\" class=\"tui-image-editor-range-value\" value=\"0\" />\n        </li>\n    </ul>\n";
	};

/***/ }),
/* 86 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _submenuBase = __webpack_require__(84);

	var _submenuBase2 = _interopRequireDefault(_submenuBase);

	var _crop = __webpack_require__(87);

	var _crop2 = _interopRequireDefault(_crop);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

	/**
	 * Crop ui class
	 * @class
	 * @ignore
	 */
	var Crop = function (_Submenu) {
	    _inherits(Crop, _Submenu);

	    function Crop(subMenuElement, _ref) {
	        var iconStyle = _ref.iconStyle,
	            menuBarPosition = _ref.menuBarPosition;

	        _classCallCheck(this, Crop);

	        var _this = _possibleConstructorReturn(this, (Crop.__proto__ || Object.getPrototypeOf(Crop)).call(this, subMenuElement, {
	            name: 'crop',
	            iconStyle: iconStyle,
	            menuBarPosition: menuBarPosition,
	            templateHtml: _crop2.default
	        }));

	        _this.status = 'active';
	        _this._els = {
	            apply: _this.selector('#tie-crop-button .apply'),
	            cancel: _this.selector('#tie-crop-button .cancel')
	        };
	        return _this;
	    }

	    /**
	     * Add event for crop
	     * @param {Object} actions - actions for crop
	     *   @param {Function} actions.crop - crop action
	     *   @param {Function} actions.cancel - cancel action
	     */


	    _createClass(Crop, [{
	        key: 'addEvent',
	        value: function addEvent(actions) {
	            var _this2 = this;

	            this.actions = actions;
	            this._els.apply.addEventListener('click', function () {
	                _this2.actions.crop();
	                _this2._els.apply.classList.remove('active');
	            });

	            this._els.cancel.addEventListener('click', function () {
	                _this2.actions.cancel();
	                _this2._els.apply.classList.remove('active');
	            });
	        }

	        /**
	         * Executed when the menu starts.
	         */

	    }, {
	        key: 'changeStartMode',
	        value: function changeStartMode() {
	            this.actions.modeChange('crop');
	        }

	        /**
	         * Returns the menu to its default state.
	         */

	    }, {
	        key: 'changeStandbyMode',
	        value: function changeStandbyMode() {
	            this.actions.stopDrawingMode();
	        }

	        /**
	         * Change apply button status
	         * @param {Boolean} enableStatus - apply button status
	         */

	    }, {
	        key: 'changeApplyButtonStatus',
	        value: function changeApplyButtonStatus(enableStatus) {
	            if (enableStatus) {
	                this._els.apply.classList.add('active');
	            } else {
	                this._els.apply.classList.remove('active');
	            }
	        }
	    }]);

	    return Crop;
	}(_submenuBase2.default);

	exports.default = Crop;

/***/ }),
/* 87 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var _ref$iconStyle = _ref.iconStyle,
	        normal = _ref$iconStyle.normal,
	        active = _ref$iconStyle.active;
	    return "\n    <ul class=\"tui-image-editor-submenu-item\">\n        <li id=\"tie-crop-button\" class=\"apply\">\n            <div class=\"tui-image-editor-button apply\">\n                <svg class=\"svg_ic-menu\">\n                    <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-apply\" class=\"normal\"/>\n                    <use xlink:href=\"" + active.path + "#" + active.name + "-ic-apply\" class=\"active\"/>\n                </svg>\n                <label>\n                    Apply\n                </label>\n            </div>\n            <div class=\"tui-image-editor-button cancel\">\n                <svg class=\"svg_ic-menu\">\n                    <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-cancel\" class=\"normal\"/>\n                    <use xlink:href=\"" + active.path + "#" + active.name + "-ic-cancel\" class=\"active\"/>\n                </svg>\n                <label>\n                    Cancel\n                </label>\n            </div>\n        </li>\n    </ul>\n";
	};

/***/ }),
/* 88 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _submenuBase = __webpack_require__(84);

	var _submenuBase2 = _interopRequireDefault(_submenuBase);

	var _flip = __webpack_require__(89);

	var _flip2 = _interopRequireDefault(_flip);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

	/**
	 * Flip ui class
	 * @class
	 * @ignore
	 */
	var Flip = function (_Submenu) {
	    _inherits(Flip, _Submenu);

	    function Flip(subMenuElement, _ref) {
	        var iconStyle = _ref.iconStyle,
	            menuBarPosition = _ref.menuBarPosition;

	        _classCallCheck(this, Flip);

	        var _this = _possibleConstructorReturn(this, (Flip.__proto__ || Object.getPrototypeOf(Flip)).call(this, subMenuElement, {
	            name: 'flip',
	            iconStyle: iconStyle,
	            menuBarPosition: menuBarPosition,
	            templateHtml: _flip2.default
	        }));

	        _this.flipStatus = false;

	        _this._els = {
	            flipButton: _this.selector('#tie-flip-button')
	        };
	        return _this;
	    }

	    /**
	     * Add event for flip
	     * @param {Object} actions - actions for flip
	     *   @param {Function} actions.flip - flip action
	     */


	    _createClass(Flip, [{
	        key: 'addEvent',
	        value: function addEvent(actions) {
	            this._actions = actions;
	            this._els.flipButton.addEventListener('click', this._changeFlip.bind(this));
	        }

	        /**
	         * change Flip status
	         * @param {object} event - change event
	         * @private
	         */

	    }, {
	        key: '_changeFlip',
	        value: function _changeFlip(event) {
	            var _this2 = this;

	            var button = event.target.closest('.tui-image-editor-button');
	            if (button) {
	                var flipType = this.getButtonType(button, ['flipX', 'flipY', 'resetFlip']);
	                if (!this.flipStatus && flipType === 'resetFlip') {
	                    return;
	                }

	                this._actions.flip(flipType).then(function (flipStatus) {
	                    var flipClassList = _this2._els.flipButton.classList;
	                    _this2.flipStatus = false;

	                    flipClassList.remove('resetFlip');
	                    _tuiCodeSnippet2.default.forEach(['flipX', 'flipY'], function (type) {
	                        flipClassList.remove(type);
	                        if (flipStatus[type]) {
	                            flipClassList.add(type);
	                            flipClassList.add('resetFlip');
	                            _this2.flipStatus = true;
	                        }
	                    });
	                });
	            }
	        }
	    }]);

	    return Flip;
	}(_submenuBase2.default);

	exports.default = Flip;

/***/ }),
/* 89 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var _ref$iconStyle = _ref.iconStyle,
	        normal = _ref$iconStyle.normal,
	        active = _ref$iconStyle.active;
	    return "\n    <ul id=\"tie-flip-button\" class=\"tui-image-editor-submenu-item\">\n        <li>\n            <div class=\"tui-image-editor-button flipX\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-flip-x\" class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-flip-x\" class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Flip X\n                </label>\n            </div>\n            <div class=\"tui-image-editor-button flipY\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-flip-y\" class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-flip-y\" class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Flip Y\n                </label>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition\">\n            <div></div>\n        </li>\n        <li>\n            <div class=\"tui-image-editor-button resetFlip\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-flip-reset\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-flip-reset\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Reset\n                </label>\n            </div>\n        </li>\n    </ul>\n";
	};

/***/ }),
/* 90 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _range = __webpack_require__(83);

	var _range2 = _interopRequireDefault(_range);

	var _submenuBase = __webpack_require__(84);

	var _submenuBase2 = _interopRequireDefault(_submenuBase);

	var _rotate = __webpack_require__(91);

	var _rotate2 = _interopRequireDefault(_rotate);

	var _util = __webpack_require__(72);

	var _consts = __webpack_require__(73);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

	var CLOCKWISE = 30;
	var COUNTERCLOCKWISE = -30;

	/**
	 * Rotate ui class
	 * @class
	 * @ignore
	 */

	var Rotate = function (_Submenu) {
	    _inherits(Rotate, _Submenu);

	    function Rotate(subMenuElement, _ref) {
	        var iconStyle = _ref.iconStyle,
	            menuBarPosition = _ref.menuBarPosition;

	        _classCallCheck(this, Rotate);

	        var _this = _possibleConstructorReturn(this, (Rotate.__proto__ || Object.getPrototypeOf(Rotate)).call(this, subMenuElement, {
	            name: 'rotate',
	            iconStyle: iconStyle,
	            menuBarPosition: menuBarPosition,
	            templateHtml: _rotate2.default
	        }));

	        _this._els = {
	            rotateButton: _this.selector('#tie-retate-button'),
	            rotateRange: new _range2.default(_this.selector('#tie-rotate-range'), _consts.defaultRotateRangeValus),
	            rotateRangeValue: _this.selector('#tie-ratate-range-value')
	        };
	        return _this;
	    }

	    /**
	     * Add event for rotate
	     * @param {Object} actions - actions for crop
	     *   @param {Function} actions.rotate - rotate action
	     *   @param {Function} actions.setAngle - set angle action
	     */


	    _createClass(Rotate, [{
	        key: 'addEvent',
	        value: function addEvent(actions) {
	            // {rotate, setAngle}
	            this.actions = actions;
	            this._els.rotateButton.addEventListener('click', this._changeRotateForButton.bind(this));
	            this._els.rotateRange.on('change', this._changeRotateForRange.bind(this));
	            this._els.rotateRangeValue.setAttribute('readonly', true);
	        }

	        /**
	         * Change rotate for range
	         * @param {number} value - angle value
	         * @private
	         */

	    }, {
	        key: '_changeRotateForRange',
	        value: function _changeRotateForRange(value) {
	            var angle = (0, _util.toInteger)(value);
	            this._els.rotateRangeValue.value = angle;
	            this.actions.setAngle(angle);
	        }

	        /**
	         * Change rotate for button
	         * @param {object} event - add button event object
	         * @private
	         */

	    }, {
	        key: '_changeRotateForButton',
	        value: function _changeRotateForButton(event) {
	            var button = event.target.closest('.tui-image-editor-button');
	            if (button) {
	                var rotateType = this.getButtonType(button, ['counterclockwise', 'clockwise']);
	                var rotateAngle = {
	                    clockwise: CLOCKWISE,
	                    counterclockwise: COUNTERCLOCKWISE
	                }[rotateType];
	                this.actions.rotate(rotateAngle);
	            }
	        }
	    }]);

	    return Rotate;
	}(_submenuBase2.default);

	exports.default = Rotate;

/***/ }),
/* 91 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var _ref$iconStyle = _ref.iconStyle,
	        normal = _ref$iconStyle.normal,
	        active = _ref$iconStyle.active;
	    return "\n    <ul class=\"tui-image-editor-submenu-item\">\n        <li id=\"tie-retate-button\">\n            <div class=\"tui-image-editor-button clockwise\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-rotate-clockwise\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-rotate-clockwise\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label> 30 </label>\n            </div>\n            <div class=\"tui-image-editor-button counterclockwise\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-rotate-counterclockwise\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-rotate-counterclockwise\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label> -30 </label>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition only-left-right\">\n            <div></div>\n        </li>\n        <li class=\"tui-image-editor-newline tui-image-editor-range-wrap\">\n            <label class=\"range\">Range</label>\n            <div id=\"tie-rotate-range\"></div>\n            <input id=\"tie-ratate-range-value\" class=\"tui-image-editor-range-value\" value=\"0\" />\n        </li>\n    </ul>\n";
	};

/***/ }),
/* 92 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _range = __webpack_require__(83);

	var _range2 = _interopRequireDefault(_range);

	var _colorpicker = __webpack_require__(81);

	var _colorpicker2 = _interopRequireDefault(_colorpicker);

	var _submenuBase = __webpack_require__(84);

	var _submenuBase2 = _interopRequireDefault(_submenuBase);

	var _text = __webpack_require__(93);

	var _text2 = _interopRequireDefault(_text);

	var _util = __webpack_require__(72);

	var _consts = __webpack_require__(73);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

	/**
	 * Crop ui class
	 * @class
	 * @ignore
	 */
	var Text = function (_Submenu) {
	    _inherits(Text, _Submenu);

	    function Text(subMenuElement, _ref) {
	        var iconStyle = _ref.iconStyle,
	            menuBarPosition = _ref.menuBarPosition;

	        _classCallCheck(this, Text);

	        var _this = _possibleConstructorReturn(this, (Text.__proto__ || Object.getPrototypeOf(Text)).call(this, subMenuElement, {
	            name: 'text',
	            iconStyle: iconStyle,
	            menuBarPosition: menuBarPosition,
	            templateHtml: _text2.default
	        }));

	        _this.effect = {
	            bold: false,
	            italic: false,
	            underline: false
	        };
	        _this.align = 'left';
	        _this._els = {
	            textEffectButton: _this.selector('#tie-text-effect-button'),
	            textAlignButton: _this.selector('#tie-text-align-button'),
	            textColorpicker: new _colorpicker2.default(_this.selector('#tie-text-color'), '#ffbb3b', _this.toggleDirection),
	            textRange: new _range2.default(_this.selector('#tie-text-range'), _consts.defaultTextRangeValus),
	            textRangeValue: _this.selector('#tie-text-range-value')
	        };
	        return _this;
	    }

	    /**
	     * Add event for text
	     * @param {Object} actions - actions for text
	     *   @param {Function} actions.changeTextStyle - change text style
	     */


	    _createClass(Text, [{
	        key: 'addEvent',
	        value: function addEvent(actions) {
	            this.actions = actions;
	            this._els.textEffectButton.addEventListener('click', this._setTextEffectHandler.bind(this));
	            this._els.textAlignButton.addEventListener('click', this._setTextAlignHandler.bind(this));
	            this._els.textRange.on('change', this._changeTextRnageHandler.bind(this));
	            this._els.textRangeValue.value = this._els.textRange.value;
	            this._els.textRangeValue.setAttribute('readonly', true);
	            this._els.textColorpicker.on('change', this._changeColorHandler.bind(this));
	        }

	        /**
	         * Returns the menu to its default state.
	         */

	    }, {
	        key: 'changeStandbyMode',
	        value: function changeStandbyMode() {
	            this.actions.stopDrawingMode();
	        }

	        /**
	         * Executed when the menu starts.
	         */

	    }, {
	        key: 'changeStartMode',
	        value: function changeStartMode() {
	            this.actions.modeChange('text');
	        }

	        /**
	         * Get text color
	         * @returns {string} - text color
	         */

	    }, {
	        key: '_setTextEffectHandler',


	        /**
	         * text effect set handler
	         * @param {object} event - add button event object
	         * @private
	         */
	        value: function _setTextEffectHandler(event) {
	            var button = event.target.closest('.tui-image-editor-button');

	            var _button$className$mat = button.className.match(/(bold|italic|underline)/),
	                styleType = _button$className$mat[0];

	            var styleObj = {
	                'bold': { fontWeight: 'bold' },
	                'italic': { fontStyle: 'italic' },
	                'underline': { textDecoration: 'underline' }
	            }[styleType];

	            this.effect[styleType] = !this.effect[styleType];
	            button.classList.toggle('active');
	            this.actions.changeTextStyle(styleObj);
	        }

	        /**
	         * text effect set handler
	         * @param {object} event - add button event object
	         * @private
	         */

	    }, {
	        key: '_setTextAlignHandler',
	        value: function _setTextAlignHandler(event) {
	            var button = event.target.closest('.tui-image-editor-button');
	            if (button) {
	                var styleType = this.getButtonType(button, ['left', 'center', 'right']);

	                event.currentTarget.classList.remove(this.align);
	                if (this.align !== styleType) {
	                    event.currentTarget.classList.add(styleType);
	                }
	                this.actions.changeTextStyle({ textAlign: styleType });

	                this.align = styleType;
	            }
	        }

	        /**
	         * text align set handler
	         * @param {number} value - range value
	         * @private
	         */

	    }, {
	        key: '_changeTextRnageHandler',
	        value: function _changeTextRnageHandler(value) {
	            value = (0, _util.toInteger)(value);
	            if ((0, _util.toInteger)(this._els.textRangeValue.value) !== value) {
	                this.actions.changeTextStyle({
	                    fontSize: value
	                });
	                this._els.textRangeValue.value = value;
	            }
	        }

	        /**
	         * change color handler
	         * @param {string} color - change color string
	         * @private
	         */

	    }, {
	        key: '_changeColorHandler',
	        value: function _changeColorHandler(color) {
	            color = color || 'transparent';
	            this.actions.changeTextStyle({
	                'fill': color
	            });
	        }
	    }, {
	        key: 'textColor',
	        get: function get() {
	            return this._els.textColorpicker.color;
	        }

	        /**
	         * Get text size
	         * @returns {string} - text size
	         */

	    }, {
	        key: 'fontSize',
	        get: function get() {
	            return this._els.textRange.value;
	        }

	        /**
	         * Set text size
	         * @param {Number} value - text size
	         */
	        ,
	        set: function set(value) {
	            this._els.textRange.value = value;
	            this._els.textRangeValue.value = value;
	        }
	    }]);

	    return Text;
	}(_submenuBase2.default);

	exports.default = Text;

/***/ }),
/* 93 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var _ref$iconStyle = _ref.iconStyle,
	        normal = _ref$iconStyle.normal,
	        active = _ref$iconStyle.active;
	    return "\n    <ul class=\"tui-image-editor-submenu-item\">\n        <li id=\"tie-text-effect-button\">\n            <div class=\"tui-image-editor-button bold\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                    <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-text-bold\" class=\"normal\"/>\n                    <use xlink:href=\"" + active.path + "#" + active.name + "-ic-text-bold\" class=\"active\"/>\n                    </svg>\n                </div>\n                <label> Bold </label>\n            </div>\n            <div class=\"tui-image-editor-button italic\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                    <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-text-italic\" class=\"normal\"/>\n                    <use xlink:href=\"" + active.path + "#" + active.name + "-ic-text-italic\" class=\"active\"/>\n                    </svg>\n                </div>\n                <label> Italic </label>\n            </div>\n            <div class=\"tui-image-editor-button underline\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-text-underline\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-text-underline\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label> Underline </label>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition\">\n            <div></div>\n        </li>\n        <li id=\"tie-text-align-button\">\n            <div class=\"tui-image-editor-button left\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                     <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-text-align-left\"\n                        class=\"normal\"/>\n                     <use xlink:href=\"" + active.path + "#" + active.name + "-ic-text-align-left\"\n                        class=\"active\"/>\n                    </svg>\n                </div>\n                <label> left </label>\n            </div>\n            <div class=\"tui-image-editor-button center\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                     <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-text-align-center\"\n                        class=\"normal\"/>\n                     <use xlink:href=\"" + active.path + "#" + active.name + "-ic-text-align-center\"\n                        class=\"active\"/>\n                    </svg>\n                </div>\n                <label> center </label>\n            </div>\n            <div class=\"tui-image-editor-button right\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                     <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-text-align-right\"\n                        class=\"normal\"/>\n                     <use xlink:href=\"" + active.path + "#" + active.name + "-ic-text-align-right\"\n                        class=\"active\"/>\n                    </svg>\n                </div>\n                <label> right </label>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition\">\n            <div></div>\n        </li>\n        <li>\n            <div id=\"tie-text-color\" title=\"Color\"></div>\n        </li>\n        <li class=\"tui-image-editor-partition only-left-right\">\n            <div></div>\n        </li>\n        <li class=\"tui-image-editor-newline tui-image-editor-range-wrap\">\n            <label class=\"range\">Text size</label>\n            <div id=\"tie-text-range\"></div>\n            <input id=\"tie-text-range-value\" class=\"tui-image-editor-range-value\" value=\"0\" />\n        </li>\n    </ul>\n";
	};

/***/ }),
/* 94 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _submenuBase = __webpack_require__(84);

	var _submenuBase2 = _interopRequireDefault(_submenuBase);

	var _util = __webpack_require__(72);

	var _util2 = _interopRequireDefault(_util);

	var _mask = __webpack_require__(95);

	var _mask2 = _interopRequireDefault(_mask);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

	/**
	 * Mask ui class
	 * @class
	 * @ignore
	 */
	var Mask = function (_Submenu) {
	    _inherits(Mask, _Submenu);

	    function Mask(subMenuElement, _ref) {
	        var iconStyle = _ref.iconStyle,
	            menuBarPosition = _ref.menuBarPosition;

	        _classCallCheck(this, Mask);

	        var _this = _possibleConstructorReturn(this, (Mask.__proto__ || Object.getPrototypeOf(Mask)).call(this, subMenuElement, {
	            name: 'mask',
	            iconStyle: iconStyle,
	            menuBarPosition: menuBarPosition,
	            templateHtml: _mask2.default
	        }));

	        _this._els = {
	            applyButton: _this.selector('#tie-mask-apply'),
	            maskImageButton: _this.selector('#tie-mask-image-file')
	        };
	        return _this;
	    }

	    /**
	     * Add event for mask
	     * @param {Object} actions - actions for crop
	     *   @param {Function} actions.loadImageFromURL - load image action
	     *   @param {Function} actions.applyFilter - apply filter action
	     */


	    _createClass(Mask, [{
	        key: 'addEvent',
	        value: function addEvent(actions) {
	            this.actions = actions;
	            this._els.maskImageButton.addEventListener('change', this._loadMaskFile.bind(this));
	            this._els.applyButton.addEventListener('click', this._applyMask.bind(this));
	        }

	        /**
	         * Apply mask
	         * @private
	         */

	    }, {
	        key: '_applyMask',
	        value: function _applyMask() {
	            this.actions.applyFilter();
	            this._els.applyButton.classList.remove('active');
	        }

	        /**
	         * Load mask file
	         * @param {object} event - File change event object
	         * @private
	         */

	    }, {
	        key: '_loadMaskFile',
	        value: function _loadMaskFile(event) {
	            var imgUrl = void 0;

	            if (!_util2.default.isSupportFileApi()) {
	                alert('This browser does not support file-api');
	            }

	            var _event$target$files = event.target.files,
	                file = _event$target$files[0];


	            if (file) {
	                imgUrl = URL.createObjectURL(file);
	                this.actions.loadImageFromURL(imgUrl, file);
	                this._els.applyButton.classList.add('active');
	            }
	        }
	    }]);

	    return Mask;
	}(_submenuBase2.default);

	exports.default = Mask;

/***/ }),
/* 95 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var _ref$iconStyle = _ref.iconStyle,
	        normal = _ref$iconStyle.normal,
	        active = _ref$iconStyle.active;
	    return "\n    <ul class=\"tui-image-editor-submenu-item\">\n        <li>\n            <div class=\"tui-image-editor-button\">\n                <div>\n                    <input type=\"file\" accept=\"image/*\" id=\"tie-mask-image-file\">\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-mask-load\" class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-mask-load\" class=\"active\"/>\n                    </svg>\n                </div>\n                <label> Load Mask Image </label>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition only-left-right\">\n            <div></div>\n        </li>\n        <li id=\"tie-mask-apply\" class=\"tui-image-editor-newline apply\">\n            <div class=\"tui-image-editor-button apply\">\n                <svg class=\"svg_ic-menu\">\n                    <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-apply\" class=\"normal\"/>\n                    <use xlink:href=\"" + active.path + "#" + active.name + "-ic-apply\" class=\"active\"/>\n                </svg>\n                <label>\n                    Apply\n                </label>\n            </div>\n        </li>\n    </ul>\n";
	};

/***/ }),
/* 96 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _colorpicker = __webpack_require__(81);

	var _colorpicker2 = _interopRequireDefault(_colorpicker);

	var _submenuBase = __webpack_require__(84);

	var _submenuBase2 = _interopRequireDefault(_submenuBase);

	var _icon = __webpack_require__(97);

	var _icon2 = _interopRequireDefault(_icon);

	var _util = __webpack_require__(72);

	var _consts = __webpack_require__(73);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

	/**
	 * Icon ui class
	 * @class
	 * @ignore
	 */
	var Icon = function (_Submenu) {
	    _inherits(Icon, _Submenu);

	    function Icon(subMenuElement, _ref) {
	        var iconStyle = _ref.iconStyle,
	            menuBarPosition = _ref.menuBarPosition;

	        _classCallCheck(this, Icon);

	        var _this = _possibleConstructorReturn(this, (Icon.__proto__ || Object.getPrototypeOf(Icon)).call(this, subMenuElement, {
	            name: 'icon',
	            iconStyle: iconStyle,
	            menuBarPosition: menuBarPosition,
	            templateHtml: _icon2.default
	        }));

	        _this.iconType = null;
	        _this._iconMap = {};

	        _this._els = {
	            registIconButton: _this.selector('#tie-icon-image-file'),
	            addIconButton: _this.selector('#tie-icon-add-button'),
	            iconColorpicker: new _colorpicker2.default(_this.selector('#tie-icon-color'), '#ffbb3b', _this.toggleDirection)
	        };
	        return _this;
	    }

	    /**
	     * Add event for icon
	     * @param {Object} actions - actions for icon
	     *   @param {Function} actions.registCustomIcon - register icon
	     *   @param {Function} actions.addIcon - add icon
	     *   @param {Function} actions.changeColor - change icon color
	     */


	    _createClass(Icon, [{
	        key: 'addEvent',
	        value: function addEvent(actions) {
	            this.actions = actions;

	            this._els.iconColorpicker.on('change', this._changeColorHandler.bind(this));
	            this._els.registIconButton.addEventListener('change', this._registeIconHandler.bind(this));
	            this._els.addIconButton.addEventListener('click', this._addIconHandler.bind(this));
	        }

	        /**
	         * Clear icon type
	         */

	    }, {
	        key: 'clearIconType',
	        value: function clearIconType() {
	            this._els.addIconButton.classList.remove(this.iconType);
	            this.iconType = null;
	        }

	        /**
	         * Register default icon
	         */

	    }, {
	        key: 'registDefaultIcon',
	        value: function registDefaultIcon() {
	            var _this2 = this;

	            _tuiCodeSnippet2.default.forEach(_consts.defaultIconPath, function (path, type) {
	                _this2.actions.registDefalutIcons(type, path);
	            });
	        }

	        /**
	         * Set icon picker color
	         * @param {string} iconColor - rgb color string
	         */

	    }, {
	        key: 'setIconPickerColor',
	        value: function setIconPickerColor(iconColor) {
	            this._els.iconColorpicker.color = iconColor;
	        }

	        /**
	         * Returns the menu to its default state.
	         */

	    }, {
	        key: 'changeStandbyMode',
	        value: function changeStandbyMode() {
	            this.clearIconType();
	            this.actions.cancelAddIcon();
	        }

	        /**
	         * Change icon color
	         * @param {string} color - color for change
	         * @private
	         */

	    }, {
	        key: '_changeColorHandler',
	        value: function _changeColorHandler(color) {
	            color = color || 'transparent';
	            this.actions.changeColor(color);
	        }

	        /**
	         * Change icon color
	         * @param {object} event - add button event object
	         * @private
	         */

	    }, {
	        key: '_addIconHandler',
	        value: function _addIconHandler(event) {
	            var button = event.target.closest('.tui-image-editor-button');

	            if (button) {
	                var iconType = button.getAttribute('data-icontype');
	                var iconColor = this._els.iconColorpicker.color;
	                this.actions.discardSelection();
	                this.actions.changeSelectableAll(false);
	                this._els.addIconButton.classList.remove(this.iconType);
	                this._els.addIconButton.classList.add(iconType);

	                if (this.iconType === iconType) {
	                    this.changeStandbyMode();
	                } else {
	                    this.actions.addIcon(iconType, iconColor);
	                    this.iconType = iconType;
	                }
	            }
	        }

	        /**
	         * register icon
	         * @param {object} event - file change event object
	         * @private
	         */

	    }, {
	        key: '_registeIconHandler',
	        value: function _registeIconHandler(event) {
	            var imgUrl = void 0;

	            if (!_util.isSupportFileApi) {
	                alert('This browser does not support file-api');
	            }

	            var _event$target$files = event.target.files,
	                file = _event$target$files[0];


	            if (file) {
	                imgUrl = URL.createObjectURL(file);
	                this.actions.registCustomIcon(imgUrl, file);
	            }
	        }
	    }]);

	    return Icon;
	}(_submenuBase2.default);

	exports.default = Icon;

/***/ }),
/* 97 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var _ref$iconStyle = _ref.iconStyle,
	        normal = _ref$iconStyle.normal,
	        active = _ref$iconStyle.active;
	    return "\n    <ul class=\"tui-image-editor-submenu-item\">\n        <li id=\"tie-icon-add-button\">\n            <div class=\"tui-image-editor-button\" data-icontype=\"icon-arrow\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-arrow\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-icon-arrow\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Arrow\n                </label>\n            </div>\n            <div class=\"tui-image-editor-button\" data-icontype=\"icon-arrow-2\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-arrow-2\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-icon-arrow-2\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Arrow-2\n                </label>\n            </div>\n            <div class=\"tui-image-editor-button\" data-icontype=\"icon-arrow-3\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-arrow-3\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-icon-arrow-3\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Arrow-3\n                </label>\n            </div>\n            <div class=\"tui-image-editor-button\" data-icontype=\"icon-star\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-star\" class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-icon-star\" class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Star-1\n                </label>\n            </div>\n            <div class=\"tui-image-editor-button\" data-icontype=\"icon-star-2\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-star-2\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-icon-star-2\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Star-2\n                </label>\n            </div>\n\n            <div class=\"tui-image-editor-button\" data-icontype=\"icon-polygon\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-polygon\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-polygon\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Polygon\n                </label>\n            </div>\n\n            <div class=\"tui-image-editor-button\" data-icontype=\"icon-location\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-location\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-icon-location\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Location\n                </label>\n            </div>\n\n            <div class=\"tui-image-editor-button\" data-icontype=\"icon-heart\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-heart\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-icon-heart\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Heart\n                </label>\n            </div>\n\n            <div class=\"tui-image-editor-button\" data-icontype=\"icon-bubble\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-bubble\"\n                            class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-icon-bubble\"\n                            class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Bubble\n                </label>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition\">\n            <div></div>\n        </li>\n        <li id=\"tie-icon-add-button\">\n            <div class=\"tui-image-editor-button\">\n                <div>\n                    <input type=\"file\" accept=\"image/*\" id=\"tie-icon-image-file\">\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-icon-load\" class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-icon-load\" class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Custom icon\n                </label>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition\">\n            <div></div>\n        </li>\n        <li>\n            <div id=\"tie-icon-color\" title=\"Color\"></div>\n        </li>\n    </ul>\n";
	};

/***/ }),
/* 98 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _util = __webpack_require__(72);

	var _util2 = _interopRequireDefault(_util);

	var _colorpicker = __webpack_require__(81);

	var _colorpicker2 = _interopRequireDefault(_colorpicker);

	var _range = __webpack_require__(83);

	var _range2 = _interopRequireDefault(_range);

	var _submenuBase = __webpack_require__(84);

	var _submenuBase2 = _interopRequireDefault(_submenuBase);

	var _draw = __webpack_require__(99);

	var _draw2 = _interopRequireDefault(_draw);

	var _consts = __webpack_require__(73);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

	var DRAW_OPACITY = 0.7;

	/**
	 * Draw ui class
	 * @class
	 * @ignore
	 */

	var Draw = function (_Submenu) {
	    _inherits(Draw, _Submenu);

	    function Draw(subMenuElement, _ref) {
	        var iconStyle = _ref.iconStyle,
	            menuBarPosition = _ref.menuBarPosition;

	        _classCallCheck(this, Draw);

	        var _this = _possibleConstructorReturn(this, (Draw.__proto__ || Object.getPrototypeOf(Draw)).call(this, subMenuElement, {
	            name: 'draw',
	            iconStyle: iconStyle,
	            menuBarPosition: menuBarPosition,
	            templateHtml: _draw2.default
	        }));

	        _this._els = {
	            lineSelectButton: _this.selector('#tie-draw-line-select-button'),
	            drawColorpicker: new _colorpicker2.default(_this.selector('#tie-draw-color'), '#00a9ff', _this.toggleDirection),
	            drawRange: new _range2.default(_this.selector('#tie-draw-range'), _consts.defaultDrawRangeValus),
	            drawRangeValue: _this.selector('#tie-draw-range-value')
	        };

	        _this.type = null;
	        _this.color = _this._els.drawColorpicker.color;
	        _this.width = _this._els.drawRange.value;
	        return _this;
	    }

	    /**
	     * Add event for draw
	     * @param {Object} actions - actions for crop
	     *   @param {Function} actions.setDrawMode - set draw mode
	     */


	    _createClass(Draw, [{
	        key: 'addEvent',
	        value: function addEvent(actions) {
	            this.actions = actions;

	            this._els.lineSelectButton.addEventListener('click', this._changeDrawType.bind(this));
	            this._els.drawColorpicker.on('change', this._changeDrawColor.bind(this));
	            this._els.drawRange.on('change', this._changeDrawRange.bind(this));
	            this._els.drawRangeValue.value = this._els.drawRange.value;
	            this._els.drawRangeValue.setAttribute('readonly', true);
	        }

	        /**
	         * set draw mode - action runner
	         */

	    }, {
	        key: 'setDrawMode',
	        value: function setDrawMode() {
	            this.actions.setDrawMode(this.type, {
	                width: this.width,
	                color: _util2.default.getRgb(this.color, DRAW_OPACITY)
	            });
	        }

	        /**
	         * Returns the menu to its default state.
	         */

	    }, {
	        key: 'changeStandbyMode',
	        value: function changeStandbyMode() {
	            this.type = null;
	            this.actions.stopDrawingMode();
	            this.actions.changeSelectableAll(true);
	            this._els.lineSelectButton.classList.remove('free');
	            this._els.lineSelectButton.classList.remove('line');
	        }

	        /**
	         * Executed when the menu starts.
	         */

	    }, {
	        key: 'changeStartMode',
	        value: function changeStartMode() {
	            this.type = 'free';
	            this._els.lineSelectButton.classList.add('free');
	            this.setDrawMode();
	        }

	        /**
	         * Change draw type event
	         * @param {object} event - line select event
	         * @private
	         */

	    }, {
	        key: '_changeDrawType',
	        value: function _changeDrawType(event) {
	            var button = event.target.closest('.tui-image-editor-button');
	            if (button) {
	                var lineType = this.getButtonType(button, ['free', 'line']);
	                this.actions.discardSelection();

	                if (this.type === lineType) {
	                    this.changeStandbyMode();

	                    return;
	                }

	                this.changeStandbyMode();
	                this.type = lineType;
	                this._els.lineSelectButton.classList.add(lineType);
	                this.setDrawMode();
	            }
	        }

	        /**
	         * Change drawing color
	         * @param {string} color - select drawing color
	         * @private
	         */

	    }, {
	        key: '_changeDrawColor',
	        value: function _changeDrawColor(color) {
	            this.color = color || 'transparent';
	            if (!this.type) {
	                this.changeStartMode();
	            } else {
	                this.setDrawMode();
	            }
	        }

	        /**
	         * Change drawing Range
	         * @param {number} value - select drawing range
	         * @private
	         */

	    }, {
	        key: '_changeDrawRange',
	        value: function _changeDrawRange(value) {
	            value = _util2.default.toInteger(value);
	            this._els.drawRangeValue.value = value;
	            this.width = value;
	            if (!this.type) {
	                this.changeStartMode();
	            } else {
	                this.setDrawMode();
	            }
	        }
	    }]);

	    return Draw;
	}(_submenuBase2.default);

	exports.default = Draw;

/***/ }),
/* 99 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function (_ref) {
	    var _ref$iconStyle = _ref.iconStyle,
	        normal = _ref$iconStyle.normal,
	        active = _ref$iconStyle.active;
	    return "\n    <ul class=\"tui-image-editor-submenu-item\">\n        <li id=\"tie-draw-line-select-button\">\n            <div class=\"tui-image-editor-button free\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-draw-free\" class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-draw-free\" class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Free\n                </label>\n            </div>\n            <div class=\"tui-image-editor-button line\">\n                <div>\n                    <svg class=\"svg_ic-submenu\">\n                        <use xlink:href=\"" + normal.path + "#" + normal.name + "-ic-draw-line\" class=\"normal\"/>\n                        <use xlink:href=\"" + active.path + "#" + active.name + "-ic-draw-line\" class=\"active\"/>\n                    </svg>\n                </div>\n                <label>\n                    Straight\n                </label>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition\">\n            <div></div>\n        </li>\n        <li>\n            <div id=\"tie-draw-color\" title=\"Color\"></div>\n        </li>\n        <li class=\"tui-image-editor-partition only-left-right\">\n            <div></div>\n        </li>\n        <li class=\"tui-image-editor-newline tui-image-editor-range-wrap\">\n            <label class=\"range\">Range</label>\n            <div id=\"tie-draw-range\"></div>\n            <input id=\"tie-draw-range-value\" class=\"tui-image-editor-range-value\" value=\"0\" />\n        </li>\n    </ul>\n";
	};

/***/ }),
/* 100 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _colorpicker = __webpack_require__(81);

	var _colorpicker2 = _interopRequireDefault(_colorpicker);

	var _range = __webpack_require__(83);

	var _range2 = _interopRequireDefault(_range);

	var _submenuBase = __webpack_require__(84);

	var _submenuBase2 = _interopRequireDefault(_submenuBase);

	var _filter = __webpack_require__(101);

	var _filter2 = _interopRequireDefault(_filter);

	var _util = __webpack_require__(72);

	var _consts = __webpack_require__(73);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

	var PICKER_CONTROL_HEIGHT = '130px';
	var BLEND_OPTIONS = ['add', 'diff', 'subtract', 'multiply', 'screen', 'lighten', 'darken'];
	var FILTER_OPTIONS = ['grayscale', 'invert', 'sepia', 'sepia2', 'blur', 'sharpen', 'emboss', 'remove-white', 'gradient-transparency', 'brightness', 'noise', 'pixelate', 'color-filter', 'tint', 'multiply', 'blend'];

	/**
	 * Filter ui class
	 * @class
	 * @ignore
	 */

	var Filter = function (_Submenu) {
	    _inherits(Filter, _Submenu);

	    function Filter(subMenuElement, _ref) {
	        var iconStyle = _ref.iconStyle,
	            menuBarPosition = _ref.menuBarPosition;

	        _classCallCheck(this, Filter);

	        var _this = _possibleConstructorReturn(this, (Filter.__proto__ || Object.getPrototypeOf(Filter)).call(this, subMenuElement, {
	            name: 'filter',
	            iconStyle: iconStyle,
	            menuBarPosition: menuBarPosition,
	            templateHtml: _filter2.default
	        }));

	        _this.checkedMap = {};
	        _this._makeControlElement();
	        return _this;
	    }

	    /**
	     * Add event for filter
	     * @param {Object} actions - actions for crop
	     *   @param {Function} actions.applyFilter - apply filter option
	     */


	    _createClass(Filter, [{
	        key: 'addEvent',
	        value: function addEvent(_ref2) {
	            var _this2 = this;

	            var applyFilter = _ref2.applyFilter;

	            var changeRangeValue = function changeRangeValue(filterName) {
	                var apply = _this2.checkedMap[filterName].checked;
	                var type = filterName;

	                applyFilter(apply, type, _this2._getFilterOption(type));
	            };

	            _tuiCodeSnippet2.default.forEach(FILTER_OPTIONS, function (filterName) {
	                var filterCheckElement = _this2.selector('#tie-' + filterName);
	                var filterNameCamelCase = (0, _util.toCamelCase)(filterName);
	                _this2.checkedMap[filterNameCamelCase] = filterCheckElement;

	                filterCheckElement.addEventListener('change', function () {
	                    return changeRangeValue(filterNameCamelCase);
	                });
	            });

	            this._els.removewhiteThresholdRange.on('change', function () {
	                return changeRangeValue('removeWhite');
	            });
	            this._els.removewhiteDistanceRange.on('change', function () {
	                return changeRangeValue('removeWhite');
	            });
	            this._els.gradientTransparencyRange.on('change', function () {
	                return changeRangeValue('gradientTransparency');
	            });
	            this._els.colorfilterThresholeRange.on('change', function () {
	                return changeRangeValue('colorFilter');
	            });
	            this._els.pixelateRange.on('change', function () {
	                return changeRangeValue('pixelate');
	            });
	            this._els.noiseRange.on('change', function () {
	                return changeRangeValue('noise');
	            });
	            this._els.brightnessRange.on('change', function () {
	                return changeRangeValue('brightness');
	            });
	            this._els.blendType.addEventListener('change', function () {
	                return changeRangeValue('blend');
	            });
	            this._els.filterBlendColor.on('change', function () {
	                return changeRangeValue('blend');
	            });
	            this._els.filterMultiplyColor.on('change', function () {
	                return changeRangeValue('multiply');
	            });
	            this._els.tintOpacity.on('change', function () {
	                return changeRangeValue('tint');
	            });
	            this._els.filterTintColor.on('change', function () {
	                return changeRangeValue('tint');
	            });
	            this._els.blendType.addEventListener('click', function (event) {
	                return event.stopPropagation();
	            });
	        }

	        /**
	         * Get filter option
	         * @param {String} type - filter type
	         * @returns {Object} filter option object
	         * @private
	         */

	    }, {
	        key: '_getFilterOption',
	        value: function _getFilterOption(type) {
	            // eslint-disable-line
	            var option = {};
	            switch (type) {
	                case 'removeWhite':
	                    option.threshold = (0, _util.toInteger)(this._els.removewhiteThresholdRange.value);
	                    option.distance = (0, _util.toInteger)(this._els.removewhiteDistanceRange.value);
	                    break;
	                case 'gradientTransparency':
	                    option.threshold = (0, _util.toInteger)(this._els.gradientTransparencyRange.value);
	                    break;
	                case 'colorFilter':
	                    option.color = '#FFFFFF';
	                    option.threshold = this._els.colorfilterThresholeRange.value;
	                    break;
	                case 'pixelate':
	                    option.blocksize = (0, _util.toInteger)(this._els.pixelateRange.value);
	                    break;
	                case 'noise':
	                    option.noise = (0, _util.toInteger)(this._els.noiseRange.value);
	                    break;
	                case 'brightness':
	                    option.brightness = (0, _util.toInteger)(this._els.brightnessRange.value);
	                    break;
	                case 'blend':
	                    option.color = this._els.filterBlendColor.color;
	                    option.mode = this._els.blendType.value;
	                    break;
	                case 'multiply':
	                    option.color = this._els.filterMultiplyColor.color;
	                    break;
	                case 'tint':
	                    option.color = this._els.filterTintColor.color;
	                    option.opacity = this._els.tintOpacity.value;
	                    break;
	                default:
	                    break;
	            }

	            return option;
	        }

	        /**
	         * Make submenu range and colorpicker control
	         * @private
	         */

	    }, {
	        key: '_makeControlElement',
	        value: function _makeControlElement() {
	            var selector = this.selector;

	            this._els = {
	                removewhiteThresholdRange: new _range2.default(selector('#tie-removewhite-threshold-range'), _consts.defaultFilterRangeValus.removewhiteThresholdRange),
	                removewhiteDistanceRange: new _range2.default(selector('#tie-removewhite-distance-range'), _consts.defaultFilterRangeValus.removewhiteDistanceRange),
	                gradientTransparencyRange: new _range2.default(selector('#tie-gradient-transparency-range'), _consts.defaultFilterRangeValus.gradientTransparencyRange),
	                brightnessRange: new _range2.default(selector('#tie-brightness-range'), _consts.defaultFilterRangeValus.brightnessRange),
	                noiseRange: new _range2.default(selector('#tie-noise-range'), _consts.defaultFilterRangeValus.noiseRange),
	                pixelateRange: new _range2.default(selector('#tie-pixelate-range'), _consts.defaultFilterRangeValus.pixelateRange),
	                colorfilterThresholeRange: new _range2.default(selector('#tie-colorfilter-threshole-range'), _consts.defaultFilterRangeValus.colorfilterThresholeRange),
	                filterTintColor: new _colorpicker2.default(selector('#tie-filter-tint-color'), '#03bd9e', this.toggleDirection),
	                filterMultiplyColor: new _colorpicker2.default(selector('#tie-filter-multiply-color'), '#515ce6', this.toggleDirection),
	                filterBlendColor: new _colorpicker2.default(selector('#tie-filter-blend-color'), '#ffbb3b', this.toggleDirection)
	            };
	            this._els.tintOpacity = this._pickerWithRange(this._els.filterTintColor.pickerControl);
	            this._els.blendType = this._pickerWithSelectbox(this._els.filterBlendColor.pickerControl);
	        }

	        /**
	         * Make submenu control for picker & range mixin
	         * @param {HTMLElement} pickerControl - pickerControl dom element
	         * @returns {Range}
	         * @private
	         */

	    }, {
	        key: '_pickerWithRange',
	        value: function _pickerWithRange(pickerControl) {
	            var rangeWrap = document.createElement('div');
	            var rangelabel = document.createElement('label');
	            var range = document.createElement('div');

	            range.id = 'tie-filter-tint-opacity';
	            rangelabel.innerHTML = 'Opacity';
	            rangeWrap.appendChild(rangelabel);
	            rangeWrap.appendChild(range);
	            pickerControl.appendChild(rangeWrap);
	            pickerControl.style.height = PICKER_CONTROL_HEIGHT;

	            return new _range2.default(range, _consts.defaultFilterRangeValus.tintOpacityRange);
	        }

	        /**
	         * Make submenu control for picker & selectbox
	         * @param {HTMLElement} pickerControl - pickerControl dom element
	         * @returns {HTMLElement}
	         * @private
	         */

	    }, {
	        key: '_pickerWithSelectbox',
	        value: function _pickerWithSelectbox(pickerControl) {
	            var selectlistWrap = document.createElement('div');
	            var selectlist = document.createElement('select');

	            selectlistWrap.className = 'tui-image-editor-selectlist-wrap';
	            selectlistWrap.appendChild(selectlist);

	            this._makeSelectOptionList(selectlist);

	            pickerControl.appendChild(selectlistWrap);
	            pickerControl.style.height = PICKER_CONTROL_HEIGHT;

	            return selectlist;
	        }

	        /**
	         * Make blend select option
	         * @param {HTMLElement} selectlist - blend option select list element
	         * @private
	         */

	    }, {
	        key: '_makeSelectOptionList',
	        value: function _makeSelectOptionList(selectlist) {
	            _tuiCodeSnippet2.default.forEach(BLEND_OPTIONS, function (option) {
	                var selectOption = document.createElement('option');
	                selectOption.setAttribute('value', option);
	                selectOption.innerHTML = option.replace(/^[a-z]/, function ($0) {
	                    return $0.toUpperCase();
	                });
	                selectlist.appendChild(selectOption);
	            });
	        }
	    }]);

	    return Filter;
	}(_submenuBase2.default);

	exports.default = Filter;

/***/ }),
/* 101 */
/***/ (function(module, exports) {

	"use strict";

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	exports.default = function () {
	    return "\n    <ul class=\"tui-image-editor-submenu-item\">\n        <li class=\"tui-image-editor-submenu-align\">\n            <div class=\"tui-image-editor-checkbox-wrap fixed-width\">\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-grayscale\">\n                    <label for=\"tie-grayscale\">Grayscale</label>\n                </div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-invert\">\n                    <label for=\"tie-invert\">Invert</label>\n                </div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-sepia\">\n                    <label for=\"tie-sepia\">Sepia</label>\n                </div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-sepia2\">\n                    <label for=\"tie-sepia2\">Sepia2</label>\n                </div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-blur\">\n                    <label for=\"tie-blur\">Blur</label>\n                </div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-sharpen\">\n                    <label for=\"tie-sharpen\">Sharpen</label>\n                </div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-emboss\">\n                    <label for=\"tie-emboss\">Emboss</label>\n                </div>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition\">\n            <div></div>\n        </li>\n        <li class=\"tui-image-editor-submenu-align\">\n            <div>\n                <div class=\"tui-image-editor-checkbox-wrap\">\n                    <div class=\"tui-image-editor-checkbox\">\n                        <input type=\"checkbox\" id=\"tie-remove-white\">\n                        <label for=\"tie-remove-white\">Remove White</label>\n                    </div>\n                </div>\n                <div class=\"tui-image-editor-newline tui-image-editor-range-wrap short\">\n                    <label>Threshold</label>\n                    <div id=\"tie-removewhite-threshold-range\"></div>\n                </div>\n                <div class=\"tui-image-editor-newline tui-image-editor-range-wrap short\">\n                    <label>Distance</label>\n                    <div id=\"tie-removewhite-distance-range\"></div>\n                </div>\n            </div>\n            <div>\n                <div class=\"tui-image-editor-newline tui-image-editor-checkbox-wrap\">\n                    <div class=\"tui-image-editor-checkbox\">\n                        <input type=\"checkbox\" id=\"tie-gradient-transparency\">\n                        <label for=\"tie-gradient-transparency\">Grayscale</label>\n                    </div>\n                </div>\n                <div class=\"tui-image-editor-newline tui-image-editor-range-wrap short\">\n                    <label>Value</label>\n                    <div id=\"tie-gradient-transparency-range\"></div>\n                </div>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition only-left-right\">\n            <div></div>\n        </li>\n        <li class=\"tui-image-editor-submenu-align\">\n            <div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-brightness\">\n                    <label for=\"tie-brightness\">Brightness</label>\n                </div>\n                <div class=\"tui-image-editor-range-wrap short\">\n                    <div id=\"tie-brightness-range\"></div>\n                </div>\n            </div>\n            <div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-noise\">\n                    <label for=\"tie-noise\">Noise</label>\n                </div>\n                <div class=\"tui-image-editor-range-wrap short\">\n                    <div id=\"tie-noise-range\"></div>\n                </div>\n            </div>\n\n            <div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-pixelate\">\n                    <label for=\"tie-pixelate\">Pixelate</label>\n                </div>\n                <div class=\"tui-image-editor-range-wrap short\">\n                    <div id=\"tie-pixelate-range\"></div>\n                </div>\n            </div>\n            <div>\n                <div class=\"tui-image-editor-newline tui-image-editor-checkbox-wrap\">\n                    <div class=\"tui-image-editor-checkbox\">\n                        <input type=\"checkbox\" id=\"tie-color-filter\">\n                        <label for=\"tie-color-filter\">Color Filter</label>\n                    </div>\n                </div>\n                <div class=\"tui-image-editor-newline tui-image-editor-range-wrap short\">\n                    <label>Threshold</label>\n                    <div id=\"tie-colorfilter-threshole-range\"></div>\n                </div>\n            </div>\n        </li>\n        <li class=\"tui-image-editor-partition\">\n            <div></div>\n        </li>\n        <li>\n            <div class=\"filter-color-item\">\n                <div id=\"tie-filter-tint-color\" title=\"Tint\"></div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-tint\">\n                    <label for=\"tie-tint\"></label>\n                </div>\n            </div>\n            <div class=\"filter-color-item\">\n                <div id=\"tie-filter-multiply-color\" title=\"Multiply\"></div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-multiply\">\n                    <label for=\"tie-multiply\"></label>\n                </div>\n            </div>\n            <div class=\"filter-color-item\">\n                <div id=\"tie-filter-blend-color\" title=\"Blend\"></div>\n                <div class=\"tui-image-editor-checkbox\">\n                    <input type=\"checkbox\" id=\"tie-blend\">\n                    <label for=\"tie-blend\"></label>\n                </div>\n            </div>\n        </li>\n    </ul>\n";
	};

/***/ }),
/* 102 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _tuiCodeSnippet = __webpack_require__(3);

	var _util = __webpack_require__(72);

	var _util2 = _interopRequireDefault(_util);

	var _imagetracer = __webpack_require__(103);

	var _imagetracer2 = _interopRequireDefault(_imagetracer);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	exports.default = {

	    /**
	     * Get ui actions
	     * @returns {Object} actions for ui
	     * @private
	     */
	    getActions: function getActions() {
	        return {
	            main: this._mainAction(),
	            shape: this._shapeAction(),
	            crop: this._cropAction(),
	            flip: this._flipAction(),
	            rotate: this._rotateAction(),
	            text: this._textAction(),
	            mask: this._maskAction(),
	            draw: this._drawAction(),
	            icon: this._iconAction(),
	            filter: this._filterAction()
	        };
	    },


	    /**
	     * Main Action
	     * @returns {Object} actions for ui main
	     * @private
	     */
	    _mainAction: function _mainAction() {
	        var _this = this;

	        var exitCropOnAction = function exitCropOnAction() {
	            if (_this.ui.submenu === 'crop') {
	                _this.stopDrawingMode();
	                _this.ui.changeMenu('crop');
	            }
	        };

	        return (0, _tuiCodeSnippet.extend)({
	            initLoadImage: function initLoadImage(imagePath, imageName) {
	                return _this.loadImageFromURL(imagePath, imageName).then(function (sizeValue) {
	                    exitCropOnAction();
	                    _this.ui.initializeImgUrl = imagePath;
	                    _this.ui.resizeEditor({ imageSize: sizeValue });
	                    _this.clearUndoStack();
	                });
	            },
	            undo: function undo() {
	                if (!_this.isEmptyUndoStack()) {
	                    exitCropOnAction();
	                    _this.undo();
	                }
	            },
	            redo: function redo() {
	                if (!_this.isEmptyRedoStack()) {
	                    exitCropOnAction();
	                    _this.redo();
	                }
	            },
	            reset: function reset() {
	                exitCropOnAction();
	                _this.loadImageFromURL(_this.ui.initializeImgUrl, 'resetImage').then(function (sizeValue) {
	                    exitCropOnAction();
	                    _this.ui.resizeEditor({ imageSize: sizeValue });
	                    _this.clearUndoStack();
	                });
	            },
	            delete: function _delete() {
	                _this.ui.changeDeleteButtonEnabled(false);
	                exitCropOnAction();
	                _this.removeActiveObject();
	                _this.activeObjectId = null;
	            },
	            deleteAll: function deleteAll() {
	                exitCropOnAction();
	                _this.clearObjects();
	                _this.ui.changeDeleteButtonEnabled(false);
	                _this.ui.changeDeleteAllButtonEnabled(false);
	            },
	            load: function load(file) {
	                if (!_util2.default.isSupportFileApi()) {
	                    alert('This browser does not support file-api');
	                }

	                _this.ui.initializeImgUrl = URL.createObjectURL(file);
	                _this.loadImageFromFile(file).then(function () {
	                    exitCropOnAction();
	                    _this.clearUndoStack();
	                    _this.ui.resizeEditor();
	                })['catch'](function (message) {
	                    return Promise.reject(message);
	                });
	            },
	            download: function download() {
	                var dataURL = _this.toDataURL();
	                var imageName = _this.getImageName();
	                var blob = void 0,
	                    type = void 0,
	                    w = void 0;

	                if (_util2.default.isSupportFileApi() && window.saveAs) {
	                    blob = _util2.default.base64ToBlob(dataURL);
	                    type = blob.type.split('/')[1];
	                    if (imageName.split('.').pop() !== type) {
	                        imageName += '.' + type;
	                    }
	                    saveAs(blob, imageName); // eslint-disable-line
	                } else {
	                    w = window.open();
	                    w.document.body.innerHTML = '<img src=\'' + dataURL + '\'>';
	                }
	            }
	        }, this._commonAction());
	    },


	    /**
	     * Icon Action
	     * @returns {Object} actions for ui icon
	     * @private
	     */
	    _iconAction: function _iconAction() {
	        var _this2 = this;

	        var cacheIconType = void 0;
	        var cacheIconColor = void 0;
	        var startX = void 0;
	        var startY = void 0;
	        var iconWidth = void 0;
	        var iconHeight = void 0;
	        var objId = void 0;

	        this.on({
	            'iconCreateResize': function iconCreateResize(_ref) {
	                var moveOriginPointer = _ref.moveOriginPointer;

	                var scaleX = (moveOriginPointer.x - startX) / iconWidth;
	                var scaleY = (moveOriginPointer.y - startY) / iconHeight;

	                _this2.setObjectPropertiesQuietly(objId, {
	                    scaleX: Math.abs(scaleX * 2),
	                    scaleY: Math.abs(scaleY * 2)
	                });
	            },
	            'iconCreateEnd': function iconCreateEnd() {
	                _this2.ui.icon.clearIconType();
	                _this2.changeSelectableAll(true);
	            }
	        });

	        var mouseDown = function mouseDown(e, originPointer) {
	            startX = originPointer.x;
	            startY = originPointer.y;

	            _this2.addIcon(cacheIconType, {
	                left: originPointer.x,
	                top: originPointer.y,
	                fill: cacheIconColor
	            }).then(function (obj) {
	                objId = obj.id;
	                iconWidth = obj.width;
	                iconHeight = obj.height;
	            });
	        };

	        return (0, _tuiCodeSnippet.extend)({
	            changeColor: function changeColor(color) {
	                if (_this2.activeObjectId) {
	                    _this2.changeIconColor(_this2.activeObjectId, color);
	                }
	            },
	            addIcon: function addIcon(iconType, iconColor) {
	                cacheIconType = iconType;
	                cacheIconColor = iconColor;
	                // this.readyAddIcon();
	                _this2.changeCursor('crosshair');
	                _this2.off('mousedown');
	                _this2.once('mousedown', mouseDown.bind(_this2));
	            },
	            cancelAddIcon: function cancelAddIcon() {
	                _this2.off('mousedown');
	                _this2.ui.icon.clearIconType();
	                _this2.changeSelectableAll(true);
	                _this2.changeCursor('default');
	            },
	            registDefalutIcons: function registDefalutIcons(type, path) {
	                var iconObj = {};
	                iconObj[type] = path;
	                _this2.registerIcons(iconObj);
	            },
	            registCustomIcon: function registCustomIcon(imgUrl, file) {
	                var imagetracer = new _imagetracer2.default();
	                imagetracer.imageToSVG(imgUrl, function (svgstr) {
	                    var _svgstr$match = svgstr.match(/path[^>]*d="([^"]*)"/),
	                        svgPath = _svgstr$match[1];

	                    var iconObj = {};
	                    iconObj[file.name] = svgPath;
	                    _this2.registerIcons(iconObj);
	                    _this2.addIcon(file.name, {
	                        left: 100,
	                        top: 100
	                    });
	                }, _imagetracer2.default.tracerDefaultOption());
	            }
	        }, this._commonAction());
	    },


	    /**
	     * Draw Action
	     * @returns {Object} actions for ui draw
	     * @private
	     */
	    _drawAction: function _drawAction() {
	        var _this3 = this;

	        return (0, _tuiCodeSnippet.extend)({
	            setDrawMode: function setDrawMode(type, settings) {
	                _this3.stopDrawingMode();
	                if (type === 'free') {
	                    _this3.startDrawingMode('FREE_DRAWING', settings);
	                } else {
	                    _this3.startDrawingMode('LINE_DRAWING', settings);
	                }
	            },
	            setColor: function setColor(color) {
	                _this3.setBrush({
	                    color: color
	                });
	            }
	        }, this._commonAction());
	    },


	    /**
	     * Mask Action
	     * @returns {Object} actions for ui mask
	     * @private
	     */
	    _maskAction: function _maskAction() {
	        var _this4 = this;

	        return (0, _tuiCodeSnippet.extend)({
	            loadImageFromURL: function loadImageFromURL(imgUrl, file) {
	                return _this4.loadImageFromURL(_this4.toDataURL(), 'FilterImage').then(function () {
	                    _this4.addImageObject(imgUrl).then(function () {
	                        URL.revokeObjectURL(file);
	                    });
	                });
	            },
	            applyFilter: function applyFilter() {
	                _this4.applyFilter('mask', {
	                    maskObjId: _this4.activeObjectId
	                });
	            }
	        }, this._commonAction());
	    },


	    /**
	     * Text Action
	     * @returns {Object} actions for ui text
	     * @private
	     */
	    _textAction: function _textAction() {
	        var _this5 = this;

	        return (0, _tuiCodeSnippet.extend)({
	            changeTextStyle: function changeTextStyle(styleObj) {
	                if (_this5.activeObjectId) {
	                    _this5.changeTextStyle(_this5.activeObjectId, styleObj);
	                }
	            }
	        }, this._commonAction());
	    },


	    /**
	     * Rotate Action
	     * @returns {Object} actions for ui rotate
	     * @private
	     */
	    _rotateAction: function _rotateAction() {
	        var _this6 = this;

	        return (0, _tuiCodeSnippet.extend)({
	            rotate: function rotate(angle) {
	                _this6.rotate(angle);
	                _this6.ui.resizeEditor();
	            },
	            setAngle: function setAngle(angle) {
	                _this6.setAngle(angle);
	                _this6.ui.resizeEditor();
	            }
	        }, this._commonAction());
	    },


	    /**
	     * Shape Action
	     * @returns {Object} actions for ui shape
	     * @private
	     */
	    _shapeAction: function _shapeAction() {
	        var _this7 = this;

	        return (0, _tuiCodeSnippet.extend)({
	            changeShape: function changeShape(changeShapeObject) {
	                if (_this7.activeObjectId) {
	                    _this7.changeShape(_this7.activeObjectId, changeShapeObject);
	                }
	            },
	            setDrawingShape: function setDrawingShape(shapeType) {
	                _this7.setDrawingShape(shapeType);
	            }
	        }, this._commonAction());
	    },


	    /**
	     * Crop Action
	     * @returns {Object} actions for ui crop
	     * @private
	     */
	    _cropAction: function _cropAction() {
	        var _this8 = this;

	        return (0, _tuiCodeSnippet.extend)({
	            crop: function crop() {
	                var cropRect = _this8.getCropzoneRect();
	                if (cropRect) {
	                    _this8.crop(cropRect).then(function () {
	                        _this8.stopDrawingMode();
	                        _this8.ui.resizeEditor();
	                        _this8.ui.changeMenu('crop');
	                    })['catch'](function (message) {
	                        return Promise.reject(message);
	                    });
	                }
	            },
	            cancel: function cancel() {
	                _this8.stopDrawingMode();
	                _this8.ui.changeMenu('crop');
	            }
	        }, this._commonAction());
	    },


	    /**
	     * Flip Action
	     * @returns {Object} actions for ui flip
	     * @private
	     */
	    _flipAction: function _flipAction() {
	        var _this9 = this;

	        return (0, _tuiCodeSnippet.extend)({
	            flip: function flip(flipType) {
	                return _this9[flipType]();
	            }
	        }, this._commonAction());
	    },


	    /**
	     * Filter Action
	     * @returns {Object} actions for ui filter
	     * @private
	     */
	    _filterAction: function _filterAction() {
	        var _this10 = this;

	        return (0, _tuiCodeSnippet.extend)({
	            applyFilter: function applyFilter(applying, type, options) {
	                if (applying) {
	                    _this10.applyFilter(type, options);
	                } else if (_this10.hasFilter(type)) {
	                    _this10.removeFilter(type);
	                }
	            }
	        }, this._commonAction());
	    },


	    /**
	     * Image Editor Event Observer
	     */
	    setReAction: function setReAction() {
	        var _this11 = this;

	        this.on({
	            undoStackChanged: function undoStackChanged(length) {
	                if (length) {
	                    _this11.ui.changeUndoButtonStatus(true);
	                    _this11.ui.changeResetButtonStatus(true);
	                } else {
	                    _this11.ui.changeUndoButtonStatus(false);
	                    _this11.ui.changeResetButtonStatus(false);
	                }
	                _this11.ui.resizeEditor();
	            },
	            redoStackChanged: function redoStackChanged(length) {
	                if (length) {
	                    _this11.ui.changeRedoButtonStatus(true);
	                } else {
	                    _this11.ui.changeRedoButtonStatus(false);
	                }
	                _this11.ui.resizeEditor();
	            },
	            /* eslint-disable complexity */
	            objectActivated: function objectActivated(obj) {
	                _this11.activeObjectId = obj.id;

	                _this11.ui.changeDeleteButtonEnabled(true);
	                _this11.ui.changeDeleteAllButtonEnabled(true);

	                if (obj.type === 'cropzone') {
	                    _this11.ui.crop.changeApplyButtonStatus(true);
	                } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) > -1) {
	                    _this11.stopDrawingMode();
	                    if (_this11.ui.submenu !== 'shape') {
	                        _this11.ui.changeMenu('shape', false, false);
	                    }
	                    _this11.ui.shape.setShapeStatus({
	                        strokeColor: obj.stroke,
	                        strokeWidth: obj.strokeWidth,
	                        fillColor: obj.fill
	                    });

	                    _this11.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height));
	                } else if (obj.type === 'path' || obj.type === 'line') {
	                    if (_this11.ui.submenu !== 'draw') {
	                        _this11.ui.changeMenu('draw', false, false);
	                        _this11.ui.draw.changeStandbyMode();
	                    }
	                } else if (['i-text', 'text'].indexOf(obj.type) > -1) {
	                    if (_this11.ui.submenu !== 'text') {
	                        _this11.ui.changeMenu('text', false, false);
	                    }
	                } else if (obj.type === 'icon') {
	                    _this11.stopDrawingMode();
	                    if (_this11.ui.submenu !== 'icon') {
	                        _this11.ui.changeMenu('icon', false, false);
	                    }
	                    _this11.ui.icon.setIconPickerColor(obj.fill);
	                }
	            },
	            /* eslint-enable complexity */
	            addText: function addText(pos) {
	                _this11.addText('Double Click', {
	                    position: pos.originPosition,
	                    styles: {
	                        fill: _this11.ui.text.textColor,
	                        fontSize: _util2.default.toInteger(_this11.ui.text.fontSize)
	                    }
	                }).then(function () {
	                    _this11.changeCursor('default');
	                });
	            },
	            addObjectAfter: function addObjectAfter(obj) {
	                if (['rect', 'circle', 'triangle'].indexOf(obj.type) > -1) {
	                    _this11.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height));
	                    _this11.ui.shape.changeStandbyMode();
	                }
	            },
	            objectScaled: function objectScaled(obj) {
	                if (['i-text', 'text'].indexOf(obj.type) > -1) {
	                    _this11.ui.text.fontSize = _util2.default.toInteger(obj.fontSize);
	                } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) >= 0) {
	                    var width = obj.width,
	                        height = obj.height;

	                    var strokeValue = _this11.ui.shape.getStrokeValue();

	                    if (width < strokeValue) {
	                        _this11.ui.shape.setStrokeValue(width);
	                    }
	                    if (height < strokeValue) {
	                        _this11.ui.shape.setStrokeValue(height);
	                    }
	                }
	            },
	            selectionCleared: function selectionCleared() {
	                _this11.activeObjectId = null;
	                if (_this11.ui.submenu === 'text') {
	                    _this11.changeCursor('text');
	                } else if (_this11.ui.submenu !== 'draw' && _this11.ui.submenu !== 'crop') {
	                    _this11.stopDrawingMode();
	                }
	            }
	        });
	    },


	    /**
	     * Common Action
	     * @returns {Object} common actions for ui
	     * @private
	     */
	    _commonAction: function _commonAction() {
	        var _this12 = this;

	        return {
	            modeChange: function modeChange(menu) {
	                switch (menu) {
	                    case 'text':
	                        _this12._changeActivateMode('TEXT');
	                        break;
	                    case 'crop':
	                        _this12.startDrawingMode('CROPPER');
	                        break;
	                    case 'shape':
	                        _this12._changeActivateMode('SHAPE');
	                        _this12.setDrawingShape(_this12.ui.shape.type, _this12.ui.shape.options);
	                        break;
	                    default:
	                        break;
	                }
	            },
	            deactivateAll: this.deactivateAll.bind(this),
	            changeSelectableAll: this.changeSelectableAll.bind(this),
	            discardSelection: this.discardSelection.bind(this),
	            stopDrawingMode: this.stopDrawingMode.bind(this)
	        };
	    },


	    /**
	     * Mixin
	     * @param {ImageEditor} ImageEditor instance
	     */
	    mixin: function mixin(ImageEditor) {
	        (0, _tuiCodeSnippet.extend)(ImageEditor.prototype, this);
	    }
	};

/***/ }),
/* 103 */
/***/ (function(module, exports) {

	'use strict';

	Object.defineProperty(exports, "__esModule", {
	    value: true
	});

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	/*
	  imagetracer.js version 1.2.4
	  Simple raster image tracer and vectorizer written in JavaScript.
	  andras@jankovics.net
	*/

	/*
	  The Unlicense / PUBLIC DOMAIN
	  This is free and unencumbered software released into the public domain.
	  Anyone is free to copy, modify, publish, use, compile, sell, or
	  distribute this software, either in source code form or as a compiled
	  binary, for any purpose, commercial or non-commercial, and by any
	  means.
	  In jurisdictions that recognize copyright laws, the author or authors
	  of this software dedicate any and all copyright interest in the
	  software to the public domain. We make this dedication for the benefit
	  of the public at large and to the detriment of our heirs and
	  successors. We intend this dedication to be an overt act of
	  relinquishment in perpetuity of all present and future rights to this
	  software under copyright law.
	  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
	  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
	  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
	  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
	  OTHER DEALINGS IN THE SOFTWARE.
	  For more information, please refer to http://unlicense.org/
	*/
	var ImageTracer = function () {
	    _createClass(ImageTracer, null, [{
	        key: 'tracerDefaultOption',
	        value: function tracerDefaultOption() {
	            return {
	                pathomit: 100,
	                ltres: 0.1,
	                qtres: 1,

	                scale: 1,
	                strokewidth: 5,
	                viewbox: false,
	                linefilter: true,
	                desc: false,
	                rightangleenhance: false,
	                pal: [{
	                    r: 0,
	                    g: 0,
	                    b: 0,
	                    a: 255
	                }, {
	                    r: 255,
	                    g: 255,
	                    b: 255,
	                    a: 255
	                }]
	            };
	        }
	        /* eslint-disable */

	    }]);

	    function ImageTracer() {
	        _classCallCheck(this, ImageTracer);

	        this.versionnumber = '1.2.4';
	        this.optionpresets = {
	            default: {
	                corsenabled: false,
	                ltres: 1,
	                qtres: 1,
	                pathomit: 8,
	                rightangleenhance: true,
	                colorsampling: 2,
	                numberofcolors: 16,
	                mincolorratio: 0,
	                colorquantcycles: 3,
	                layering: 0,
	                strokewidth: 1,
	                linefilter: false,
	                scale: 1,
	                roundcoords: 1,
	                viewbox: false,
	                desc: false,
	                lcpr: 0,
	                qcpr: 0,
	                blurradius: 0,
	                blurdelta: 20
	            },
	            'posterized1': {
	                colorsampling: 0,
	                numberofcolors: 2
	            },
	            'posterized2': {
	                numberofcolors: 4,
	                blurradius: 5
	            },
	            'curvy': {
	                ltres: 0.01,
	                linefilter: true,
	                rightangleenhance: false },
	            'sharp': { qtres: 0.01,
	                linefilter: false },
	            'detailed': { pathomit: 0,
	                roundcoords: 2,
	                ltres: 0.5,
	                qtres: 0.5,
	                numberofcolors: 64 },
	            'smoothed': { blurradius: 5,
	                blurdelta: 64 },
	            'grayscale': { colorsampling: 0,
	                colorquantcycles: 1,
	                numberofcolors: 7 },
	            'fixedpalette': { colorsampling: 0,
	                colorquantcycles: 1,
	                numberofcolors: 27 },
	            'randomsampling1': { colorsampling: 1,
	                numberofcolors: 8 },
	            'randomsampling2': { colorsampling: 1,
	                numberofcolors: 64 },
	            'artistic1': { colorsampling: 0,
	                colorquantcycles: 1,
	                pathomit: 0,
	                blurradius: 5,
	                blurdelta: 64,
	                ltres: 0.01,
	                linefilter: true,
	                numberofcolors: 16,
	                strokewidth: 2 },
	            'artistic2': { qtres: 0.01,
	                colorsampling: 0,
	                colorquantcycles: 1,
	                numberofcolors: 4,
	                strokewidth: 0 },
	            'artistic3': { qtres: 10,
	                ltres: 10,
	                numberofcolors: 8 },
	            'artistic4': { qtres: 10,
	                ltres: 10,
	                numberofcolors: 64,
	                blurradius: 5,
	                blurdelta: 256,
	                strokewidth: 2 },
	            'posterized3': { ltres: 1,
	                qtres: 1,
	                pathomit: 20,
	                rightangleenhance: true,
	                colorsampling: 0,
	                numberofcolors: 3,
	                mincolorratio: 0,
	                colorquantcycles: 3,
	                blurradius: 3,
	                blurdelta: 20,
	                strokewidth: 0,
	                linefilter: false,
	                roundcoords: 1,
	                pal: [{ r: 0,
	                    g: 0,
	                    b: 100,
	                    a: 255 }, { r: 255,
	                    g: 255,
	                    b: 255,
	                    a: 255 }] }
	        };

	        this.pathscan_combined_lookup = [[[-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1]], [[0, 1, 0, -1], [-1, -1, -1, -1], [-1, -1, -1, -1], [0, 2, -1, 0]], [[-1, -1, -1, -1], [-1, -1, -1, -1], [0, 1, 0, -1], [0, 0, 1, 0]], [[0, 0, 1, 0], [-1, -1, -1, -1], [0, 2, -1, 0], [-1, -1, -1, -1]], [[-1, -1, -1, -1], [0, 0, 1, 0], [0, 3, 0, 1], [-1, -1, -1, -1]], [[13, 3, 0, 1], [13, 2, -1, 0], [7, 1, 0, -1], [7, 0, 1, 0]], [[-1, -1, -1, -1], [0, 1, 0, -1], [-1, -1, -1, -1], [0, 3, 0, 1]], [[0, 3, 0, 1], [0, 2, -1, 0], [-1, -1, -1, -1], [-1, -1, -1, -1]], [[0, 3, 0, 1], [0, 2, -1, 0], [-1, -1, -1, -1], [-1, -1, -1, -1]], [[-1, -1, -1, -1], [0, 1, 0, -1], [-1, -1, -1, -1], [0, 3, 0, 1]], [[11, 1, 0, -1], [14, 0, 1, 0], [14, 3, 0, 1], [11, 2, -1, 0]], [[-1, -1, -1, -1], [0, 0, 1, 0], [0, 3, 0, 1], [-1, -1, -1, -1]], [[0, 0, 1, 0], [-1, -1, -1, -1], [0, 2, -1, 0], [-1, -1, -1, -1]], [[-1, -1, -1, -1], [-1, -1, -1, -1], [0, 1, 0, -1], [0, 0, 1, 0]], [[0, 1, 0, -1], [-1, -1, -1, -1], [-1, -1, -1, -1], [0, 2, -1, 0]], [[-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1]]];

	        this.gks = [[0.27901, 0.44198, 0.27901], [0.135336, 0.228569, 0.272192, 0.228569, 0.135336], [0.086776, 0.136394, 0.178908, 0.195843, 0.178908, 0.136394, 0.086776], [0.063327, 0.093095, 0.122589, 0.144599, 0.152781, 0.144599, 0.122589, 0.093095, 0.063327], [0.049692, 0.069304, 0.089767, 0.107988, 0.120651, 0.125194, 0.120651, 0.107988, 0.089767, 0.069304, 0.049692]];

	        this.specpalette = [{ r: 0, g: 0, b: 0, a: 255 }, { r: 128, g: 128, b: 128, a: 255 }, { r: 0, g: 0, b: 128, a: 255 }, { r: 64, g: 64, b: 128, a: 255 }, { r: 192, g: 192, b: 192, a: 255 }, { r: 255, g: 255, b: 255, a: 255 }, { r: 128, g: 128, b: 192, a: 255 }, { r: 0, g: 0, b: 192, a: 255 }, { r: 128, g: 0, b: 0, a: 255 }, { r: 128, g: 64, b: 64, a: 255 }, { r: 128, g: 0, b: 128, a: 255 }, { r: 168, g: 168, b: 168, a: 255 }, { r: 192, g: 128, b: 128, a: 255 }, { r: 192, g: 0, b: 0, a: 255 }, { r: 255, g: 255, b: 255, a: 255 }, { r: 0, g: 128, b: 0, a: 255 }];
	    }

	    _createClass(ImageTracer, [{
	        key: 'imageToSVG',
	        value: function imageToSVG(url, callback, options) {
	            var _this = this;

	            options = this.checkoptions(options);
	            this.loadImage(url, function (canvas) {
	                callback(_this.imagedataToSVG(_this.getImgdata(canvas), options));
	            }, options);
	        }
	    }, {
	        key: 'imagedataToSVG',
	        value: function imagedataToSVG(imgd, options) {
	            options = this.checkoptions(options);
	            var td = this.imagedataToTracedata(imgd, options);

	            return this.getsvgstring(td, options);
	        }
	    }, {
	        key: 'imageToTracedata',
	        value: function imageToTracedata(url, callback, options) {
	            var _this2 = this;

	            options = this.checkoptions(options);
	            this.loadImage(url, function (canvas) {
	                callback(_this2.imagedataToTracedata(_this2.getImgdata(canvas), options));
	            }, options);
	        }
	    }, {
	        key: 'imagedataToTracedata',
	        value: function imagedataToTracedata(imgd, options) {
	            options = this.checkoptions(options);
	            var ii = this.colorquantization(imgd, options);
	            var tracedata = void 0;
	            if (options.layering === 0) {
	                tracedata = {
	                    layers: [],
	                    palette: ii.palette,
	                    width: ii.array[0].length - 2,
	                    height: ii.array.length - 2
	                };

	                for (var colornum = 0; colornum < ii.palette.length; colornum += 1) {
	                    var tracedlayer = this.batchtracepaths(this.internodes(this.pathscan(this.layeringstep(ii, colornum), options.pathomit), options), options.ltres, options.qtres);
	                    tracedata.layers.push(tracedlayer);
	                }
	            } else {
	                var ls = this.layering(ii);
	                if (options.layercontainerid) {
	                    this.drawLayers(ls, this.specpalette, options.scale, options.layercontainerid);
	                }
	                var bps = this.batchpathscan(ls, options.pathomit);
	                var bis = this.batchinternodes(bps, options);
	                tracedata = {
	                    layers: this.batchtracelayers(bis, options.ltres, options.qtres),
	                    palette: ii.palette,
	                    width: imgd.width,
	                    height: imgd.height
	                };
	            }

	            return tracedata;
	        }
	    }, {
	        key: 'checkoptions',
	        value: function checkoptions(options) {
	            options = options || {};
	            if (typeof options === 'string') {
	                options = options.toLowerCase();
	                if (this.optionpresets[options]) {
	                    options = this.optionpresets[options];
	                } else {
	                    options = {};
	                }
	            }
	            var ok = Object.keys(this.optionpresets['default']);
	            for (var k = 0; k < ok.length; k += 1) {
	                if (!options.hasOwnProperty(ok[k])) {
	                    options[ok[k]] = this.optionpresets['default'][ok[k]];
	                }
	            }

	            return options;
	        }
	    }, {
	        key: 'colorquantization',
	        value: function colorquantization(imgd, options) {
	            var arr = [];
	            var idx = 0;
	            var cd = void 0;
	            var cdl = void 0;
	            var ci = void 0;
	            var paletteacc = [];
	            var pixelnum = imgd.width * imgd.height;
	            var i = void 0;
	            var j = void 0;
	            var k = void 0;
	            var cnt = void 0;
	            var palette = void 0;

	            for (j = 0; j < imgd.height + 2; j += 1) {
	                arr[j] = [];
	                for (i = 0; i < imgd.width + 2; i += 1) {
	                    arr[j][i] = -1;
	                }
	            }
	            if (options.pal) {
	                palette = options.pal;
	            } else if (options.colorsampling === 0) {
	                palette = this.generatepalette(options.numberofcolors);
	            } else if (options.colorsampling === 1) {
	                palette = this.samplepalette(options.numberofcolors, imgd);
	            } else {
	                palette = this.samplepalette2(options.numberofcolors, imgd);
	            }
	            if (options.blurradius > 0) {
	                imgd = this.blur(imgd, options.blurradius, options.blurdelta);
	            }
	            for (cnt = 0; cnt < options.colorquantcycles; cnt += 1) {
	                if (cnt > 0) {
	                    for (k = 0; k < palette.length; k += 1) {
	                        if (paletteacc[k].n > 0) {
	                            palette[k] = { r: Math.floor(paletteacc[k].r / paletteacc[k].n),
	                                g: Math.floor(paletteacc[k].g / paletteacc[k].n),
	                                b: Math.floor(paletteacc[k].b / paletteacc[k].n),
	                                a: Math.floor(paletteacc[k].a / paletteacc[k].n) };
	                        }

	                        if (paletteacc[k].n / pixelnum < options.mincolorratio && cnt < options.colorquantcycles - 1) {
	                            palette[k] = { r: Math.floor(Math.random() * 255),
	                                g: Math.floor(Math.random() * 255),
	                                b: Math.floor(Math.random() * 255),
	                                a: Math.floor(Math.random() * 255) };
	                        }
	                    }
	                }

	                for (i = 0; i < palette.length; i += 1) {
	                    paletteacc[i] = { r: 0,
	                        g: 0,
	                        b: 0,
	                        a: 0,
	                        n: 0 };
	                }

	                for (j = 0; j < imgd.height; j += 1) {
	                    for (i = 0; i < imgd.width; i += 1) {
	                        idx = (j * imgd.width + i) * 4;

	                        ci = 0;
	                        cdl = 1024;
	                        for (k = 0; k < palette.length; k += 1) {
	                            cd = Math.abs(palette[k].r - imgd.data[idx]) + Math.abs(palette[k].g - imgd.data[idx + 1]) + Math.abs(palette[k].b - imgd.data[idx + 2]) + Math.abs(palette[k].a - imgd.data[idx + 3]);

	                            if (cd < cdl) {
	                                cdl = cd;
	                                ci = k;
	                            }
	                        }

	                        paletteacc[ci].r += imgd.data[idx];
	                        paletteacc[ci].g += imgd.data[idx + 1];
	                        paletteacc[ci].b += imgd.data[idx + 2];
	                        paletteacc[ci].a += imgd.data[idx + 3];
	                        paletteacc[ci].n += 1;

	                        arr[j + 1][i + 1] = ci;
	                    }
	                }
	            }

	            return { array: arr,
	                palette: palette };
	        }
	    }, {
	        key: 'samplepalette',
	        value: function samplepalette(numberofcolors, imgd) {
	            var idx = void 0;
	            var palette = [];
	            for (var i = 0; i < numberofcolors; i += 1) {
	                idx = Math.floor(Math.random() * imgd.data.length / 4) * 4;
	                palette.push({ r: imgd.data[idx],
	                    g: imgd.data[idx + 1],
	                    b: imgd.data[idx + 2],
	                    a: imgd.data[idx + 3] });
	            }

	            return palette;
	        }
	    }, {
	        key: 'samplepalette2',
	        value: function samplepalette2(numberofcolors, imgd) {
	            var idx = void 0;
	            var palette = [];
	            var ni = Math.ceil(Math.sqrt(numberofcolors));
	            var nj = Math.ceil(numberofcolors / ni);
	            var vx = imgd.width / (ni + 1);
	            var vy = imgd.height / (nj + 1);
	            for (var j = 0; j < nj; j += 1) {
	                for (var i = 0; i < ni; i += 1) {
	                    if (palette.length === numberofcolors) {
	                        break;
	                    } else {
	                        idx = Math.floor((j + 1) * vy * imgd.width + (i + 1) * vx) * 4;
	                        palette.push({ r: imgd.data[idx],
	                            g: imgd.data[idx + 1],
	                            b: imgd.data[idx + 2],
	                            a: imgd.data[idx + 3] });
	                    }
	                }
	            }

	            return palette;
	        }
	    }, {
	        key: 'generatepalette',
	        value: function generatepalette(numberofcolors) {
	            var palette = [];
	            var rcnt = void 0;
	            var gcnt = void 0;
	            var bcnt = void 0;
	            if (numberofcolors < 8) {
	                var graystep = Math.floor(255 / (numberofcolors - 1));
	                for (var i = 0; i < numberofcolors; i += 1) {
	                    palette.push({ r: i * graystep,
	                        g: i * graystep,
	                        b: i * graystep,
	                        a: 255 });
	                }
	            } else {
	                var colorqnum = Math.floor(Math.pow(numberofcolors, 1 / 3));
	                var colorstep = Math.floor(255 / (colorqnum - 1));
	                var rndnum = numberofcolors - colorqnum * colorqnum * colorqnum;
	                for (rcnt = 0; rcnt < colorqnum; rcnt += 1) {
	                    for (gcnt = 0; gcnt < colorqnum; gcnt += 1) {
	                        for (bcnt = 0; bcnt < colorqnum; bcnt += 1) {
	                            palette.push({ r: rcnt * colorstep,
	                                g: gcnt * colorstep,
	                                b: bcnt * colorstep,
	                                a: 255 });
	                        }
	                    }
	                }
	                for (rcnt = 0; rcnt < rndnum; rcnt += 1) {
	                    palette.push({ r: Math.floor(Math.random() * 255),
	                        g: Math.floor(Math.random() * 255),
	                        b: Math.floor(Math.random() * 255),
	                        a: Math.floor(Math.random() * 255) });
	                }
	            }

	            return palette;
	        }
	    }, {
	        key: 'layering',
	        value: function layering(ii) {
	            var layers = [];
	            var val = 0;
	            var ah = ii.array.length;
	            var aw = ii.array[0].length;
	            var n1 = void 0;
	            var n2 = void 0;
	            var n3 = void 0;
	            var n4 = void 0;
	            var n5 = void 0;
	            var n6 = void 0;
	            var n7 = void 0;
	            var n8 = void 0;
	            var i = void 0;
	            var j = void 0;
	            var k = void 0;
	            for (k = 0; k < ii.palette.length; k += 1) {
	                layers[k] = [];
	                for (j = 0; j < ah; j += 1) {
	                    layers[k][j] = [];
	                    for (i = 0; i < aw; i += 1) {
	                        layers[k][j][i] = 0;
	                    }
	                }
	            }
	            for (j = 1; j < ah - 1; j += 1) {
	                for (i = 1; i < aw - 1; i += 1) {
	                    val = ii.array[j][i];

	                    n1 = ii.array[j - 1][i - 1] === val ? 1 : 0;
	                    n2 = ii.array[j - 1][i] === val ? 1 : 0;
	                    n3 = ii.array[j - 1][i + 1] === val ? 1 : 0;
	                    n4 = ii.array[j][i - 1] === val ? 1 : 0;
	                    n5 = ii.array[j][i + 1] === val ? 1 : 0;
	                    n6 = ii.array[j + 1][i - 1] === val ? 1 : 0;
	                    n7 = ii.array[j + 1][i] === val ? 1 : 0;
	                    n8 = ii.array[j + 1][i + 1] === val ? 1 : 0;

	                    layers[val][j + 1][i + 1] = 1 + n5 * 2 + n8 * 4 + n7 * 8;
	                    if (!n4) {
	                        layers[val][j + 1][i] = 0 + 2 + n7 * 4 + n6 * 8;
	                    }
	                    if (!n2) {
	                        layers[val][j][i + 1] = 0 + n3 * 2 + n5 * 4 + 8;
	                    }
	                    if (!n1) {
	                        layers[val][j][i] = 0 + n2 * 2 + 4 + n4 * 8;
	                    }
	                }
	            }

	            return layers;
	        }
	    }, {
	        key: 'layeringstep',
	        value: function layeringstep(ii, cnum) {
	            var layer = [];
	            var ah = ii.array.length;
	            var aw = ii.array[0].length;
	            var i = void 0;
	            var j = void 0;
	            for (j = 0; j < ah; j += 1) {
	                layer[j] = [];
	                for (i = 0; i < aw; i += 1) {
	                    layer[j][i] = 0;
	                }
	            }
	            for (j = 1; j < ah; j += 1) {
	                for (i = 1; i < aw; i += 1) {
	                    layer[j][i] = (ii.array[j - 1][i - 1] === cnum ? 1 : 0) + (ii.array[j - 1][i] === cnum ? 2 : 0) + (ii.array[j][i - 1] === cnum ? 8 : 0) + (ii.array[j][i] === cnum ? 4 : 0);
	                }
	            }

	            return layer;
	        }
	    }, {
	        key: 'pathscan',
	        value: function pathscan(arr, pathomit) {
	            var paths = [];
	            var pacnt = 0;
	            var pcnt = 0;
	            var px = 0;
	            var py = 0;
	            var w = arr[0].length;
	            var h = arr.length;
	            var dir = 0;
	            var pathfinished = true;
	            var holepath = false;
	            var lookuprow = void 0;
	            for (var j = 0; j < h; j += 1) {
	                for (var i = 0; i < w; i += 1) {
	                    if (arr[j][i] === 4 || arr[j][i] === 11) {
	                        px = i;
	                        py = j;
	                        paths[pacnt] = {};
	                        paths[pacnt].points = [];
	                        paths[pacnt].boundingbox = [px, py, px, py];
	                        paths[pacnt].holechildren = [];
	                        pathfinished = false;
	                        pcnt = 0;
	                        holepath = arr[j][i] === 11;
	                        dir = 1;

	                        while (!pathfinished) {
	                            paths[pacnt].points[pcnt] = {};
	                            paths[pacnt].points[pcnt].x = px - 1;
	                            paths[pacnt].points[pcnt].y = py - 1;
	                            paths[pacnt].points[pcnt].t = arr[py][px];

	                            if (px - 1 < paths[pacnt].boundingbox[0]) {
	                                paths[pacnt].boundingbox[0] = px - 1;
	                            }
	                            if (px - 1 > paths[pacnt].boundingbox[2]) {
	                                paths[pacnt].boundingbox[2] = px - 1;
	                            }
	                            if (py - 1 < paths[pacnt].boundingbox[1]) {
	                                paths[pacnt].boundingbox[1] = py - 1;
	                            }
	                            if (py - 1 > paths[pacnt].boundingbox[3]) {
	                                paths[pacnt].boundingbox[3] = py - 1;
	                            }

	                            lookuprow = this.pathscan_combined_lookup[arr[py][px]][dir];
	                            arr[py][px] = lookuprow[0];dir = lookuprow[1];px += lookuprow[2];py += lookuprow[3];

	                            if (px - 1 === paths[pacnt].points[0].x && py - 1 === paths[pacnt].points[0].y) {
	                                pathfinished = true;

	                                if (paths[pacnt].points.length < pathomit) {
	                                    paths.pop();
	                                } else {
	                                    paths[pacnt].isholepath = !!holepath;

	                                    if (holepath) {
	                                        var parentidx = 0,
	                                            parentbbox = [-1, -1, w + 1, h + 1];
	                                        for (var parentcnt = 0; parentcnt < pacnt; parentcnt++) {
	                                            if (!paths[parentcnt].isholepath && this.boundingboxincludes(paths[parentcnt].boundingbox, paths[pacnt].boundingbox) && this.boundingboxincludes(parentbbox, paths[parentcnt].boundingbox)) {
	                                                parentidx = parentcnt;
	                                                parentbbox = paths[parentcnt].boundingbox;
	                                            }
	                                        }
	                                        paths[parentidx].holechildren.push(pacnt);
	                                    }
	                                    pacnt += 1;
	                                }
	                            }
	                            pcnt += 1;
	                        }
	                    }
	                }
	            }

	            return paths;
	        }
	    }, {
	        key: 'boundingboxincludes',
	        value: function boundingboxincludes(parentbbox, childbbox) {
	            return parentbbox[0] < childbbox[0] && parentbbox[1] < childbbox[1] && parentbbox[2] > childbbox[2] && parentbbox[3] > childbbox[3];
	        }
	    }, {
	        key: 'batchpathscan',
	        value: function batchpathscan(layers, pathomit) {
	            var bpaths = [];
	            for (var k in layers) {
	                if (!layers.hasOwnProperty(k)) {
	                    continue;
	                }
	                bpaths[k] = this.pathscan(layers[k], pathomit);
	            }

	            return bpaths;
	        }
	    }, {
	        key: 'internodes',
	        value: function internodes(paths, options) {
	            var ins = [];
	            var palen = 0;
	            var nextidx = 0;
	            var nextidx2 = 0;
	            var previdx = 0;
	            var previdx2 = 0;
	            var pacnt = void 0;
	            var pcnt = void 0;
	            for (pacnt = 0; pacnt < paths.length; pacnt += 1) {
	                ins[pacnt] = {};
	                ins[pacnt].points = [];
	                ins[pacnt].boundingbox = paths[pacnt].boundingbox;
	                ins[pacnt].holechildren = paths[pacnt].holechildren;
	                ins[pacnt].isholepath = paths[pacnt].isholepath;
	                palen = paths[pacnt].points.length;

	                for (pcnt = 0; pcnt < palen; pcnt += 1) {
	                    nextidx = (pcnt + 1) % palen;nextidx2 = (pcnt + 2) % palen;previdx = (pcnt - 1 + palen) % palen;previdx2 = (pcnt - 2 + palen) % palen;

	                    if (options.rightangleenhance && this.testrightangle(paths[pacnt], previdx2, previdx, pcnt, nextidx, nextidx2)) {
	                        if (ins[pacnt].points.length > 0) {
	                            ins[pacnt].points[ins[pacnt].points.length - 1].linesegment = this.getdirection(ins[pacnt].points[ins[pacnt].points.length - 1].x, ins[pacnt].points[ins[pacnt].points.length - 1].y, paths[pacnt].points[pcnt].x, paths[pacnt].points[pcnt].y);
	                        }

	                        ins[pacnt].points.push({
	                            x: paths[pacnt].points[pcnt].x,
	                            y: paths[pacnt].points[pcnt].y,
	                            linesegment: this.getdirection(paths[pacnt].points[pcnt].x, paths[pacnt].points[pcnt].y, (paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2, (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2)
	                        });
	                    }

	                    ins[pacnt].points.push({
	                        x: (paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2,
	                        y: (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2,
	                        linesegment: this.getdirection((paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2, (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2, (paths[pacnt].points[nextidx].x + paths[pacnt].points[nextidx2].x) / 2, (paths[pacnt].points[nextidx].y + paths[pacnt].points[nextidx2].y) / 2)
	                    });
	                }
	            }

	            return ins;
	        }
	    }, {
	        key: 'testrightangle',
	        value: function testrightangle(path, idx1, idx2, idx3, idx4, idx5) {
	            return path.points[idx3].x === path.points[idx1].x && path.points[idx3].x === path.points[idx2].x && path.points[idx3].y === path.points[idx4].y && path.points[idx3].y === path.points[idx5].y || path.points[idx3].y === path.points[idx1].y && path.points[idx3].y === path.points[idx2].y && path.points[idx3].x === path.points[idx4].x && path.points[idx3].x === path.points[idx5].x;
	        }
	    }, {
	        key: 'getdirection',
	        value: function getdirection(x1, y1, x2, y2) {
	            var val = 8;
	            if (x1 < x2) {
	                if (y1 < y2) {
	                    val = 1;
	                } else if (y1 > y2) {
	                    val = 7;
	                } else {
	                    val = 0;
	                }
	            } else if (x1 > x2) {
	                if (y1 < y2) {
	                    val = 3;
	                } else if (y1 > y2) {
	                    val = 5;
	                } else {
	                    val = 4;
	                }
	            } else if (y1 < y2) {
	                val = 2;
	            } else if (y1 > y2) {
	                val = 6;
	            } else {
	                val = 8;
	            }

	            return val;
	        }
	    }, {
	        key: 'batchinternodes',
	        value: function batchinternodes(bpaths, options) {
	            var binternodes = [];
	            for (var k in bpaths) {
	                if (!bpaths.hasOwnProperty(k)) {
	                    continue;
	                }
	                binternodes[k] = this.internodes(bpaths[k], options);
	            }

	            return binternodes;
	        }
	    }, {
	        key: 'tracepath',
	        value: function tracepath(path, ltres, qtres) {
	            var pcnt = 0;
	            var segtype1 = void 0;
	            var segtype2 = void 0;
	            var seqend = void 0;
	            var smp = {};
	            smp.segments = [];
	            smp.boundingbox = path.boundingbox;
	            smp.holechildren = path.holechildren;
	            smp.isholepath = path.isholepath;

	            while (pcnt < path.points.length) {
	                segtype1 = path.points[pcnt].linesegment;
	                segtype2 = -1;
	                seqend = pcnt + 1;
	                while ((path.points[seqend].linesegment === segtype1 || path.points[seqend].linesegment === segtype2 || segtype2 === -1) && seqend < path.points.length - 1) {
	                    if (path.points[seqend].linesegment !== segtype1 && segtype2 === -1) {
	                        segtype2 = path.points[seqend].linesegment;
	                    }
	                    seqend += 1;
	                }
	                if (seqend === path.points.length - 1) {
	                    seqend = 0;
	                }

	                smp.segments = smp.segments.concat(this.fitseq(path, ltres, qtres, pcnt, seqend));

	                if (seqend > 0) {
	                    pcnt = seqend;
	                } else {
	                    pcnt = path.points.length;
	                }
	            }

	            return smp;
	        }
	    }, {
	        key: 'fitseq',
	        value: function fitseq(path, ltres, qtres, seqstart, seqend) {
	            if (seqend > path.points.length || seqend < 0) {
	                return [];
	            }
	            var errorpoint = seqstart,
	                errorval = 0,
	                curvepass = true,
	                px = void 0,
	                py = void 0,
	                dist2 = void 0;
	            var tl = seqend - seqstart;if (tl < 0) {
	                tl += path.points.length;
	            }
	            var vx = (path.points[seqend].x - path.points[seqstart].x) / tl,
	                vy = (path.points[seqend].y - path.points[seqstart].y) / tl;
	            var pcnt = (seqstart + 1) % path.points.length,
	                pl = void 0;
	            while (pcnt != seqend) {
	                pl = pcnt - seqstart;if (pl < 0) {
	                    pl += path.points.length;
	                }
	                px = path.points[seqstart].x + vx * pl;py = path.points[seqstart].y + vy * pl;
	                dist2 = (path.points[pcnt].x - px) * (path.points[pcnt].x - px) + (path.points[pcnt].y - py) * (path.points[pcnt].y - py);
	                if (dist2 > ltres) {
	                    curvepass = false;
	                }
	                if (dist2 > errorval) {
	                    errorpoint = pcnt;errorval = dist2;
	                }
	                pcnt = (pcnt + 1) % path.points.length;
	            }
	            if (curvepass) {
	                return [{ type: 'L',
	                    x1: path.points[seqstart].x,
	                    y1: path.points[seqstart].y,
	                    x2: path.points[seqend].x,
	                    y2: path.points[seqend].y }];
	            }
	            var fitpoint = errorpoint;curvepass = true;errorval = 0;
	            var t = (fitpoint - seqstart) / tl,
	                t1 = (1 - t) * (1 - t),
	                t2 = 2 * (1 - t) * t,
	                t3 = t * t;
	            var cpx = (t1 * path.points[seqstart].x + t3 * path.points[seqend].x - path.points[fitpoint].x) / -t2,
	                cpy = (t1 * path.points[seqstart].y + t3 * path.points[seqend].y - path.points[fitpoint].y) / -t2;
	            pcnt = seqstart + 1;
	            while (pcnt != seqend) {
	                t = (pcnt - seqstart) / tl;t1 = (1 - t) * (1 - t);t2 = 2 * (1 - t) * t;t3 = t * t;
	                px = t1 * path.points[seqstart].x + t2 * cpx + t3 * path.points[seqend].x;
	                py = t1 * path.points[seqstart].y + t2 * cpy + t3 * path.points[seqend].y;
	                dist2 = (path.points[pcnt].x - px) * (path.points[pcnt].x - px) + (path.points[pcnt].y - py) * (path.points[pcnt].y - py);
	                if (dist2 > qtres) {
	                    curvepass = false;
	                }
	                if (dist2 > errorval) {
	                    errorpoint = pcnt;errorval = dist2;
	                }
	                pcnt = (pcnt + 1) % path.points.length;
	            }
	            if (curvepass) {
	                return [{ type: 'Q',
	                    x1: path.points[seqstart].x,
	                    y1: path.points[seqstart].y,
	                    x2: cpx,
	                    y2: cpy,
	                    x3: path.points[seqend].x,
	                    y3: path.points[seqend].y }];
	            }
	            var splitpoint = fitpoint;

	            return this.fitseq(path, ltres, qtres, seqstart, splitpoint).concat(this.fitseq(path, ltres, qtres, splitpoint, seqend));
	        }
	    }, {
	        key: 'batchtracepaths',
	        value: function batchtracepaths(internodepaths, ltres, qtres) {
	            var btracedpaths = [];
	            for (var k in internodepaths) {
	                if (!internodepaths.hasOwnProperty(k)) {
	                    continue;
	                }
	                btracedpaths.push(this.tracepath(internodepaths[k], ltres, qtres));
	            }

	            return btracedpaths;
	        }
	    }, {
	        key: 'batchtracelayers',
	        value: function batchtracelayers(binternodes, ltres, qtres) {
	            var btbis = [];
	            for (var k in binternodes) {
	                if (!binternodes.hasOwnProperty(k)) {
	                    continue;
	                }
	                btbis[k] = this.batchtracepaths(binternodes[k], ltres, qtres);
	            }

	            return btbis;
	        }
	    }, {
	        key: 'roundtodec',
	        value: function roundtodec(val, places) {
	            return Number(val.toFixed(places));
	        }
	    }, {
	        key: 'svgpathstring',
	        value: function svgpathstring(tracedata, lnum, pathnum, options) {
	            var layer = tracedata.layers[lnum],
	                smp = layer[pathnum],
	                str = '',
	                pcnt = void 0;
	            if (options.linefilter && smp.segments.length < 3) {
	                return str;
	            }
	            str = '<path ' + (options.desc ? 'desc="l ' + lnum + ' p ' + pathnum + '" ' : '') + this.tosvgcolorstr(tracedata.palette[lnum], options) + 'd="';
	            if (options.roundcoords === -1) {
	                str += 'M ' + smp.segments[0].x1 * options.scale + ' ' + smp.segments[0].y1 * options.scale + ' ';
	                for (pcnt = 0; pcnt < smp.segments.length; pcnt++) {
	                    str += smp.segments[pcnt].type + ' ' + smp.segments[pcnt].x2 * options.scale + ' ' + smp.segments[pcnt].y2 * options.scale + ' ';
	                    if (smp.segments[pcnt].hasOwnProperty('x3')) {
	                        str += smp.segments[pcnt].x3 * options.scale + ' ' + smp.segments[pcnt].y3 * options.scale + ' ';
	                    }
	                }
	                str += 'Z ';
	            } else {
	                str += 'M ' + this.roundtodec(smp.segments[0].x1 * options.scale, options.roundcoords) + ' ' + this.roundtodec(smp.segments[0].y1 * options.scale, options.roundcoords) + ' ';
	                for (pcnt = 0; pcnt < smp.segments.length; pcnt++) {
	                    str += smp.segments[pcnt].type + ' ' + this.roundtodec(smp.segments[pcnt].x2 * options.scale, options.roundcoords) + ' ' + this.roundtodec(smp.segments[pcnt].y2 * options.scale, options.roundcoords) + ' ';
	                    if (smp.segments[pcnt].hasOwnProperty('x3')) {
	                        str += this.roundtodec(smp.segments[pcnt].x3 * options.scale, options.roundcoords) + ' ' + this.roundtodec(smp.segments[pcnt].y3 * options.scale, options.roundcoords) + ' ';
	                    }
	                }
	                str += 'Z ';
	            }
	            for (var hcnt = 0; hcnt < smp.holechildren.length; hcnt++) {
	                var hsmp = layer[smp.holechildren[hcnt]];

	                if (options.roundcoords === -1) {
	                    if (hsmp.segments[hsmp.segments.length - 1].hasOwnProperty('x3')) {
	                        str += 'M ' + hsmp.segments[hsmp.segments.length - 1].x3 * options.scale + ' ' + hsmp.segments[hsmp.segments.length - 1].y3 * options.scale + ' ';
	                    } else {
	                        str += 'M ' + hsmp.segments[hsmp.segments.length - 1].x2 * options.scale + ' ' + hsmp.segments[hsmp.segments.length - 1].y2 * options.scale + ' ';
	                    }
	                    for (pcnt = hsmp.segments.length - 1; pcnt >= 0; pcnt--) {
	                        str += hsmp.segments[pcnt].type + ' ';
	                        if (hsmp.segments[pcnt].hasOwnProperty('x3')) {
	                            str += hsmp.segments[pcnt].x2 * options.scale + ' ' + hsmp.segments[pcnt].y2 * options.scale + ' ';
	                        }
	                        str += hsmp.segments[pcnt].x1 * options.scale + ' ' + hsmp.segments[pcnt].y1 * options.scale + ' ';
	                    }
	                } else {
	                    if (hsmp.segments[hsmp.segments.length - 1].hasOwnProperty('x3')) {
	                        str += 'M ' + this.roundtodec(hsmp.segments[hsmp.segments.length - 1].x3 * options.scale) + ' ' + this.roundtodec(hsmp.segments[hsmp.segments.length - 1].y3 * options.scale) + ' ';
	                    } else {
	                        str += 'M ' + this.roundtodec(hsmp.segments[hsmp.segments.length - 1].x2 * options.scale) + ' ' + this.roundtodec(hsmp.segments[hsmp.segments.length - 1].y2 * options.scale) + ' ';
	                    }
	                    for (pcnt = hsmp.segments.length - 1; pcnt >= 0; pcnt--) {
	                        str += hsmp.segments[pcnt].type + ' ';
	                        if (hsmp.segments[pcnt].hasOwnProperty('x3')) {
	                            str += this.roundtodec(hsmp.segments[pcnt].x2 * options.scale) + ' ' + this.roundtodec(hsmp.segments[pcnt].y2 * options.scale) + ' ';
	                        }
	                        str += this.roundtodec(hsmp.segments[pcnt].x1 * options.scale) + ' ' + this.roundtodec(hsmp.segments[pcnt].y1 * options.scale) + ' ';
	                    }
	                }
	                str += 'Z ';
	            }
	            str += '" />';
	            if (options.lcpr || options.qcpr) {
	                for (pcnt = 0; pcnt < smp.segments.length; pcnt++) {
	                    if (smp.segments[pcnt].hasOwnProperty('x3') && options.qcpr) {
	                        str += '<circle cx="' + smp.segments[pcnt].x2 * options.scale + '" cy="' + smp.segments[pcnt].y2 * options.scale + '" r="' + options.qcpr + '" fill="cyan" stroke-width="' + options.qcpr * 0.2 + '" stroke="black" />';
	                        str += '<circle cx="' + smp.segments[pcnt].x3 * options.scale + '" cy="' + smp.segments[pcnt].y3 * options.scale + '" r="' + options.qcpr + '" fill="white" stroke-width="' + options.qcpr * 0.2 + '" stroke="black" />';
	                        str += '<line x1="' + smp.segments[pcnt].x1 * options.scale + '" y1="' + smp.segments[pcnt].y1 * options.scale + '" x2="' + smp.segments[pcnt].x2 * options.scale + '" y2="' + smp.segments[pcnt].y2 * options.scale + '" stroke-width="' + options.qcpr * 0.2 + '" stroke="cyan" />';
	                        str += '<line x1="' + smp.segments[pcnt].x2 * options.scale + '" y1="' + smp.segments[pcnt].y2 * options.scale + '" x2="' + smp.segments[pcnt].x3 * options.scale + '" y2="' + smp.segments[pcnt].y3 * options.scale + '" stroke-width="' + options.qcpr * 0.2 + '" stroke="cyan" />';
	                    }
	                    if (!smp.segments[pcnt].hasOwnProperty('x3') && options.lcpr) {
	                        str += '<circle cx="' + smp.segments[pcnt].x2 * options.scale + '" cy="' + smp.segments[pcnt].y2 * options.scale + '" r="' + options.lcpr + '" fill="white" stroke-width="' + options.lcpr * 0.2 + '" stroke="black" />';
	                    }
	                }

	                for (var hcnt = 0; hcnt < smp.holechildren.length; hcnt++) {
	                    var hsmp = layer[smp.holechildren[hcnt]];
	                    for (pcnt = 0; pcnt < hsmp.segments.length; pcnt++) {
	                        if (hsmp.segments[pcnt].hasOwnProperty('x3') && options.qcpr) {
	                            str += '<circle cx="' + hsmp.segments[pcnt].x2 * options.scale + '" cy="' + hsmp.segments[pcnt].y2 * options.scale + '" r="' + options.qcpr + '" fill="cyan" stroke-width="' + options.qcpr * 0.2 + '" stroke="black" />';
	                            str += '<circle cx="' + hsmp.segments[pcnt].x3 * options.scale + '" cy="' + hsmp.segments[pcnt].y3 * options.scale + '" r="' + options.qcpr + '" fill="white" stroke-width="' + options.qcpr * 0.2 + '" stroke="black" />';
	                            str += '<line x1="' + hsmp.segments[pcnt].x1 * options.scale + '" y1="' + hsmp.segments[pcnt].y1 * options.scale + '" x2="' + hsmp.segments[pcnt].x2 * options.scale + '" y2="' + hsmp.segments[pcnt].y2 * options.scale + '" stroke-width="' + options.qcpr * 0.2 + '" stroke="cyan" />';
	                            str += '<line x1="' + hsmp.segments[pcnt].x2 * options.scale + '" y1="' + hsmp.segments[pcnt].y2 * options.scale + '" x2="' + hsmp.segments[pcnt].x3 * options.scale + '" y2="' + hsmp.segments[pcnt].y3 * options.scale + '" stroke-width="' + options.qcpr * 0.2 + '" stroke="cyan" />';
	                        }
	                        if (!hsmp.segments[pcnt].hasOwnProperty('x3') && options.lcpr) {
	                            str += '<circle cx="' + hsmp.segments[pcnt].x2 * options.scale + '" cy="' + hsmp.segments[pcnt].y2 * options.scale + '" r="' + options.lcpr + '" fill="white" stroke-width="' + options.lcpr * 0.2 + '" stroke="black" />';
	                        }
	                    }
	                }
	            }

	            return str;
	        }
	    }, {
	        key: 'getsvgstring',
	        value: function getsvgstring(tracedata, options) {
	            options = this.checkoptions(options);
	            var w = tracedata.width * options.scale;
	            var h = tracedata.height * options.scale;

	            var svgstr = '<svg ' + (options.viewbox ? 'viewBox="0 0 ' + w + ' ' + h + '" ' : 'width="' + w + '" height="' + h + '" ') + 'version="1.1" xmlns="http://www.w3.org/2000/svg" desc="Created with imagetracer.js version ' + this.versionnumber + '" >';
	            for (var lcnt = 0; lcnt < tracedata.layers.length; lcnt += 1) {
	                for (var pcnt = 0; pcnt < tracedata.layers[lcnt].length; pcnt += 1) {
	                    if (!tracedata.layers[lcnt][pcnt].isholepath) {
	                        svgstr += this.svgpathstring(tracedata, lcnt, pcnt, options);
	                    }
	                }
	            }
	            svgstr += '</svg>';

	            return svgstr;
	        }
	    }, {
	        key: 'compareNumbers',
	        value: function compareNumbers(a, b) {
	            return a - b;
	        }
	    }, {
	        key: 'torgbastr',
	        value: function torgbastr(c) {
	            return 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + c.a + ')';
	        }
	    }, {
	        key: 'tosvgcolorstr',
	        value: function tosvgcolorstr(c, options) {
	            return 'fill="rgb(' + c.r + ',' + c.g + ',' + c.b + ')" stroke="rgb(' + c.r + ',' + c.g + ',' + c.b + ')" stroke-width="' + options.strokewidth + '" opacity="' + c.a / 255.0 + '" ';
	        }
	    }, {
	        key: 'appendSVGString',
	        value: function appendSVGString(svgstr, parentid) {
	            var div = void 0;
	            if (parentid) {
	                div = document.getElementById(parentid);
	                if (!div) {
	                    div = document.createElement('div');
	                    div.id = parentid;
	                    document.body.appendChild(div);
	                }
	            } else {
	                div = document.createElement('div');
	                document.body.appendChild(div);
	            }
	            div.innerHTML += svgstr;
	        }
	    }, {
	        key: 'blur',
	        value: function blur(imgd, radius, delta) {
	            var i = void 0,
	                j = void 0,
	                k = void 0,
	                d = void 0,
	                idx = void 0,
	                racc = void 0,
	                gacc = void 0,
	                bacc = void 0,
	                aacc = void 0,
	                wacc = void 0;
	            var imgd2 = { width: imgd.width,
	                height: imgd.height,
	                data: [] };
	            radius = Math.floor(radius);if (radius < 1) {
	                return imgd;
	            }if (radius > 5) {
	                radius = 5;
	            }delta = Math.abs(delta);if (delta > 1024) {
	                delta = 1024;
	            }
	            var thisgk = this.gks[radius - 1];
	            for (j = 0; j < imgd.height; j++) {
	                for (i = 0; i < imgd.width; i++) {
	                    racc = 0;gacc = 0;bacc = 0;aacc = 0;wacc = 0;

	                    for (k = -radius; k < radius + 1; k++) {
	                        if (i + k > 0 && i + k < imgd.width) {
	                            idx = (j * imgd.width + i + k) * 4;
	                            racc += imgd.data[idx] * thisgk[k + radius];
	                            gacc += imgd.data[idx + 1] * thisgk[k + radius];
	                            bacc += imgd.data[idx + 2] * thisgk[k + radius];
	                            aacc += imgd.data[idx + 3] * thisgk[k + radius];
	                            wacc += thisgk[k + radius];
	                        }
	                    }

	                    idx = (j * imgd.width + i) * 4;
	                    imgd2.data[idx] = Math.floor(racc / wacc);
	                    imgd2.data[idx + 1] = Math.floor(gacc / wacc);
	                    imgd2.data[idx + 2] = Math.floor(bacc / wacc);
	                    imgd2.data[idx + 3] = Math.floor(aacc / wacc);
	                }
	            }
	            var himgd = new Uint8ClampedArray(imgd2.data);
	            for (j = 0; j < imgd.height; j++) {
	                for (i = 0; i < imgd.width; i++) {
	                    racc = 0;gacc = 0;bacc = 0;aacc = 0;wacc = 0;

	                    for (k = -radius; k < radius + 1; k++) {
	                        if (j + k > 0 && j + k < imgd.height) {
	                            idx = ((j + k) * imgd.width + i) * 4;
	                            racc += himgd[idx] * thisgk[k + radius];
	                            gacc += himgd[idx + 1] * thisgk[k + radius];
	                            bacc += himgd[idx + 2] * thisgk[k + radius];
	                            aacc += himgd[idx + 3] * thisgk[k + radius];
	                            wacc += thisgk[k + radius];
	                        }
	                    }

	                    idx = (j * imgd.width + i) * 4;
	                    imgd2.data[idx] = Math.floor(racc / wacc);
	                    imgd2.data[idx + 1] = Math.floor(gacc / wacc);
	                    imgd2.data[idx + 2] = Math.floor(bacc / wacc);
	                    imgd2.data[idx + 3] = Math.floor(aacc / wacc);
	                }
	            }
	            for (j = 0; j < imgd.height; j++) {
	                for (i = 0; i < imgd.width; i++) {
	                    idx = (j * imgd.width + i) * 4;

	                    d = Math.abs(imgd2.data[idx] - imgd.data[idx]) + Math.abs(imgd2.data[idx + 1] - imgd.data[idx + 1]) + Math.abs(imgd2.data[idx + 2] - imgd.data[idx + 2]) + Math.abs(imgd2.data[idx + 3] - imgd.data[idx + 3]);

	                    if (d > delta) {
	                        imgd2.data[idx] = imgd.data[idx];
	                        imgd2.data[idx + 1] = imgd.data[idx + 1];
	                        imgd2.data[idx + 2] = imgd.data[idx + 2];
	                        imgd2.data[idx + 3] = imgd.data[idx + 3];
	                    }
	                }
	            }

	            return imgd2;
	        }
	    }, {
	        key: 'loadImage',
	        value: function loadImage(url, callback, options) {
	            var img = new Image();
	            if (options && options.corsenabled) {
	                img.crossOrigin = 'Anonymous';
	            }
	            img.src = url;
	            img.onload = function () {
	                var canvas = document.createElement('canvas');
	                canvas.width = img.width;
	                canvas.height = img.height;
	                var context = canvas.getContext('2d');
	                context.drawImage(img, 0, 0);
	                callback(canvas);
	            };
	        }
	    }, {
	        key: 'getImgdata',
	        value: function getImgdata(canvas) {
	            var context = canvas.getContext('2d');

	            return context.getImageData(0, 0, canvas.width, canvas.height);
	        }
	    }, {
	        key: 'drawLayers',
	        value: function drawLayers(layers, palette, scale, parentid) {
	            scale = scale || 1;
	            var w = void 0,
	                h = void 0,
	                i = void 0,
	                j = void 0,
	                k = void 0;
	            var div = void 0;
	            if (parentid) {
	                div = document.getElementById(parentid);
	                if (!div) {
	                    div = document.createElement('div');
	                    div.id = parentid;
	                    document.body.appendChild(div);
	                }
	            } else {
	                div = document.createElement('div');
	                document.body.appendChild(div);
	            }
	            for (k in layers) {
	                if (!layers.hasOwnProperty(k)) {
	                    continue;
	                }

	                w = layers[k][0].length;
	                h = layers[k].length;

	                var canvas = document.createElement('canvas');
	                canvas.width = w * scale;
	                canvas.height = h * scale;
	                var context = canvas.getContext('2d');

	                for (j = 0; j < h; j += 1) {
	                    for (i = 0; i < w; i += 1) {
	                        context.fillStyle = this.torgbastr(palette[layers[k][j][i] % palette.length]);
	                        context.fillRect(i * scale, j * scale, scale, scale);
	                    }
	                }

	                div.appendChild(canvas);
	            }
	        }
	    }]);

	    return ImageTracer;
	}();

	exports.default = ImageTracer;

/***/ }),
/* 104 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * @fileoverview Graphics module
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      */


	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	var _imageLoader = __webpack_require__(106);

	var _imageLoader2 = _interopRequireDefault(_imageLoader);

	var _cropper = __webpack_require__(108);

	var _cropper2 = _interopRequireDefault(_cropper);

	var _flip = __webpack_require__(110);

	var _flip2 = _interopRequireDefault(_flip);

	var _rotation = __webpack_require__(111);

	var _rotation2 = _interopRequireDefault(_rotation);

	var _freeDrawing = __webpack_require__(112);

	var _freeDrawing2 = _interopRequireDefault(_freeDrawing);

	var _line = __webpack_require__(113);

	var _line2 = _interopRequireDefault(_line);

	var _text = __webpack_require__(114);

	var _text2 = _interopRequireDefault(_text);

	var _icon = __webpack_require__(115);

	var _icon2 = _interopRequireDefault(_icon);

	var _filter = __webpack_require__(116);

	var _filter2 = _interopRequireDefault(_filter);

	var _shape = __webpack_require__(122);

	var _shape2 = _interopRequireDefault(_shape);

	var _cropper3 = __webpack_require__(124);

	var _cropper4 = _interopRequireDefault(_cropper3);

	var _freeDrawing3 = __webpack_require__(126);

	var _freeDrawing4 = _interopRequireDefault(_freeDrawing3);

	var _lineDrawing = __webpack_require__(127);

	var _lineDrawing2 = _interopRequireDefault(_lineDrawing);

	var _shape3 = __webpack_require__(128);

	var _shape4 = _interopRequireDefault(_shape3);

	var _text3 = __webpack_require__(129);

	var _text4 = _interopRequireDefault(_text3);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	var _util = __webpack_require__(72);

	var _util2 = _interopRequireDefault(_util);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	var components = _consts2.default.componentNames;
	var events = _consts2.default.eventNames;

	var drawingModes = _consts2.default.drawingModes,
	    fObjectOptions = _consts2.default.fObjectOptions;
	var extend = _tuiCodeSnippet2.default.extend,
	    stamp = _tuiCodeSnippet2.default.stamp,
	    isArray = _tuiCodeSnippet2.default.isArray,
	    isString = _tuiCodeSnippet2.default.isString,
	    forEachArray = _tuiCodeSnippet2.default.forEachArray,
	    forEachOwnProperties = _tuiCodeSnippet2.default.forEachOwnProperties,
	    CustomEvents = _tuiCodeSnippet2.default.CustomEvents;


	var DEFAULT_CSS_MAX_WIDTH = 1000;
	var DEFAULT_CSS_MAX_HEIGHT = 800;

	var cssOnly = {
	    cssOnly: true
	};
	var backstoreOnly = {
	    backstoreOnly: true
	};

	/**
	 * Graphics class
	 * @class
	 * @param {string|jQuery|HTMLElement} wrapper - Wrapper's element or selector
	 * @param {Object} [option] - Canvas max width & height of css
	 *  @param {number} option.cssMaxWidth - Canvas css-max-width
	 *  @param {number} option.cssMaxHeight - Canvas css-max-height
	 *  @param {boolean} option.useItext - Use IText in text mode
	 *  @param {boolean} option.useDragAddIcon - Use dragable add in icon mode
	 * @ignore
	 */

	var Graphics = function () {
	    function Graphics(element) {
	        var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
	            cssMaxWidth = _ref.cssMaxWidth,
	            cssMaxHeight = _ref.cssMaxHeight,
	            _ref$useItext = _ref.useItext,
	            useItext = _ref$useItext === undefined ? false : _ref$useItext,
	            _ref$useDragAddIcon = _ref.useDragAddIcon,
	            useDragAddIcon = _ref$useDragAddIcon === undefined ? false : _ref$useDragAddIcon;

	        _classCallCheck(this, Graphics);

	        /**
	         * Fabric image instance
	         * @type {fabric.Image}
	         */
	        this.canvasImage = null;

	        /**
	         * Max width of canvas elements
	         * @type {number}
	         */
	        this.cssMaxWidth = cssMaxWidth || DEFAULT_CSS_MAX_WIDTH;

	        /**
	         * Max height of canvas elements
	         * @type {number}
	         */
	        this.cssMaxHeight = cssMaxHeight || DEFAULT_CSS_MAX_HEIGHT;

	        /**
	         * Use Itext mode for text component
	         * @type {boolean}
	         */
	        this.useItext = useItext;

	        /**
	         * Use add drag icon mode for icon component
	         * @type {boolean}
	         */
	        this.useDragAddIcon = useDragAddIcon;

	        /**
	         * cropper Selection Style
	         * @type {Object}
	         */
	        this.cropSelectionStyle = {};

	        /**
	         * Image name
	         * @type {string}
	         */
	        this.imageName = '';

	        /**
	         * Object Map
	         * @type {Object}
	         * @private
	         */
	        this._objects = {};

	        /**
	         * Fabric-Canvas instance
	         * @type {fabric.Canvas}
	         * @private
	         */
	        this._canvas = null;

	        /**
	         * Drawing mode
	         * @type {string}
	         * @private
	         */
	        this._drawingMode = drawingModes.NORMAL;

	        /**
	         * DrawingMode map
	         * @type {Object.<string, DrawingMode>}
	         * @private
	         */
	        this._drawingModeMap = {};

	        /**
	         * Component map
	         * @type {Object.<string, Component>}
	         * @private
	         */
	        this._componentMap = {};

	        /**
	         * fabric event handlers
	         * @type {Object.<string, function>}
	         * @private
	         */
	        this._handler = {
	            onMouseDown: this._onMouseDown.bind(this),
	            onObjectAdded: this._onObjectAdded.bind(this),
	            onObjectRemoved: this._onObjectRemoved.bind(this),
	            onObjectMoved: this._onObjectMoved.bind(this),
	            onObjectScaled: this._onObjectScaled.bind(this),
	            onObjectSelected: this._onObjectSelected.bind(this),
	            onPathCreated: this._onPathCreated.bind(this),
	            onSelectionCleared: this._onSelectionCleared.bind(this),
	            onSelectionCreated: this._onSelectionCreated.bind(this)
	        };

	        this._setCanvasElement(element);
	        this._createDrawingModeInstances();
	        this._createComponents();
	        this._attachCanvasEvents();
	    }

	    /**
	     * Destroy canvas element
	     */


	    _createClass(Graphics, [{
	        key: 'destroy',
	        value: function destroy() {
	            var wrapperEl = this._canvas.wrapperEl;


	            this._canvas.clear();

	            wrapperEl.parentNode.removeChild(wrapperEl);
	        }

	        /**
	         * Deactivates all objects on canvas
	         * @returns {Graphics} this
	         */

	    }, {
	        key: 'deactivateAll',
	        value: function deactivateAll() {
	            this._canvas.deactivateAll();

	            return this;
	        }

	        /**
	         * Renders all objects on canvas
	         * @returns {Graphics} this
	         */

	    }, {
	        key: 'renderAll',
	        value: function renderAll() {
	            this._canvas.renderAll();

	            return this;
	        }

	        /**
	         * Adds objects on canvas
	         * @param {Object|Array} objects - objects
	         */

	    }, {
	        key: 'add',
	        value: function add(objects) {
	            var _canvas;

	            var theArgs = [];
	            if (isArray(objects)) {
	                theArgs = objects;
	            } else {
	                theArgs.push(objects);
	            }

	            (_canvas = this._canvas).add.apply(_canvas, theArgs);
	        }

	        /**
	         * Removes the object or group
	         * @param {Object} target - graphics object or group
	         * @returns {boolean} true if contains or false
	         */

	    }, {
	        key: 'contains',
	        value: function contains(target) {
	            return this._canvas.contains(target);
	        }

	        /**
	         * Gets all objects or group
	         * @returns {Array} all objects, shallow copy
	         */

	    }, {
	        key: 'getObjects',
	        value: function getObjects() {
	            return this._canvas.getObjects().slice();
	        }

	        /**
	         * Get an object by id
	         * @param {number} id - object id
	         * @returns {fabric.Object} object corresponding id
	         */

	    }, {
	        key: 'getObject',
	        value: function getObject(id) {
	            return this._objects[id];
	        }

	        /**
	         * Removes the object or group
	         * @param {Object} target - graphics object or group
	         */

	    }, {
	        key: 'remove',
	        value: function remove(target) {
	            this._canvas.remove(target);
	        }

	        /**
	         * Removes all object or group
	         * @param {boolean} includesBackground - remove the background image or not
	         * @returns {Array} all objects array which is removed
	         */

	    }, {
	        key: 'removeAll',
	        value: function removeAll(includesBackground) {
	            var canvas = this._canvas;
	            var objects = canvas.getObjects().slice();
	            canvas.remove.apply(canvas, this._canvas.getObjects());

	            if (includesBackground) {
	                canvas.clear();
	            }

	            return objects;
	        }

	        /**
	         * Removes an object or group by id
	         * @param {number} id - object id
	         * @returns {Array} removed objects
	         */

	    }, {
	        key: 'removeObjectById',
	        value: function removeObjectById(id) {
	            var objects = [];
	            var canvas = this._canvas;
	            var target = this.getObject(id);
	            var isValidGroup = target && target.isType('group') && !target.isEmpty();

	            if (isValidGroup) {
	                canvas.discardActiveGroup(); // restore states for each objects
	                target.forEachObject(function (obj) {
	                    objects.push(obj);
	                    obj.remove();
	                });
	            } else if (canvas.contains(target)) {
	                objects.push(target);
	                target.remove();
	            }

	            return objects;
	        }

	        /**
	         * Get an id by object instance
	         * @param {fabric.Object} object object
	         * @returns {number} object id if it exists or null
	         */

	    }, {
	        key: 'getObjectId',
	        value: function getObjectId(object) {
	            var key = null;
	            for (key in this._objects) {
	                if (this._objects.hasOwnProperty(key)) {
	                    if (object === this._objects[key]) {
	                        return key;
	                    }
	                }
	            }

	            return null;
	        }

	        /**
	         * Gets an active object or group
	         * @returns {Object} active object or group instance
	         */

	    }, {
	        key: 'getActiveObject',
	        value: function getActiveObject() {
	            return this._canvas.getActiveObject();
	        }

	        /**
	         * Gets an active group object
	         * @returns {Object} active group object instance
	         */

	    }, {
	        key: 'getActiveGroupObject',
	        value: function getActiveGroupObject() {
	            return this._canvas.getActiveGroup();
	        }

	        /**
	         * Activates an object or group
	         * @param {Object} target - target object or group
	         */

	    }, {
	        key: 'setActiveObject',
	        value: function setActiveObject(target) {
	            this._canvas.setActiveObject(target);
	        }

	        /**
	         * Set Crop selection style
	         * @param {Object} style - Selection styles
	         */

	    }, {
	        key: 'setCropSelectionStyle',
	        value: function setCropSelectionStyle(style) {
	            this.cropSelectionStyle = style;
	        }

	        /**
	         * Get component
	         * @param {string} name - Component name
	         * @returns {Component}
	         */

	    }, {
	        key: 'getComponent',
	        value: function getComponent(name) {
	            return this._componentMap[name];
	        }

	        /**
	         * Get current drawing mode
	         * @returns {string}
	         */

	    }, {
	        key: 'getDrawingMode',
	        value: function getDrawingMode() {
	            return this._drawingMode;
	        }

	        /**
	         * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first.
	         * @param {String} mode Can be one of <I>'CROPPER', 'FREE_DRAWING', 'LINE', 'TEXT', 'SHAPE'</I>
	         * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING'
	         *  @param {Number} [option.width] brush width
	         *  @param {String} [option.color] brush color
	         * @returns {boolean} true if success or false
	         */

	    }, {
	        key: 'startDrawingMode',
	        value: function startDrawingMode(mode, option) {
	            if (this._isSameDrawingMode(mode)) {
	                return true;
	            }

	            // If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first.
	            this.stopDrawingMode();

	            var drawingModeInstance = this._getDrawingModeInstance(mode);
	            if (drawingModeInstance && drawingModeInstance.start) {
	                drawingModeInstance.start(this, option);

	                this._drawingMode = mode;
	            }

	            return !!drawingModeInstance;
	        }

	        /**
	         * Stop the current drawing mode and back to the 'NORMAL' mode
	         */

	    }, {
	        key: 'stopDrawingMode',
	        value: function stopDrawingMode() {
	            if (this._isSameDrawingMode(drawingModes.NORMAL)) {
	                return;
	            }

	            var drawingModeInstance = this._getDrawingModeInstance(this.getDrawingMode());
	            if (drawingModeInstance && drawingModeInstance.end) {
	                drawingModeInstance.end(this);
	            }
	            this._drawingMode = drawingModes.NORMAL;
	        }

	        /**
	         * To data url from canvas
	         * @param {string} type - A DOMString indicating the image format. The default type is image/png.
	         * @returns {string} A DOMString containing the requested data URI.
	         */

	    }, {
	        key: 'toDataURL',
	        value: function toDataURL(type) {
	            return this._canvas && this._canvas.toDataURL(type);
	        }

	        /**
	         * Save image(background) of canvas
	         * @param {string} name - Name of image
	         * @param {?fabric.Image} canvasImage - Fabric image instance
	         */

	    }, {
	        key: 'setCanvasImage',
	        value: function setCanvasImage(name, canvasImage) {
	            if (canvasImage) {
	                stamp(canvasImage);
	            }
	            this.imageName = name;
	            this.canvasImage = canvasImage;
	        }

	        /**
	         * Set css max dimension
	         * @param {{width: number, height: number}} maxDimension - Max width & Max height
	         */

	    }, {
	        key: 'setCssMaxDimension',
	        value: function setCssMaxDimension(maxDimension) {
	            this.cssMaxWidth = maxDimension.width || this.cssMaxWidth;
	            this.cssMaxHeight = maxDimension.height || this.cssMaxHeight;
	        }

	        /**
	         * Adjust canvas dimension with scaling image
	         */

	    }, {
	        key: 'adjustCanvasDimension',
	        value: function adjustCanvasDimension() {
	            var canvasImage = this.canvasImage.scale(1);

	            var _canvasImage$getBound = canvasImage.getBoundingRect(),
	                width = _canvasImage$getBound.width,
	                height = _canvasImage$getBound.height;

	            var maxDimension = this._calcMaxDimension(width, height);

	            this.setCanvasCssDimension({
	                width: '100%',
	                height: '100%', // Set height '' for IE9
	                'max-width': maxDimension.width + 'px',
	                'max-height': maxDimension.height + 'px'
	            });

	            this.setCanvasBackstoreDimension({
	                width: width,
	                height: height
	            });
	            this._canvas.centerObject(canvasImage);
	        }

	        /**
	         * Set canvas dimension - css only
	         *  {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions}
	         * @param {Object} dimension - Canvas css dimension
	         */

	    }, {
	        key: 'setCanvasCssDimension',
	        value: function setCanvasCssDimension(dimension) {
	            this._canvas.setDimensions(dimension, cssOnly);
	        }

	        /**
	         * Set canvas dimension - backstore only
	         *  {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions}
	         * @param {Object} dimension - Canvas backstore dimension
	         */

	    }, {
	        key: 'setCanvasBackstoreDimension',
	        value: function setCanvasBackstoreDimension(dimension) {
	            this._canvas.setDimensions(dimension, backstoreOnly);
	        }

	        /**
	         * Set image properties
	         * {@link http://fabricjs.com/docs/fabric.Image.html#set}
	         * @param {Object} setting - Image properties
	         * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas
	         */

	    }, {
	        key: 'setImageProperties',
	        value: function setImageProperties(setting, withRendering) {
	            var canvasImage = this.canvasImage;


	            if (!canvasImage) {
	                return;
	            }

	            canvasImage.set(setting).setCoords();
	            if (withRendering) {
	                this._canvas.renderAll();
	            }
	        }

	        /**
	         * Returns canvas element of fabric.Canvas[[lower-canvas]]
	         * @returns {HTMLCanvasElement}
	         */

	    }, {
	        key: 'getCanvasElement',
	        value: function getCanvasElement() {
	            return this._canvas.getElement();
	        }

	        /**
	         * Get fabric.Canvas instance
	         * @returns {fabric.Canvas}
	         * @private
	         */

	    }, {
	        key: 'getCanvas',
	        value: function getCanvas() {
	            return this._canvas;
	        }

	        /**
	         * Get canvasImage (fabric.Image instance)
	         * @returns {fabric.Image}
	         */

	    }, {
	        key: 'getCanvasImage',
	        value: function getCanvasImage() {
	            return this.canvasImage;
	        }

	        /**
	         * Get image name
	         * @returns {string}
	         */

	    }, {
	        key: 'getImageName',
	        value: function getImageName() {
	            return this.imageName;
	        }

	        /**
	         * Add image object on canvas
	         * @param {string} imgUrl - Image url to make object
	         * @returns {Promise}
	         */

	    }, {
	        key: 'addImageObject',
	        value: function addImageObject(imgUrl) {
	            var _this = this;

	            var callback = this._callbackAfterLoadingImageObject.bind(this);

	            return new _promise2.default(function (resolve) {
	                _fabric2.default.Image.fromURL(imgUrl, function (image) {
	                    callback(image);
	                    resolve(_this.createObjectProperties(image));
	                }, {
	                    crossOrigin: 'Anonymous'
	                });
	            });
	        }

	        /**
	         * Get center position of canvas
	         * @returns {Object} {left, top}
	         */

	    }, {
	        key: 'getCenter',
	        value: function getCenter() {
	            return this._canvas.getCenter();
	        }

	        /**
	         * Get cropped rect
	         * @returns {Object} rect
	         */

	    }, {
	        key: 'getCropzoneRect',
	        value: function getCropzoneRect() {
	            return this.getComponent(components.CROPPER).getCropzoneRect();
	        }

	        /**
	         * Get cropped image data
	         * @param {Object} cropRect cropzone rect
	         *  @param {Number} cropRect.left left position
	         *  @param {Number} cropRect.top top position
	         *  @param {Number} cropRect.width width
	         *  @param {Number} cropRect.height height
	         * @returns {?{imageName: string, url: string}} cropped Image data
	         */

	    }, {
	        key: 'getCroppedImageData',
	        value: function getCroppedImageData(cropRect) {
	            return this.getComponent(components.CROPPER).getCroppedImageData(cropRect);
	        }

	        /**
	         * Set brush option
	         * @param {Object} option brush option
	         *  @param {Number} option.width width
	         *  @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)'
	         */

	    }, {
	        key: 'setBrush',
	        value: function setBrush(option) {
	            var drawingMode = this._drawingMode;
	            var compName = components.FREE_DRAWING;

	            if (drawingMode === drawingModes.LINE) {
	                compName = drawingModes.LINE;
	            }

	            this.getComponent(compName).setBrush(option);
	        }

	        /**
	         * Set states of current drawing shape
	         * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
	         * @param {Object} [options] - Shape options
	         *      @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
	         *      @param {string} [options.stoke] - Shape outline color
	         *      @param {number} [options.strokeWidth] - Shape outline width
	         *      @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
	         *      @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
	         *      @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
	         *      @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
	         *      @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
	         */

	    }, {
	        key: 'setDrawingShape',
	        value: function setDrawingShape(type, options) {
	            this.getComponent(components.SHAPE).setStates(type, options);
	        }

	        /**
	         * Register icon paths
	         * @param {Object} pathInfos - Path infos
	         *  @param {string} pathInfos.key - key
	         *  @param {string} pathInfos.value - value
	         */

	    }, {
	        key: 'registerPaths',
	        value: function registerPaths(pathInfos) {
	            this.getComponent(components.ICON).registerPaths(pathInfos);
	        }

	        /**
	         * Change cursor style
	         * @param {string} cursorType - cursor type
	         */

	    }, {
	        key: 'changeCursor',
	        value: function changeCursor(cursorType) {
	            var canvas = this.getCanvas();
	            canvas.defaultCursor = cursorType;
	            canvas.renderAll();
	        }

	        /**
	         * Whether it has the filter or not
	         * @param {string} type - Filter type
	         * @returns {boolean} true if it has the filter
	         */

	    }, {
	        key: 'hasFilter',
	        value: function hasFilter(type) {
	            return this.getComponent(components.FILTER).hasFilter(type);
	        }

	        /**
	         * Set selection style of fabric object by init option
	         * @param {Object} styles - Selection styles
	         */

	    }, {
	        key: 'setSelectionStyle',
	        value: function setSelectionStyle(styles) {
	            extend(fObjectOptions.SELECTION_STYLE, styles);
	        }

	        /**
	         * Set object properties
	         * @param {number} id - object id
	         * @param {Object} props - props
	         *     @param {string} [props.fill] Color
	         *     @param {string} [props.fontFamily] Font type for text
	         *     @param {number} [props.fontSize] Size
	         *     @param {string} [props.fontStyle] Type of inclination (normal / italic)
	         *     @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold)
	         *     @param {string} [props.textAlign] Type of text align (left / center / right)
	         *     @param {string} [props.textDecoraiton] Type of line (underline / line-throgh / overline)
	         * @returns {Object} applied properties
	         */

	    }, {
	        key: 'setObjectProperties',
	        value: function setObjectProperties(id, props) {
	            var object = this.getObject(id);
	            var clone = extend({}, props);

	            object.set(clone);

	            object.setCoords();

	            this.getCanvas().renderAll();

	            return clone;
	        }

	        /**
	         * Get object properties corresponding key
	         * @param {number} id - object id
	         * @param {Array<string>|ObjectProps|string} keys - property's key
	         * @returns {Object} properties
	         */

	    }, {
	        key: 'getObjectProperties',
	        value: function getObjectProperties(id, keys) {
	            var object = this.getObject(id);
	            var props = {};

	            if (isString(keys)) {
	                props[keys] = object[keys];
	            } else if (isArray(keys)) {
	                forEachArray(keys, function (value) {
	                    props[value] = object[value];
	                });
	            } else {
	                forEachOwnProperties(keys, function (value, key) {
	                    props[key] = object[key];
	                });
	            }

	            return props;
	        }

	        /**
	         * Get object position by originX, originY
	         * @param {number} id - object id
	         * @param {string} originX - can be 'left', 'center', 'right'
	         * @param {string} originY - can be 'top', 'center', 'bottom'
	         * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null
	         */

	    }, {
	        key: 'getObjectPosition',
	        value: function getObjectPosition(id, originX, originY) {
	            var targetObj = this.getObject(id);
	            if (!targetObj) {
	                return null;
	            }

	            return targetObj.getPointByOrigin(originX, originY);
	        }

	        /**
	         * Set object position  by originX, originY
	         * @param {number} id - object id
	         * @param {Object} posInfo - position object
	         *  @param {number} posInfo.x - x position
	         *  @param {number} posInfo.y - y position
	         *  @param {string} posInfo.originX - can be 'left', 'center', 'right'
	         *  @param {string} posInfo.originY - can be 'top', 'center', 'bottom'
	         * @returns {boolean} true if target id is valid or false
	         */

	    }, {
	        key: 'setObjectPosition',
	        value: function setObjectPosition(id, posInfo) {
	            var targetObj = this.getObject(id);
	            var x = posInfo.x,
	                y = posInfo.y,
	                originX = posInfo.originX,
	                originY = posInfo.originY;

	            if (!targetObj) {
	                return false;
	            }

	            var targetOrigin = targetObj.getPointByOrigin(originX, originY);
	            var centerOrigin = targetObj.getPointByOrigin('center', 'center');
	            var diffX = centerOrigin.x - targetOrigin.x;
	            var diffY = centerOrigin.y - targetOrigin.y;

	            targetObj.set({
	                left: x + diffX,
	                top: y + diffY
	            });

	            targetObj.setCoords();

	            return true;
	        }

	        /**
	         * Get the canvas size
	         * @returns {Object} {{width: number, height: number}} image size
	         */

	    }, {
	        key: 'getCanvasSize',
	        value: function getCanvasSize() {
	            var image = this.getCanvasImage();

	            return {
	                width: image ? image.width : 0,
	                height: image ? image.height : 0
	            };
	        }

	        /**
	         * Get a DrawingMode instance
	         * @param {string} modeName - DrawingMode Class Name
	         * @returns {DrawingMode} DrawingMode instance
	         * @private
	         */

	    }, {
	        key: '_getDrawingModeInstance',
	        value: function _getDrawingModeInstance(modeName) {
	            return this._drawingModeMap[modeName];
	        }

	        /**
	         * Set canvas element to fabric.Canvas
	         * @param {jQuery|Element|string} element - Wrapper or canvas element or selector
	         * @private
	         */

	    }, {
	        key: '_setCanvasElement',
	        value: function _setCanvasElement(element) {
	            var selectedElement = void 0;
	            var canvasElement = void 0;

	            if (element.jquery) {
	                selectedElement = element[0];
	            } else if (element.nodeType) {
	                selectedElement = element;
	            } else {
	                selectedElement = document.querySelector(element);
	            }

	            if (selectedElement.nodeName.toUpperCase() !== 'CANVAS') {
	                canvasElement = document.createElement('canvas');
	                selectedElement.appendChild(canvasElement);
	            }

	            this._canvas = new _fabric2.default.Canvas(canvasElement, {
	                containerClass: 'tui-image-editor-canvas-container',
	                enableRetinaScaling: false
	            });
	        }

	        /**
	         * Creates DrawingMode instances
	         * @private
	         */

	    }, {
	        key: '_createDrawingModeInstances',
	        value: function _createDrawingModeInstances() {
	            this._register(this._drawingModeMap, new _cropper4.default());
	            this._register(this._drawingModeMap, new _freeDrawing4.default());
	            this._register(this._drawingModeMap, new _lineDrawing2.default());
	            this._register(this._drawingModeMap, new _shape4.default());
	            this._register(this._drawingModeMap, new _text4.default());
	        }

	        /**
	         * Create components
	         * @private
	         */

	    }, {
	        key: '_createComponents',
	        value: function _createComponents() {
	            this._register(this._componentMap, new _imageLoader2.default(this));
	            this._register(this._componentMap, new _cropper2.default(this));
	            this._register(this._componentMap, new _flip2.default(this));
	            this._register(this._componentMap, new _rotation2.default(this));
	            this._register(this._componentMap, new _freeDrawing2.default(this));
	            this._register(this._componentMap, new _line2.default(this));
	            this._register(this._componentMap, new _text2.default(this));
	            this._register(this._componentMap, new _icon2.default(this));
	            this._register(this._componentMap, new _filter2.default(this));
	            this._register(this._componentMap, new _shape2.default(this));
	        }

	        /**
	         * Register component
	         * @param {Object} map - map object
	         * @param {Object} module - module which has getName method
	         * @private
	         */

	    }, {
	        key: '_register',
	        value: function _register(map, module) {
	            map[module.getName()] = module;
	        }

	        /**
	         * Get the current drawing mode is same with given mode
	         * @param {string} mode drawing mode
	         * @returns {boolean} true if same or false
	         */

	    }, {
	        key: '_isSameDrawingMode',
	        value: function _isSameDrawingMode(mode) {
	            return this.getDrawingMode() === mode;
	        }

	        /**
	         * Calculate max dimension of canvas
	         * The css-max dimension is dynamically decided with maintaining image ratio
	         * The css-max dimension is lower than canvas dimension (attribute of canvas, not css)
	         * @param {number} width - Canvas width
	         * @param {number} height - Canvas height
	         * @returns {{width: number, height: number}} - Max width & Max height
	         * @private
	         */

	    }, {
	        key: '_calcMaxDimension',
	        value: function _calcMaxDimension(width, height) {
	            var wScaleFactor = this.cssMaxWidth / width;
	            var hScaleFactor = this.cssMaxHeight / height;
	            var cssMaxWidth = Math.min(width, this.cssMaxWidth);
	            var cssMaxHeight = Math.min(height, this.cssMaxHeight);

	            if (wScaleFactor < 1 && wScaleFactor < hScaleFactor) {
	                cssMaxWidth = width * wScaleFactor;
	                cssMaxHeight = height * wScaleFactor;
	            } else if (hScaleFactor < 1 && hScaleFactor < wScaleFactor) {
	                cssMaxWidth = width * hScaleFactor;
	                cssMaxHeight = height * hScaleFactor;
	            }

	            return {
	                width: Math.floor(cssMaxWidth),
	                height: Math.floor(cssMaxHeight)
	            };
	        }

	        /**
	         * Callback function after loading image
	         * @param {fabric.Image} obj - Fabric image object
	         * @private
	         */

	    }, {
	        key: '_callbackAfterLoadingImageObject',
	        value: function _callbackAfterLoadingImageObject(obj) {
	            var centerPos = this.getCanvasImage().getCenterPoint();

	            obj.set(_consts2.default.fObjectOptions.SELECTION_STYLE);
	            obj.set({
	                left: centerPos.x,
	                top: centerPos.y,
	                crossOrigin: 'Anonymous'
	            });

	            this.getCanvas().add(obj).setActiveObject(obj);
	        }

	        /**
	         * Attach canvas's events
	         */

	    }, {
	        key: '_attachCanvasEvents',
	        value: function _attachCanvasEvents() {
	            var canvas = this._canvas;
	            var handler = this._handler;
	            canvas.on({
	                'mouse:down': handler.onMouseDown,
	                'object:added': handler.onObjectAdded,
	                'object:removed': handler.onObjectRemoved,
	                'object:moving': handler.onObjectMoved,
	                'object:scaling': handler.onObjectScaled,
	                'object:selected': handler.onObjectSelected,
	                'path:created': handler.onPathCreated,
	                'selection:cleared': handler.onSelectionCleared,
	                'selection:created': handler.onSelectionCreated
	            });
	        }

	        /**
	         * "mouse:down" canvas event handler
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onMouseDown',
	        value: function _onMouseDown(fEvent) {
	            var originPointer = this._canvas.getPointer(fEvent.e);
	            this.fire(events.MOUSE_DOWN, fEvent.e, originPointer);
	        }

	        /**
	         * "object:added" canvas event handler
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onObjectAdded',
	        value: function _onObjectAdded(fEvent) {
	            var obj = fEvent.target;
	            if (obj.isType('cropzone')) {
	                return;
	            }

	            this._addFabricObject(obj);
	        }

	        /**
	         * "object:removed" canvas event handler
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onObjectRemoved',
	        value: function _onObjectRemoved(fEvent) {
	            var obj = fEvent.target;

	            this._removeFabricObject(stamp(obj));
	        }

	        /**
	         * "object:moving" canvas event handler
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onObjectMoved',
	        value: function _onObjectMoved(fEvent) {
	            var target = fEvent.target;

	            var params = this.createObjectProperties(target);

	            this.fire(events.OBJECT_MOVED, params);
	        }

	        /**
	         * "object:scaling" canvas event handler
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onObjectScaled',
	        value: function _onObjectScaled(fEvent) {
	            var target = fEvent.target;

	            var params = this.createObjectProperties(target);

	            this.fire(events.OBJECT_SCALED, params);
	        }

	        /**
	         * "object:selected" canvas event handler
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onObjectSelected',
	        value: function _onObjectSelected(fEvent) {
	            var target = fEvent.target;

	            var params = this.createObjectProperties(target);

	            this.fire(events.OBJECT_ACTIVATED, params);
	        }

	        /**
	         * "path:created" canvas event handler
	         * @param {{path: fabric.Path}} obj - Path object
	         * @private
	         */

	    }, {
	        key: '_onPathCreated',
	        value: function _onPathCreated(obj) {
	            obj.path.set(_consts2.default.fObjectOptions.SELECTION_STYLE);

	            var params = this.createObjectProperties(obj.path);

	            this.fire(events.ADD_OBJECT, params);
	        }

	        /**
	         * "selction:cleared" canvas event handler
	         * @private
	         */

	    }, {
	        key: '_onSelectionCleared',
	        value: function _onSelectionCleared() {
	            this.fire(events.SELECTION_CLEARED);
	        }

	        /**
	         * "selction:created" canvas event handler
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onSelectionCreated',
	        value: function _onSelectionCreated(fEvent) {
	            this.fire(events.SELECTION_CREATED, fEvent.target);
	        }

	        /**
	         * Canvas discard selection all
	         */

	    }, {
	        key: 'discardSelection',
	        value: function discardSelection() {
	            this._canvas.discardActiveGroup();
	            this._canvas.discardActiveObject();
	            this._canvas.renderAll();
	        }

	        /**
	         * Canvas Selectable status change
	         * @param {boolean} selectable - expect status
	         */

	    }, {
	        key: 'changeSelectableAll',
	        value: function changeSelectableAll(selectable) {
	            this._canvas.forEachObject(function (obj) {
	                obj.selectable = selectable;
	                obj.hoverCursor = selectable ? 'move' : 'crosshair';
	            });
	        }

	        /**
	         * Return object's properties
	         * @param {fabric.Object} obj - fabric object
	         * @returns {Object} properties object
	         */

	    }, {
	        key: 'createObjectProperties',
	        value: function createObjectProperties(obj) {
	            var predefinedKeys = ['left', 'top', 'width', 'height', 'fill', 'stroke', 'strokeWidth', 'opacity'];
	            var props = {
	                id: stamp(obj),
	                type: obj.type
	            };

	            extend(props, _util2.default.getProperties(obj, predefinedKeys));

	            if (['i-text', 'text'].indexOf(obj.type) > -1) {
	                extend(props, this._createTextProperties(obj, props));
	            }

	            return props;
	        }

	        /**
	         * Get text object's properties
	         * @param {fabric.Object} obj - fabric text object
	         * @param {Object} props - properties
	         * @returns {Object} properties object
	         */

	    }, {
	        key: '_createTextProperties',
	        value: function _createTextProperties(obj) {
	            var predefinedKeys = ['text', 'fontFamily', 'fontSize', 'fontStyle', 'textAlign', 'textDecoration'];
	            var props = {};
	            extend(props, _util2.default.getProperties(obj, predefinedKeys));

	            return props;
	        }

	        /**
	         * Add object array by id
	         * @param {fabric.Object} obj - fabric object
	         * @returns {number} object id
	         */

	    }, {
	        key: '_addFabricObject',
	        value: function _addFabricObject(obj) {
	            var id = stamp(obj);
	            this._objects[id] = obj;

	            return id;
	        }

	        /**
	         * Remove an object in array yb id
	         * @param {number} id - object id
	         */

	    }, {
	        key: '_removeFabricObject',
	        value: function _removeFabricObject(id) {
	            delete this._objects[id];
	        }
	    }]);

	    return Graphics;
	}();

	CustomEvents.mixin(Graphics);
	module.exports = Graphics;

/***/ }),
/* 105 */
/***/ (function(module, exports) {

	module.exports = __WEBPACK_EXTERNAL_MODULE_105__;

/***/ }),
/* 106 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _component = __webpack_require__(107);

	var _component2 = _interopRequireDefault(_component);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview Image loader
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var componentNames = _consts2.default.componentNames,
	    rejectMessages = _consts2.default.rejectMessages;

	var imageOption = {
	    padding: 0,
	    crossOrigin: 'Anonymous'
	};

	/**
	 * ImageLoader components
	 * @extends {Component}
	 * @class ImageLoader
	 * @param {Graphics} graphics - Graphics instance
	 * @ignore
	 */

	var ImageLoader = function (_Component) {
	    _inherits(ImageLoader, _Component);

	    function ImageLoader(graphics) {
	        _classCallCheck(this, ImageLoader);

	        return _possibleConstructorReturn(this, (ImageLoader.__proto__ || Object.getPrototypeOf(ImageLoader)).call(this, componentNames.IMAGE_LOADER, graphics));
	    }

	    /**
	     * Load image from url
	     * @param {?string} imageName - File name
	     * @param {?(fabric.Image|string)} img - fabric.Image instance or URL of an image
	     * @returns {jQuery.Deferred} deferred
	     */


	    _createClass(ImageLoader, [{
	        key: 'load',
	        value: function load(imageName, img) {
	            var _this2 = this;

	            var promise = void 0;

	            if (!imageName && !img) {
	                // Back to the initial state, not error.
	                var canvas = this.getCanvas();

	                canvas.backgroundImage = null;
	                canvas.renderAll();

	                promise = new _promise2.default(function (resolve) {
	                    _this2.setCanvasImage('', null);
	                    resolve();
	                });
	            } else {
	                promise = this._setBackgroundImage(img).then(function (oImage) {
	                    _this2.setCanvasImage(imageName, oImage);
	                    _this2.adjustCanvasDimension();

	                    return oImage;
	                });
	            }

	            return promise;
	        }

	        /**
	         * Set background image
	         * @param {?(fabric.Image|String)} img fabric.Image instance or URL of an image to set background to
	         * @returns {$.Deferred} deferred
	         * @private
	         */

	    }, {
	        key: '_setBackgroundImage',
	        value: function _setBackgroundImage(img) {
	            var _this3 = this;

	            if (!img) {
	                return _promise2.default.reject(rejectMessages.loadImage);
	            }

	            return new _promise2.default(function (resolve, reject) {
	                var canvas = _this3.getCanvas();

	                canvas.setBackgroundImage(img, function () {
	                    var oImage = canvas.backgroundImage;

	                    if (oImage.getElement()) {
	                        resolve(oImage);
	                    } else {
	                        reject(rejectMessages.loadingImageFailed);
	                    }
	                }, imageOption);
	            });
	        }
	    }]);

	    return ImageLoader;
	}(_component2.default);

	module.exports = ImageLoader;

/***/ }),
/* 107 */
/***/ (function(module, exports) {

	"use strict";

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Component interface
	 */

	/**
	 * Component interface
	 * @class
	 * @param {string} name - component name
	 * @param {Graphics} graphics - Graphics instance
	 * @ignore
	 */
	var Component = function () {
	  function Component(name, graphics) {
	    _classCallCheck(this, Component);

	    /**
	     * Component name
	     * @type {string}
	     */
	    this.name = name;

	    /**
	     * Graphics instance
	     * @type {Graphics}
	     */
	    this.graphics = graphics;
	  }

	  /**
	   * Fire Graphics event
	   * @param {Array} args - arguments
	   * @returns {Object} return value
	   */


	  _createClass(Component, [{
	    key: "fire",
	    value: function fire() {
	      var context = this.graphics;

	      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
	        args[_key] = arguments[_key];
	      }

	      return this.graphics.fire.apply(context, args);
	    }

	    /**
	     * Save image(background) of canvas
	     * @param {string} name - Name of image
	     * @param {fabric.Image} oImage - Fabric image instance
	     */

	  }, {
	    key: "setCanvasImage",
	    value: function setCanvasImage(name, oImage) {
	      this.graphics.setCanvasImage(name, oImage);
	    }

	    /**
	     * Returns canvas element of fabric.Canvas[[lower-canvas]]
	     * @returns {HTMLCanvasElement}
	     */

	  }, {
	    key: "getCanvasElement",
	    value: function getCanvasElement() {
	      return this.graphics.getCanvasElement();
	    }

	    /**
	     * Get fabric.Canvas instance
	     * @returns {fabric.Canvas}
	     */

	  }, {
	    key: "getCanvas",
	    value: function getCanvas() {
	      return this.graphics.getCanvas();
	    }

	    /**
	     * Get canvasImage (fabric.Image instance)
	     * @returns {fabric.Image}
	     */

	  }, {
	    key: "getCanvasImage",
	    value: function getCanvasImage() {
	      return this.graphics.getCanvasImage();
	    }

	    /**
	     * Get image name
	     * @returns {string}
	     */

	  }, {
	    key: "getImageName",
	    value: function getImageName() {
	      return this.graphics.getImageName();
	    }

	    /**
	     * Get image editor
	     * @returns {ImageEditor}
	     */

	  }, {
	    key: "getEditor",
	    value: function getEditor() {
	      return this.graphics.getEditor();
	    }

	    /**
	     * Return component name
	     * @returns {string}
	     */

	  }, {
	    key: "getName",
	    value: function getName() {
	      return this.name;
	    }

	    /**
	     * Set image properties
	     * @param {Object} setting - Image properties
	     * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas
	     */

	  }, {
	    key: "setImageProperties",
	    value: function setImageProperties(setting, withRendering) {
	      this.graphics.setImageProperties(setting, withRendering);
	    }

	    /**
	     * Set canvas dimension - css only
	     * @param {Object} dimension - Canvas css dimension
	     */

	  }, {
	    key: "setCanvasCssDimension",
	    value: function setCanvasCssDimension(dimension) {
	      this.graphics.setCanvasCssDimension(dimension);
	    }

	    /**
	     * Set canvas dimension - css only
	     * @param {Object} dimension - Canvas backstore dimension
	     */

	  }, {
	    key: "setCanvasBackstoreDimension",
	    value: function setCanvasBackstoreDimension(dimension) {
	      this.graphics.setCanvasBackstoreDimension(dimension);
	    }

	    /**
	     * Adjust canvas dimension with scaling image
	     */

	  }, {
	    key: "adjustCanvasDimension",
	    value: function adjustCanvasDimension() {
	      this.graphics.adjustCanvasDimension();
	    }
	  }]);

	  return Component;
	}();

	module.exports = Component;

/***/ }),
/* 108 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	var _component = __webpack_require__(107);

	var _component2 = _interopRequireDefault(_component);

	var _cropzone = __webpack_require__(109);

	var _cropzone2 = _interopRequireDefault(_cropzone);

	var _consts = __webpack_require__(73);

	var _util = __webpack_require__(72);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview Image crop module (start cropping, end cropping)
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var MOUSE_MOVE_THRESHOLD = 10;

	/**
	 * Cropper components
	 * @param {Graphics} graphics - Graphics instance
	 * @extends {Component}
	 * @class Cropper
	 * @ignore
	 */

	var Cropper = function (_Component) {
	    _inherits(Cropper, _Component);

	    function Cropper(graphics) {
	        _classCallCheck(this, Cropper);

	        /**
	         * Cropzone
	         * @type {Cropzone}
	         * @private
	         */
	        var _this = _possibleConstructorReturn(this, (Cropper.__proto__ || Object.getPrototypeOf(Cropper)).call(this, _consts.componentNames.CROPPER, graphics));

	        _this._cropzone = null;

	        /**
	         * StartX of Cropzone
	         * @type {number}
	         * @private
	         */
	        _this._startX = null;

	        /**
	         * StartY of Cropzone
	         * @type {number}
	         * @private
	         */
	        _this._startY = null;

	        /**
	         * State whether shortcut key is pressed or not
	         * @type {boolean}
	         * @private
	         */
	        _this._withShiftKey = false;

	        /**
	         * Listeners
	         * @type {object.<string, function>}
	         * @private
	         */
	        _this._listeners = {
	            keydown: _this._onKeyDown.bind(_this),
	            keyup: _this._onKeyUp.bind(_this),
	            mousedown: _this._onFabricMouseDown.bind(_this),
	            mousemove: _this._onFabricMouseMove.bind(_this),
	            mouseup: _this._onFabricMouseUp.bind(_this)
	        };
	        return _this;
	    }

	    /**
	     * Start cropping
	     */


	    _createClass(Cropper, [{
	        key: 'start',
	        value: function start() {
	            if (this._cropzone) {
	                return;
	            }
	            var canvas = this.getCanvas();

	            canvas.forEachObject(function (obj) {
	                // {@link http://fabricjs.com/docs/fabric.Object.html#evented}
	                obj.evented = false;
	            });

	            this._cropzone = new _cropzone2.default({
	                left: -10,
	                top: -10,
	                width: 1,
	                height: 1,
	                strokeWidth: 0, // {@link https://github.com/kangax/fabric.js/issues/2860}
	                cornerSize: 10,
	                cornerColor: 'black',
	                fill: 'transparent',
	                hasRotatingPoint: false,
	                hasBorders: false,
	                lockScalingFlip: true,
	                lockRotation: true
	            }, this.graphics.cropSelectionStyle);

	            canvas.deactivateAll();
	            canvas.add(this._cropzone);
	            canvas.on('mouse:down', this._listeners.mousedown);
	            canvas.selection = false;
	            canvas.defaultCursor = 'crosshair';

	            _fabric2.default.util.addListener(document, 'keydown', this._listeners.keydown);
	            _fabric2.default.util.addListener(document, 'keyup', this._listeners.keyup);
	        }

	        /**
	         * End cropping
	         */

	    }, {
	        key: 'end',
	        value: function end() {
	            var canvas = this.getCanvas();
	            var cropzone = this._cropzone;

	            if (!cropzone) {
	                return;
	            }
	            cropzone.remove();
	            canvas.selection = true;
	            canvas.defaultCursor = 'default';
	            canvas.off('mouse:down', this._listeners.mousedown);
	            canvas.forEachObject(function (obj) {
	                obj.evented = true;
	            });

	            this._cropzone = null;

	            _fabric2.default.util.removeListener(document, 'keydown', this._listeners.keydown);
	            _fabric2.default.util.removeListener(document, 'keyup', this._listeners.keyup);
	        }

	        /**
	         * onMousedown handler in fabric canvas
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseDown',
	        value: function _onFabricMouseDown(fEvent) {
	            var canvas = this.getCanvas();

	            if (fEvent.target) {
	                return;
	            }

	            canvas.selection = false;
	            var coord = canvas.getPointer(fEvent.e);

	            this._startX = coord.x;
	            this._startY = coord.y;

	            canvas.on({
	                'mouse:move': this._listeners.mousemove,
	                'mouse:up': this._listeners.mouseup
	            });
	        }

	        /**
	         * onMousemove handler in fabric canvas
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseMove',
	        value: function _onFabricMouseMove(fEvent) {
	            var canvas = this.getCanvas();
	            var pointer = canvas.getPointer(fEvent.e);
	            var x = pointer.x,
	                y = pointer.y;

	            var cropzone = this._cropzone;

	            if (Math.abs(x - this._startX) + Math.abs(y - this._startY) > MOUSE_MOVE_THRESHOLD) {
	                cropzone.remove();
	                cropzone.set(this._calcRectDimensionFromPoint(x, y));

	                canvas.add(cropzone);
	            }
	        }

	        /**
	         * Get rect dimension setting from Canvas-Mouse-Position(x, y)
	         * @param {number} x - Canvas-Mouse-Position x
	         * @param {number} y - Canvas-Mouse-Position Y
	         * @returns {{left: number, top: number, width: number, height: number}}
	         * @private
	         */

	    }, {
	        key: '_calcRectDimensionFromPoint',
	        value: function _calcRectDimensionFromPoint(x, y) {
	            var canvas = this.getCanvas();
	            var canvasWidth = canvas.getWidth();
	            var canvasHeight = canvas.getHeight();
	            var startX = this._startX;
	            var startY = this._startY;
	            var left = (0, _util.clamp)(x, 0, startX);
	            var top = (0, _util.clamp)(y, 0, startY);
	            var width = (0, _util.clamp)(x, startX, canvasWidth) - left; // (startX <= x(mouse) <= canvasWidth) - left
	            var height = (0, _util.clamp)(y, startY, canvasHeight) - top; // (startY <= y(mouse) <= canvasHeight) - top

	            if (this._withShiftKey) {
	                // make fixed ratio cropzone
	                if (width > height) {
	                    height = width;
	                } else if (height > width) {
	                    width = height;
	                }

	                if (startX >= x) {
	                    left = startX - width;
	                }

	                if (startY >= y) {
	                    top = startY - height;
	                }
	            }

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

	        /**
	         * onMouseup handler in fabric canvas
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseUp',
	        value: function _onFabricMouseUp() {
	            var cropzone = this._cropzone;
	            var listeners = this._listeners;
	            var canvas = this.getCanvas();

	            canvas.setActiveObject(cropzone);
	            canvas.off({
	                'mouse:move': listeners.mousemove,
	                'mouse:up': listeners.mouseup
	            });
	        }

	        /**
	         * Get cropped image data
	         * @param {Object} cropRect cropzone rect
	         *  @param {Number} cropRect.left left position
	         *  @param {Number} cropRect.top top position
	         *  @param {Number} cropRect.width width
	         *  @param {Number} cropRect.height height
	         * @returns {?{imageName: string, url: string}} cropped Image data
	         */

	    }, {
	        key: 'getCroppedImageData',
	        value: function getCroppedImageData(cropRect) {
	            var canvas = this.getCanvas();
	            var containsCropzone = canvas.contains(this._cropzone);
	            if (!cropRect) {
	                return null;
	            }

	            if (containsCropzone) {
	                this._cropzone.remove();
	            }

	            var imageData = {
	                imageName: this.getImageName(),
	                url: canvas.toDataURL(cropRect)
	            };

	            if (containsCropzone) {
	                canvas.add(this._cropzone);
	            }

	            return imageData;
	        }

	        /**
	         * Get cropped rect
	         * @returns {Object} rect
	         */

	    }, {
	        key: 'getCropzoneRect',
	        value: function getCropzoneRect() {
	            var cropzone = this._cropzone;

	            if (!cropzone.isValid()) {
	                return null;
	            }

	            return {
	                left: cropzone.getLeft(),
	                top: cropzone.getTop(),
	                width: cropzone.getWidth(),
	                height: cropzone.getHeight()
	            };
	        }

	        /**
	         * Keydown event handler
	         * @param {KeyboardEvent} e - Event object
	         * @private
	         */

	    }, {
	        key: '_onKeyDown',
	        value: function _onKeyDown(e) {
	            if (e.keyCode === _consts.keyCodes.SHIFT) {
	                this._withShiftKey = true;
	            }
	        }

	        /**
	         * Keyup event handler
	         * @param {KeyboardEvent} e - Event object
	         * @private
	         */

	    }, {
	        key: '_onKeyUp',
	        value: function _onKeyUp(e) {
	            if (e.keyCode === _consts.keyCodes.SHIFT) {
	                this._withShiftKey = false;
	            }
	        }
	    }]);

	    return Cropper;
	}(_component2.default);

	module.exports = Cropper;

/***/ }),
/* 109 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	var _util = __webpack_require__(72);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var CORNER_TYPE_TOP_LEFT = 'tl'; /**
	                                  * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                  * @fileoverview Cropzone extending fabric.Rect
	                                  */

	var CORNER_TYPE_TOP_RIGHT = 'tr';
	var CORNER_TYPE_MIDDLE_TOP = 'mt';
	var CORNER_TYPE_MIDDLE_LEFT = 'ml';
	var CORNER_TYPE_MIDDLE_RIGHT = 'mr';
	var CORNER_TYPE_MIDDLE_BOTTOM = 'mb';
	var CORNER_TYPE_BOTTOM_LEFT = 'bl';
	var CORNER_TYPE_BOTTOM_RIGHT = 'br';

	/**
	 * Cropzone object
	 * Issue: IE7, 8(with excanvas)
	 *  - Cropzone is a black zone without transparency.
	 * @class Cropzone
	 * @extends {fabric.Rect}
	 * @ignore
	 */
	var Cropzone = _fabric2.default.util.createClass(_fabric2.default.Rect, /** @lends Cropzone.prototype */{
	    /**
	     * Constructor
	     * @param {Object} options Options object
	     * @override
	     */
	    initialize: function initialize(options, extendsOptions) {
	        options = _tuiCodeSnippet2.default.extend(options, extendsOptions);
	        options.type = 'cropzone';

	        this.callSuper('initialize', options);

	        this.options = options;

	        this.on({
	            'moving': this._onMoving,
	            'scaling': this._onScaling
	        });
	    },


	    /**
	     * Render Crop-zone
	     * @param {CanvasRenderingContext2D} ctx - Context
	     * @private
	     * @override
	     */
	    _render: function _render(ctx) {
	        var cropzoneDashLineWidth = 7;
	        var cropzoneDashLineOffset = 7;
	        this.callSuper('_render', ctx);

	        // Calc original scale
	        var originalFlipX = this.flipX ? -1 : 1;
	        var originalFlipY = this.flipY ? -1 : 1;
	        var originalScaleX = originalFlipX / this.scaleX;
	        var originalScaleY = originalFlipY / this.scaleY;

	        // Set original scale
	        ctx.scale(originalScaleX, originalScaleY);

	        // Render outer rect
	        this._fillOuterRect(ctx, 'rgba(0, 0, 0, 0.55)');

	        if (this.options.lineWidth) {
	            this._fillInnerRect(ctx);
	        } else {
	            // Black dash line
	            this._strokeBorder(ctx, 'rgb(0, 0, 0)', {
	                lineDashWidth: cropzoneDashLineWidth
	            });

	            // White dash line
	            this._strokeBorder(ctx, 'rgb(255, 255, 255)', {
	                lineDashWidth: cropzoneDashLineWidth,
	                lineDashOffset: cropzoneDashLineOffset
	            });
	        }

	        // Reset scale
	        ctx.scale(1 / originalScaleX, 1 / originalScaleY);
	    },


	    /**
	     * Cropzone-coordinates with outer rectangle
	     *
	     *     x0     x1         x2      x3
	     *  y0 +--------------------------+
	     *     |///////|//////////|///////|    // <--- "Outer-rectangle"
	     *     |///////|//////////|///////|
	     *  y1 +-------+----------+-------+
	     *     |///////| Cropzone |///////|    Cropzone is the "Inner-rectangle"
	     *     |///////|  (0, 0)  |///////|    Center point (0, 0)
	     *  y2 +-------+----------+-------+
	     *     |///////|//////////|///////|
	     *     |///////|//////////|///////|
	     *  y3 +--------------------------+
	     *
	     * @typedef {{x: Array<number>, y: Array<number>}} cropzoneCoordinates
	     * @ignore
	     */

	    /**
	     * Fill outer rectangle
	     * @param {CanvasRenderingContext2D} ctx - Context
	     * @param {string|CanvasGradient|CanvasPattern} fillStyle - Fill-style
	     * @private
	     */
	    _fillOuterRect: function _fillOuterRect(ctx, fillStyle) {
	        var _getCoordinates = this._getCoordinates(ctx),
	            x = _getCoordinates.x,
	            y = _getCoordinates.y;

	        ctx.save();
	        ctx.fillStyle = fillStyle;
	        ctx.beginPath();

	        // Outer rectangle
	        // Numbers are +/-1 so that overlay edges don't get blurry.
	        ctx.moveTo(x[0] - 1, y[0] - 1);
	        ctx.lineTo(x[3] + 1, y[0] - 1);
	        ctx.lineTo(x[3] + 1, y[3] + 1);
	        ctx.lineTo(x[0] - 1, y[3] + 1);
	        ctx.lineTo(x[0] - 1, y[0] - 1);
	        ctx.closePath();

	        // Inner rectangle
	        ctx.moveTo(x[1], y[1]);
	        ctx.lineTo(x[1], y[2]);
	        ctx.lineTo(x[2], y[2]);
	        ctx.lineTo(x[2], y[1]);
	        ctx.lineTo(x[1], y[1]);
	        ctx.closePath();

	        ctx.fill();
	        ctx.restore();
	    },


	    /**
	     * Draw Inner grid line
	     * @param {CanvasRenderingContext2D} ctx - Context
	     * @private
	     */
	    _fillInnerRect: function _fillInnerRect(ctx) {
	        var _getCoordinates2 = this._getCoordinates(ctx),
	            outerX = _getCoordinates2.x,
	            outerY = _getCoordinates2.y;

	        var x = this._caculateInnerPosition(outerX, (outerX[2] - outerX[1]) / 3);
	        var y = this._caculateInnerPosition(outerY, (outerY[2] - outerY[1]) / 3);

	        ctx.save();
	        ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
	        ctx.lineWidth = this.options.lineWidth;
	        ctx.beginPath();

	        ctx.moveTo(x[0], y[1]);
	        ctx.lineTo(x[3], y[1]);

	        ctx.moveTo(x[0], y[2]);
	        ctx.lineTo(x[3], y[2]);

	        ctx.moveTo(x[1], y[0]);
	        ctx.lineTo(x[1], y[3]);

	        ctx.moveTo(x[2], y[0]);
	        ctx.lineTo(x[2], y[3]);
	        ctx.stroke();
	        ctx.closePath();

	        ctx.restore();
	    },


	    /**
	     * Calculate Inner Position
	     * @param {Array} outer - outer position
	     * @param {number} size - interval for calcaulate
	     * @returns {Array} - inner position
	     * @private
	     */
	    _caculateInnerPosition: function _caculateInnerPosition(outer, size) {
	        var position = [];
	        position[0] = outer[1];
	        position[1] = outer[1] + size;
	        position[2] = outer[1] + size * 2;
	        position[3] = outer[2];

	        return position;
	    },


	    /**
	     * Get coordinates
	     * @param {CanvasRenderingContext2D} ctx - Context
	     * @returns {cropzoneCoordinates} - {@link cropzoneCoordinates}
	     * @private
	     */
	    _getCoordinates: function _getCoordinates(ctx) {
	        var width = this.getWidth(),
	            height = this.getHeight(),
	            halfWidth = width / 2,
	            halfHeight = height / 2,
	            left = this.getLeft(),
	            top = this.getTop(),
	            canvasEl = ctx.canvas; // canvas element, not fabric object

	        return {
	            x: _tuiCodeSnippet2.default.map([-(halfWidth + left), // x0
	            -halfWidth, // x1
	            halfWidth, // x2
	            halfWidth + (canvasEl.width - left - width) // x3
	            ], Math.ceil),
	            y: _tuiCodeSnippet2.default.map([-(halfHeight + top), // y0
	            -halfHeight, // y1
	            halfHeight, // y2
	            halfHeight + (canvasEl.height - top - height) // y3
	            ], Math.ceil)
	        };
	    },


	    /**
	     * Stroke border
	     * @param {CanvasRenderingContext2D} ctx - Context
	     * @param {string|CanvasGradient|CanvasPattern} strokeStyle - Stroke-style
	     * @param {number} lineDashWidth - Dash width
	     * @param {number} [lineDashOffset] - Dash offset
	     * @private
	     */
	    _strokeBorder: function _strokeBorder(ctx, strokeStyle, _ref) {
	        var lineDashWidth = _ref.lineDashWidth,
	            lineDashOffset = _ref.lineDashOffset,
	            lineWidth = _ref.lineWidth;

	        var halfWidth = this.getWidth() / 2,
	            halfHeight = this.getHeight() / 2;

	        ctx.save();
	        ctx.strokeStyle = strokeStyle;

	        if (ctx.setLineDash) {
	            ctx.setLineDash([lineDashWidth, lineDashWidth]);
	        }
	        if (lineDashOffset) {
	            ctx.lineDashOffset = lineDashOffset;
	        }
	        if (lineWidth) {
	            ctx.lineWidth = lineWidth;
	        }

	        ctx.beginPath();
	        ctx.moveTo(-halfWidth, -halfHeight);
	        ctx.lineTo(halfWidth, -halfHeight);
	        ctx.lineTo(halfWidth, halfHeight);
	        ctx.lineTo(-halfWidth, halfHeight);
	        ctx.lineTo(-halfWidth, -halfHeight);
	        ctx.stroke();

	        ctx.restore();
	    },


	    /**
	     * onMoving event listener
	     * @private
	     */
	    _onMoving: function _onMoving() {
	        var left = this.getLeft(),
	            top = this.getTop(),
	            width = this.getWidth(),
	            height = this.getHeight(),
	            maxLeft = this.canvas.getWidth() - width,
	            maxTop = this.canvas.getHeight() - height;

	        this.setLeft((0, _util.clamp)(left, 0, maxLeft));
	        this.setTop((0, _util.clamp)(top, 0, maxTop));
	    },


	    /**
	     * onScaling event listener
	     * @param {{e: MouseEvent}} fEvent - Fabric event
	     * @private
	     */
	    _onScaling: function _onScaling(fEvent) {
	        var pointer = this.canvas.getPointer(fEvent.e),
	            settings = this._calcScalingSizeFromPointer(pointer);

	        // On scaling cropzone,
	        // change real width and height and fix scaleFactor to 1
	        this.scale(1).set(settings);
	    },


	    /**
	     * Calc scaled size from mouse pointer with selected corner
	     * @param {{x: number, y: number}} pointer - Mouse position
	     * @returns {Object} Having left or(and) top or(and) width or(and) height.
	     * @private
	     */
	    _calcScalingSizeFromPointer: function _calcScalingSizeFromPointer(pointer) {
	        var pointerX = pointer.x,
	            pointerY = pointer.y,
	            tlScalingSize = this._calcTopLeftScalingSizeFromPointer(pointerX, pointerY),
	            brScalingSize = this._calcBottomRightScalingSizeFromPointer(pointerX, pointerY);

	        /*
	         * @todo: ì¼ë°˜ ê°ì²´ì—ì„œ shift ì¡°í•©í‚¤ë¥¼ ëˆ„ë¥´ë©´ free size scalingì´ ë¨ --> í™•ì¸í•´ë³¼ê²ƒ
	         *      canvas.class.js // _scaleObject: function(...){...}
	         */
	        return this._makeScalingSettings(tlScalingSize, brScalingSize);
	    },


	    /**
	     * Calc scaling size(position + dimension) from left-top corner
	     * @param {number} x - Mouse position X
	     * @param {number} y - Mouse position Y
	     * @returns {{top: number, left: number, width: number, height: number}}
	     * @private
	     */
	    _calcTopLeftScalingSizeFromPointer: function _calcTopLeftScalingSizeFromPointer(x, y) {
	        var bottom = this.getHeight() + this.top,
	            right = this.getWidth() + this.left,
	            top = (0, _util.clamp)(y, 0, bottom - 1),
	            // 0 <= top <= (bottom - 1)
	        left = (0, _util.clamp)(x, 0, right - 1); // 0 <= left <= (right - 1)

	        // When scaling "Top-Left corner": It fixes right and bottom coordinates
	        return {
	            top: top,
	            left: left,
	            width: right - left,
	            height: bottom - top
	        };
	    },


	    /**
	     * Calc scaling size from right-bottom corner
	     * @param {number} x - Mouse position X
	     * @param {number} y - Mouse position Y
	     * @returns {{width: number, height: number}}
	     * @private
	     */
	    _calcBottomRightScalingSizeFromPointer: function _calcBottomRightScalingSizeFromPointer(x, y) {
	        var _canvas = this.canvas,
	            maxX = _canvas.width,
	            maxY = _canvas.height;
	        var left = this.left,
	            top = this.top;

	        // When scaling "Bottom-Right corner": It fixes left and top coordinates

	        return {
	            width: (0, _util.clamp)(x, left + 1, maxX) - left, // (width = x - left), (left + 1 <= x <= maxX)
	            height: (0, _util.clamp)(y, top + 1, maxY) - top // (height = y - top), (top + 1 <= y <= maxY)
	        };
	    },


	    /* eslint-disable complexity */
	    /**
	     * Make scaling settings
	     * @param {{width: number, height: number, left: number, top: number}} tl - Top-Left setting
	     * @param {{width: number, height: number}} br - Bottom-Right setting
	     * @returns {{width: ?number, height: ?number, left: ?number, top: ?number}} Position setting
	     * @private
	     */
	    _makeScalingSettings: function _makeScalingSettings(tl, br) {
	        var tlWidth = tl.width;
	        var tlHeight = tl.height;
	        var brHeight = br.height;
	        var brWidth = br.width;
	        var tlLeft = tl.left;
	        var tlTop = tl.top;
	        var settings = void 0;

	        switch (this.__corner) {
	            case CORNER_TYPE_TOP_LEFT:
	                settings = tl;
	                break;
	            case CORNER_TYPE_TOP_RIGHT:
	                settings = {
	                    width: brWidth,
	                    height: tlHeight,
	                    top: tlTop
	                };
	                break;
	            case CORNER_TYPE_BOTTOM_LEFT:
	                settings = {
	                    width: tlWidth,
	                    height: brHeight,
	                    left: tlLeft
	                };
	                break;
	            case CORNER_TYPE_BOTTOM_RIGHT:
	                settings = br;
	                break;
	            case CORNER_TYPE_MIDDLE_LEFT:
	                settings = {
	                    width: tlWidth,
	                    left: tlLeft
	                };
	                break;
	            case CORNER_TYPE_MIDDLE_TOP:
	                settings = {
	                    height: tlHeight,
	                    top: tlTop
	                };
	                break;
	            case CORNER_TYPE_MIDDLE_RIGHT:
	                settings = {
	                    width: brWidth
	                };
	                break;
	            case CORNER_TYPE_MIDDLE_BOTTOM:
	                settings = {
	                    height: brHeight
	                };
	                break;
	            default:
	                break;
	        }

	        return settings;
	    },
	    /* eslint-enable complexity */

	    /**
	     * Return the whether this cropzone is valid
	     * @returns {boolean}
	     */
	    isValid: function isValid() {
	        return this.left >= 0 && this.top >= 0 && this.width > 0 && this.height > 0;
	    }
	});

	module.exports = Cropzone;

/***/ }),
/* 110 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _component = __webpack_require__(107);

	var _component2 = _interopRequireDefault(_component);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview Image flip module
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var componentNames = _consts2.default.componentNames,
	    rejectMessages = _consts2.default.rejectMessages;

	/**
	 * Flip
	 * @class Flip
	 * @param {Graphics} graphics - Graphics instance
	 * @extends {Component}
	 * @ignore
	 */

	var Flip = function (_Component) {
	    _inherits(Flip, _Component);

	    function Flip(graphics) {
	        _classCallCheck(this, Flip);

	        return _possibleConstructorReturn(this, (Flip.__proto__ || Object.getPrototypeOf(Flip)).call(this, componentNames.FLIP, graphics));
	    }

	    /**
	     * Get current flip settings
	     * @returns {{flipX: Boolean, flipY: Boolean}}
	     */


	    _createClass(Flip, [{
	        key: 'getCurrentSetting',
	        value: function getCurrentSetting() {
	            var canvasImage = this.getCanvasImage();

	            return {
	                flipX: canvasImage.flipX,
	                flipY: canvasImage.flipY
	            };
	        }

	        /**
	         * Set flipX, flipY
	         * @param {{flipX: Boolean, flipY: Boolean}} newSetting - Flip setting
	         * @returns {jQuery.Deferred}
	         */

	    }, {
	        key: 'set',
	        value: function set(newSetting) {
	            var setting = this.getCurrentSetting();
	            var isChangingFlipX = setting.flipX !== newSetting.flipX;
	            var isChangingFlipY = setting.flipY !== newSetting.flipY;

	            if (!isChangingFlipX && !isChangingFlipY) {
	                return _promise2.default.reject(rejectMessages.flip);
	            }

	            _tuiCodeSnippet2.default.extend(setting, newSetting);
	            this.setImageProperties(setting, true);
	            this._invertAngle(isChangingFlipX, isChangingFlipY);
	            this._flipObjects(isChangingFlipX, isChangingFlipY);

	            return _promise2.default.resolve({
	                flipX: setting.flipX,
	                flipY: setting.flipY,
	                angle: this.getCanvasImage().angle
	            });
	        }

	        /**
	         * Invert image angle for flip
	         * @param {boolean} isChangingFlipX - Change flipX
	         * @param {boolean} isChangingFlipY - Change flipY
	         */

	    }, {
	        key: '_invertAngle',
	        value: function _invertAngle(isChangingFlipX, isChangingFlipY) {
	            var canvasImage = this.getCanvasImage();
	            var angle = canvasImage.angle;


	            if (isChangingFlipX) {
	                angle *= -1;
	            }
	            if (isChangingFlipY) {
	                angle *= -1;
	            }
	            canvasImage.setAngle(parseFloat(angle)).setCoords(); // parseFloat for -0 to 0
	        }

	        /**
	         * Flip objects
	         * @param {boolean} isChangingFlipX - Change flipX
	         * @param {boolean} isChangingFlipY - Change flipY
	         * @private
	         */

	    }, {
	        key: '_flipObjects',
	        value: function _flipObjects(isChangingFlipX, isChangingFlipY) {
	            var canvas = this.getCanvas();

	            if (isChangingFlipX) {
	                canvas.forEachObject(function (obj) {
	                    obj.set({
	                        angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0
	                        flipX: !obj.flipX,
	                        left: canvas.width - obj.left
	                    }).setCoords();
	                });
	            }
	            if (isChangingFlipY) {
	                canvas.forEachObject(function (obj) {
	                    obj.set({
	                        angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0
	                        flipY: !obj.flipY,
	                        top: canvas.height - obj.top
	                    }).setCoords();
	                });
	            }
	            canvas.renderAll();
	        }

	        /**
	         * Reset flip settings
	         * @returns {jQuery.Deferred}
	         */

	    }, {
	        key: 'reset',
	        value: function reset() {
	            return this.set({
	                flipX: false,
	                flipY: false
	            });
	        }

	        /**
	         * Flip x
	         * @returns {jQuery.Deferred}
	         */

	    }, {
	        key: 'flipX',
	        value: function flipX() {
	            var current = this.getCurrentSetting();

	            return this.set({
	                flipX: !current.flipX,
	                flipY: current.flipY
	            });
	        }

	        /**
	         * Flip y
	         * @returns {jQuery.Deferred}
	         */

	    }, {
	        key: 'flipY',
	        value: function flipY() {
	            var current = this.getCurrentSetting();

	            return this.set({
	                flipX: current.flipX,
	                flipY: !current.flipY
	            });
	        }
	    }]);

	    return Flip;
	}(_component2.default);

	module.exports = Flip;

/***/ }),
/* 111 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _component = __webpack_require__(107);

	var _component2 = _interopRequireDefault(_component);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview Image rotation module
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var componentNames = _consts2.default.componentNames;

	/**
	 * Image Rotation component
	 * @class Rotation
	 * @extends {Component}
	 * @param {Graphics} graphics - Graphics instance
	 * @ignore
	 */

	var Rotation = function (_Component) {
	    _inherits(Rotation, _Component);

	    function Rotation(graphics) {
	        _classCallCheck(this, Rotation);

	        return _possibleConstructorReturn(this, (Rotation.__proto__ || Object.getPrototypeOf(Rotation)).call(this, componentNames.ROTATION, graphics));
	    }

	    /**
	     * Get current angle
	     * @returns {Number}
	     */


	    _createClass(Rotation, [{
	        key: 'getCurrentAngle',
	        value: function getCurrentAngle() {
	            return this.getCanvasImage().angle;
	        }

	        /**
	         * Set angle of the image
	         *
	         *  Do not call "this.setImageProperties" for setting angle directly.
	         *  Before setting angle, The originX,Y of image should be set to center.
	         *      See "http://fabricjs.com/docs/fabric.Object.html#setAngle"
	         *
	         * @param {number} angle - Angle value
	         * @returns {jQuery.Deferred}
	         */

	    }, {
	        key: 'setAngle',
	        value: function setAngle(angle) {
	            var oldAngle = this.getCurrentAngle() % 360; // The angle is lower than 2*PI(===360 degrees)

	            angle %= 360;

	            var canvasImage = this.getCanvasImage();
	            var oldImageCenter = canvasImage.getCenterPoint();
	            canvasImage.setAngle(angle).setCoords();
	            this.adjustCanvasDimension();
	            var newImageCenter = canvasImage.getCenterPoint();
	            this._rotateForEachObject(oldImageCenter, newImageCenter, angle - oldAngle);

	            return _promise2.default.resolve(angle);
	        }

	        /**
	         * Rotate for each object
	         * @param {fabric.Point} oldImageCenter - Image center point before rotation
	         * @param {fabric.Point} newImageCenter - Image center point after rotation
	         * @param {number} angleDiff - Image angle difference after rotation
	         * @private
	         */

	    }, {
	        key: '_rotateForEachObject',
	        value: function _rotateForEachObject(oldImageCenter, newImageCenter, angleDiff) {
	            var canvas = this.getCanvas();
	            var centerDiff = {
	                x: oldImageCenter.x - newImageCenter.x,
	                y: oldImageCenter.y - newImageCenter.y
	            };

	            canvas.forEachObject(function (obj) {
	                var objCenter = obj.getCenterPoint();
	                var radian = _fabric2.default.util.degreesToRadians(angleDiff);
	                var newObjCenter = _fabric2.default.util.rotatePoint(objCenter, oldImageCenter, radian);

	                obj.set({
	                    left: newObjCenter.x - centerDiff.x,
	                    top: newObjCenter.y - centerDiff.y,
	                    angle: (obj.angle + angleDiff) % 360
	                });
	                obj.setCoords();
	            });
	            canvas.renderAll();
	        }

	        /**
	         * Rotate the image
	         * @param {number} additionalAngle - Additional angle
	         * @returns {jQuery.Deferred}
	         */

	    }, {
	        key: 'rotate',
	        value: function rotate(additionalAngle) {
	            var current = this.getCurrentAngle();

	            return this.setAngle(current + additionalAngle);
	        }
	    }]);

	    return Rotation;
	}(_component2.default);

	module.exports = Rotation;

/***/ }),
/* 112 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	var _component = __webpack_require__(107);

	var _component2 = _interopRequireDefault(_component);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview Free drawing module, Set brush
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	/**
	 * FreeDrawing
	 * @class FreeDrawing
	 * @param {Graphics} graphics - Graphics instance
	 * @extends {Component}
	 * @ignore
	 */
	var FreeDrawing = function (_Component) {
	  _inherits(FreeDrawing, _Component);

	  function FreeDrawing(graphics) {
	    _classCallCheck(this, FreeDrawing);

	    /**
	     * Brush width
	     * @type {number}
	     */
	    var _this = _possibleConstructorReturn(this, (FreeDrawing.__proto__ || Object.getPrototypeOf(FreeDrawing)).call(this, _consts2.default.componentNames.FREE_DRAWING, graphics));

	    _this.width = 12;

	    /**
	     * fabric.Color instance for brush color
	     * @type {fabric.Color}
	     */
	    _this.oColor = new _fabric2.default.Color('rgba(0, 0, 0, 0.5)');
	    return _this;
	  }

	  /**
	   * Start free drawing mode
	   * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
	   */


	  _createClass(FreeDrawing, [{
	    key: 'start',
	    value: function start(setting) {
	      var canvas = this.getCanvas();

	      canvas.isDrawingMode = true;
	      this.setBrush(setting);
	    }

	    /**
	     * Set brush
	     * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
	     */

	  }, {
	    key: 'setBrush',
	    value: function setBrush(setting) {
	      var brush = this.getCanvas().freeDrawingBrush;

	      setting = setting || {};
	      this.width = setting.width || this.width;
	      if (setting.color) {
	        this.oColor = new _fabric2.default.Color(setting.color);
	      }
	      brush.width = this.width;
	      brush.color = this.oColor.toRgba();
	    }

	    /**
	     * End free drawing mode
	     */

	  }, {
	    key: 'end',
	    value: function end() {
	      var canvas = this.getCanvas();

	      canvas.isDrawingMode = false;
	    }
	  }]);

	  return FreeDrawing;
	}(_component2.default);

	module.exports = FreeDrawing;

/***/ }),
/* 113 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	var _component = __webpack_require__(107);

	var _component2 = _interopRequireDefault(_component);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview Free drawing module, Set brush
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var eventNames = _consts2.default.eventNames;

	/**
	 * Line
	 * @class Line
	 * @param {Graphics} graphics - Graphics instance
	 * @extends {Component}
	 * @ignore
	 */

	var Line = function (_Component) {
	    _inherits(Line, _Component);

	    function Line(graphics) {
	        _classCallCheck(this, Line);

	        /**
	         * Brush width
	         * @type {number}
	         * @private
	         */
	        var _this = _possibleConstructorReturn(this, (Line.__proto__ || Object.getPrototypeOf(Line)).call(this, _consts2.default.componentNames.LINE, graphics));

	        _this._width = 12;

	        /**
	         * fabric.Color instance for brush color
	         * @type {fabric.Color}
	         * @private
	         */
	        _this._oColor = new _fabric2.default.Color('rgba(0, 0, 0, 0.5)');

	        /**
	         * Listeners
	         * @type {object.<string, function>}
	         * @private
	         */
	        _this._listeners = {
	            mousedown: _this._onFabricMouseDown.bind(_this),
	            mousemove: _this._onFabricMouseMove.bind(_this),
	            mouseup: _this._onFabricMouseUp.bind(_this)
	        };
	        return _this;
	    }

	    /**
	     * Start drawing line mode
	     * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
	     */


	    _createClass(Line, [{
	        key: 'start',
	        value: function start(setting) {
	            var canvas = this.getCanvas();

	            canvas.defaultCursor = 'crosshair';
	            canvas.selection = false;

	            this.setBrush(setting);

	            canvas.forEachObject(function (obj) {
	                obj.set({
	                    evented: false
	                });
	            });

	            canvas.on({
	                'mouse:down': this._listeners.mousedown
	            });
	        }

	        /**
	         * Set brush
	         * @param {{width: ?number, color: ?string}} [setting] - Brush width & color
	         */

	    }, {
	        key: 'setBrush',
	        value: function setBrush(setting) {
	            var brush = this.getCanvas().freeDrawingBrush;

	            setting = setting || {};
	            this._width = setting.width || this._width;

	            if (setting.color) {
	                this._oColor = new _fabric2.default.Color(setting.color);
	            }
	            brush.width = this._width;
	            brush.color = this._oColor.toRgba();
	        }

	        /**
	         * End drawing line mode
	         */

	    }, {
	        key: 'end',
	        value: function end() {
	            var canvas = this.getCanvas();

	            canvas.defaultCursor = 'default';
	            canvas.selection = true;

	            canvas.forEachObject(function (obj) {
	                obj.set({
	                    evented: true
	                });
	            });

	            canvas.off('mouse:down', this._listeners.mousedown);
	        }

	        /**
	         * Mousedown event handler in fabric canvas
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseDown',
	        value: function _onFabricMouseDown(fEvent) {
	            var canvas = this.getCanvas();
	            var pointer = canvas.getPointer(fEvent.e);
	            var points = [pointer.x, pointer.y, pointer.x, pointer.y];

	            this._line = new _fabric2.default.Line(points, {
	                stroke: this._oColor.toRgba(),
	                strokeWidth: this._width,
	                evented: false
	            });

	            this._line.set(_consts2.default.fObjectOptions.SELECTION_STYLE);

	            canvas.add(this._line);

	            canvas.on({
	                'mouse:move': this._listeners.mousemove,
	                'mouse:up': this._listeners.mouseup
	            });
	        }

	        /**
	         * Mousemove event handler in fabric canvas
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseMove',
	        value: function _onFabricMouseMove(fEvent) {
	            var canvas = this.getCanvas();
	            var pointer = canvas.getPointer(fEvent.e);

	            this._line.set({
	                x2: pointer.x,
	                y2: pointer.y
	            });

	            this._line.setCoords();

	            canvas.renderAll();
	        }

	        /**
	         * Mouseup event handler in fabric canvas
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseUp',
	        value: function _onFabricMouseUp() {
	            var canvas = this.getCanvas();
	            var params = this.graphics.createObjectProperties(this._line);

	            this.fire(eventNames.ADD_OBJECT, params);

	            this._line = null;

	            canvas.off({
	                'mouse:move': this._listeners.mousemove,
	                'mouse:up': this._listeners.mouseup
	            });
	        }
	    }]);

	    return Line;
	}(_component2.default);

	module.exports = Line;

/***/ }),
/* 114 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _component = __webpack_require__(107);

	var _component2 = _interopRequireDefault(_component);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	var _util = __webpack_require__(72);

	var _util2 = _interopRequireDefault(_util);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview Text module
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var events = _consts2.default.eventNames;

	var defaultStyles = {
	    fill: '#000000',
	    left: 0,
	    top: 0
	};
	var resetStyles = {
	    fill: '#000000',
	    fontStyle: 'normal',
	    fontWeight: 'normal',
	    textAlign: 'left',
	    textDecoraiton: ''
	};
	var browser = _tuiCodeSnippet2.default.browser;


	var TEXTAREA_CLASSNAME = 'tui-image-eidtor-textarea';
	var TEXTAREA_STYLES = _util2.default.makeStyleText({
	    position: 'absolute',
	    padding: 0,
	    display: 'none',
	    border: '1px dotted red',
	    overflow: 'hidden',
	    resize: 'none',
	    outline: 'none',
	    'border-radius': 0,
	    'background-color': 'transparent',
	    '-webkit-appearance': 'none',
	    'z-index': 9999,
	    'white-space': 'pre'
	});
	var EXTRA_PIXEL_LINEHEIGHT = 0.1;
	var DBCLICK_TIME = 500;

	/**
	 * Text
	 * @class Text
	 * @param {Graphics} graphics - Graphics instance
	 * @extends {Component}
	 * @ignore
	 */

	var Text = function (_Component) {
	    _inherits(Text, _Component);

	    function Text(graphics) {
	        _classCallCheck(this, Text);

	        /**
	         * Default text style
	         * @type {Object}
	         */
	        var _this = _possibleConstructorReturn(this, (Text.__proto__ || Object.getPrototypeOf(Text)).call(this, _consts2.default.componentNames.TEXT, graphics));

	        _this._defaultStyles = defaultStyles;

	        /**
	         * Selected state
	         * @type {boolean}
	         */
	        _this._isSelected = false;

	        /**
	         * Selected text object
	         * @type {Object}
	         */
	        _this._selectedObj = {};

	        /**
	         * Editing text object
	         * @type {Object}
	         */
	        _this._editingObj = {};

	        /**
	         * Listeners for fabric event
	         * @type {Object}
	         */
	        _this._listeners = {
	            mousedown: _this._onFabricMouseDown.bind(_this),
	            select: _this._onFabricSelect.bind(_this),
	            selectClear: _this._onFabricSelectClear.bind(_this),
	            scaling: _this._onFabricScaling.bind(_this)
	        };

	        /**
	         * Textarea element for editing
	         * @type {HTMLElement}
	         */
	        _this._textarea = null;

	        /**
	         * Ratio of current canvas
	         * @type {number}
	         */
	        _this._ratio = 1;

	        /**
	         * Last click time
	         * @type {Date}
	         */
	        _this._lastClickTime = new Date().getTime();

	        /**
	         * Text object infos before editing
	         * @type {Object}
	         */
	        _this._editingObjInfos = {};

	        /**
	         * Previous state of editing
	         * @type {boolean}
	         */
	        _this.isPrevEditing = false;

	        /**
	         * use itext
	         * @type {boolean}
	         */
	        _this.useItext = graphics.useItext;
	        return _this;
	    }

	    /**
	     * Start input text mode
	     */


	    _createClass(Text, [{
	        key: 'start',
	        value: function start() {
	            var canvas = this.getCanvas();

	            canvas.selection = false;
	            canvas.defaultCursor = 'text';
	            canvas.on({
	                'mouse:down': this._listeners.mousedown,
	                'object:selected': this._listeners.select,
	                'before:selection:cleared': this._listeners.selectClear,
	                'object:scaling': this._listeners.scaling,
	                'text:editing': this._listeners.modify
	            });

	            if (this.useItext) {
	                canvas.forEachObject(function (obj) {
	                    if (obj.type === 'i-text') {
	                        obj.set({
	                            left: obj.left - obj.width / 2,
	                            top: obj.top - obj.height / 2,
	                            originX: 'left',
	                            originY: 'top'
	                        });
	                    }
	                });
	            } else {
	                this._createTextarea();
	            }

	            this.setCanvasRatio();
	        }

	        /**
	         * End input text mode
	         */

	    }, {
	        key: 'end',
	        value: function end() {
	            var canvas = this.getCanvas();

	            canvas.selection = true;
	            canvas.defaultCursor = 'default';

	            if (this.useItext) {
	                canvas.forEachObject(function (obj) {
	                    if (obj.type === 'i-text') {
	                        if (obj.text === '') {
	                            obj.remove();
	                        } else {
	                            obj.set({
	                                left: obj.left + obj.width / 2,
	                                top: obj.top + obj.height / 2,
	                                originX: 'center',
	                                originY: 'center'
	                            });
	                        }
	                    }
	                });
	            } else {
	                canvas.deactivateAllWithDispatch();
	                this._removeTextarea();
	            }

	            canvas.off({
	                'mouse:down': this._listeners.mousedown,
	                'object:selected': this._listeners.select,
	                'before:selection:cleared': this._listeners.selectClear,
	                'object:scaling': this._listeners.scaling,
	                'text:editing': this._listeners.modify
	            });
	        }

	        /**
	         * Add new text on canvas image
	         * @param {string} text - Initial input text
	         * @param {Object} options - Options for generating text
	         *     @param {Object} [options.styles] Initial styles
	         *         @param {string} [options.styles.fill] Color
	         *         @param {string} [options.styles.fontFamily] Font type for text
	         *         @param {number} [options.styles.fontSize] Size
	         *         @param {string} [options.styles.fontStyle] Type of inclination (normal / italic)
	         *         @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold)
	         *         @param {string} [options.styles.textAlign] Type of text align (left / center / right)
	         *         @param {string} [options.styles.textDecoraiton] Type of line (underline / line-throgh / overline)
	         *     @param {{x: number, y: number}} [options.position] - Initial position
	         * @returns {Promise}
	         */

	    }, {
	        key: 'add',
	        value: function add(text, options) {
	            var _this2 = this;

	            return new _promise2.default(function (resolve) {
	                var canvas = _this2.getCanvas();
	                var newText = null;
	                var selectionStyle = _consts2.default.fObjectOptions.SELECTION_STYLE;
	                var styles = _this2._defaultStyles;

	                _this2._setInitPos(options.position);

	                if (options.styles) {
	                    styles = _tuiCodeSnippet2.default.extend(styles, options.styles);
	                }

	                if (_this2.useItext) {
	                    newText = new _fabric2.default.IText(text, styles);
	                    selectionStyle = _tuiCodeSnippet2.default.extend({}, selectionStyle, {
	                        originX: 'left',
	                        originY: 'top'
	                    });
	                } else {
	                    newText = new _fabric2.default.Text(text, styles);
	                }

	                newText.set(selectionStyle);
	                newText.on({
	                    mouseup: _this2._onFabricMouseUp.bind(_this2)
	                });

	                canvas.add(newText);

	                if (!canvas.getActiveObject()) {
	                    canvas.setActiveObject(newText);
	                }

	                _this2.isPrevEditing = true;
	                resolve(_this2.graphics.createObjectProperties(newText));
	            });
	        }

	        /**
	         * Change text of activate object on canvas image
	         * @param {Object} activeObj - Current selected text object
	         * @param {string} text - Changed text
	         * @returns {Promise}
	         */

	    }, {
	        key: 'change',
	        value: function change(activeObj, text) {
	            var _this3 = this;

	            return new _promise2.default(function (resolve) {
	                activeObj.set('text', text);

	                _this3.getCanvas().renderAll();
	                resolve();
	            });
	        }

	        /**
	         * Set style
	         * @param {Object} activeObj - Current selected text object
	         * @param {Object} styleObj - Initial styles
	         *     @param {string} [styleObj.fill] Color
	         *     @param {string} [styleObj.fontFamily] Font type for text
	         *     @param {number} [styleObj.fontSize] Size
	         *     @param {string} [styleObj.fontStyle] Type of inclination (normal / italic)
	         *     @param {string} [styleObj.fontWeight] Type of thicker or thinner looking (normal / bold)
	         *     @param {string} [styleObj.textAlign] Type of text align (left / center / right)
	         *     @param {string} [styleObj.textDecoraiton] Type of line (underline / line-throgh / overline)
	         * @returns {Promise}
	         */

	    }, {
	        key: 'setStyle',
	        value: function setStyle(activeObj, styleObj) {
	            var _this4 = this;

	            return new _promise2.default(function (resolve) {
	                _tuiCodeSnippet2.default.forEach(styleObj, function (val, key) {
	                    if (activeObj[key] === val) {
	                        styleObj[key] = resetStyles[key] || '';
	                    }
	                }, _this4);

	                activeObj.set(styleObj);

	                _this4.getCanvas().renderAll();
	                resolve();
	            });
	        }

	        /**
	         * Get the text
	         * @param {Object} activeObj - Current selected text object
	         * @returns {String} text
	         */

	    }, {
	        key: 'getText',
	        value: function getText(activeObj) {
	            return activeObj.getText();
	        }

	        /**
	         * Set infos of the current selected object
	         * @param {fabric.Text} obj - Current selected text object
	         * @param {boolean} state - State of selecting
	         */

	    }, {
	        key: 'setSelectedInfo',
	        value: function setSelectedInfo(obj, state) {
	            this._selectedObj = obj;
	            this._isSelected = state;
	        }

	        /**
	         * Whether object is selected or not
	         * @returns {boolean} State of selecting
	         */

	    }, {
	        key: 'isSelected',
	        value: function isSelected() {
	            return this._isSelected;
	        }

	        /**
	         * Get current selected text object
	         * @returns {fabric.Text} Current selected text object
	         */

	    }, {
	        key: 'getSelectedObj',
	        value: function getSelectedObj() {
	            return this._selectedObj;
	        }

	        /**
	         * Set ratio value of canvas
	         */

	    }, {
	        key: 'setCanvasRatio',
	        value: function setCanvasRatio() {
	            var canvasElement = this.getCanvasElement();
	            var cssWidth = parseInt(canvasElement.style.maxWidth, 10);
	            var originWidth = canvasElement.width;
	            var ratio = originWidth / cssWidth;

	            this._ratio = ratio;
	        }

	        /**
	         * Get ratio value of canvas
	         * @returns {number} Ratio value
	         */

	    }, {
	        key: 'getCanvasRatio',
	        value: function getCanvasRatio() {
	            return this._ratio;
	        }

	        /**
	         * Set initial position on canvas image
	         * @param {{x: number, y: number}} [position] - Selected position
	         * @private
	         */

	    }, {
	        key: '_setInitPos',
	        value: function _setInitPos(position) {
	            position = position || this.getCanvasImage().getCenterPoint();

	            this._defaultStyles.left = position.x;
	            this._defaultStyles.top = position.y;
	        }

	        /**
	         * Create textarea element on canvas container
	         * @private
	         */

	    }, {
	        key: '_createTextarea',
	        value: function _createTextarea() {
	            var container = this.getCanvasElement().parentNode;
	            var textarea = document.createElement('textarea');

	            textarea.className = TEXTAREA_CLASSNAME;
	            textarea.setAttribute('style', TEXTAREA_STYLES);
	            textarea.setAttribute('wrap', 'off');

	            container.appendChild(textarea);

	            this._textarea = textarea;

	            this._listeners = _tuiCodeSnippet2.default.extend(this._listeners, {
	                input: this._onInput.bind(this),
	                keydown: this._onKeyDown.bind(this),
	                blur: this._onBlur.bind(this),
	                scroll: this._onScroll.bind(this)
	            });

	            if (browser.msie && browser.version === 9) {
	                _fabric2.default.util.addListener(textarea, 'keydown', this._listeners.keydown);
	            } else {
	                _fabric2.default.util.addListener(textarea, 'input', this._listeners.input);
	            }
	            _fabric2.default.util.addListener(textarea, 'blur', this._listeners.blur);
	            _fabric2.default.util.addListener(textarea, 'scroll', this._listeners.scroll);
	        }

	        /**
	         * Remove textarea element on canvas container
	         * @private
	         */

	    }, {
	        key: '_removeTextarea',
	        value: function _removeTextarea() {
	            var container = this.getCanvasElement().parentNode;
	            var textarea = container.querySelector('textarea');

	            container.removeChild(textarea);

	            this._textarea = null;

	            if (browser.msie && browser.version < 10) {
	                _fabric2.default.util.removeListener(textarea, 'keydown', this._listeners.keydown);
	            } else {
	                _fabric2.default.util.removeListener(textarea, 'input', this._listeners.input);
	            }
	            _fabric2.default.util.removeListener(textarea, 'blur', this._listeners.blur);
	            _fabric2.default.util.removeListener(textarea, 'scroll', this._listeners.scroll);
	        }

	        /**
	         * Input event handler
	         * @private
	         */

	    }, {
	        key: '_onInput',
	        value: function _onInput() {
	            var ratio = this.getCanvasRatio();
	            var obj = this._editingObj;
	            var textareaStyle = this._textarea.style;

	            textareaStyle.width = Math.ceil(obj.getWidth() / ratio) + 'px';
	            textareaStyle.height = Math.ceil(obj.getHeight() / ratio) + 'px';
	        }

	        /**
	         * Keydown event handler
	         * @private
	         */

	    }, {
	        key: '_onKeyDown',
	        value: function _onKeyDown() {
	            var _this5 = this;

	            var ratio = this.getCanvasRatio();
	            var obj = this._editingObj;
	            var textareaStyle = this._textarea.style;

	            setTimeout(function () {
	                obj.setText(_this5._textarea.value);

	                textareaStyle.width = Math.ceil(obj.getWidth() / ratio) + 'px';
	                textareaStyle.height = Math.ceil(obj.getHeight() / ratio) + 'px';
	            }, 0);
	        }

	        /**
	         * Blur event handler
	         * @private
	         */

	    }, {
	        key: '_onBlur',
	        value: function _onBlur() {
	            var ratio = this.getCanvasRatio();
	            var editingObj = this._editingObj;
	            var editingObjInfos = this._editingObjInfos;
	            var textContent = this._textarea.value;
	            var transWidth = editingObj.getWidth() / ratio - editingObjInfos.width / ratio;
	            var transHeight = editingObj.getHeight() / ratio - editingObjInfos.height / ratio;

	            if (ratio === 1) {
	                transWidth /= 2;
	                transHeight /= 2;
	            }

	            this._textarea.style.display = 'none';

	            editingObj.set({
	                left: editingObjInfos.left + transWidth,
	                top: editingObjInfos.top + transHeight
	            });

	            if (textContent.length) {
	                this.getCanvas().add(editingObj);

	                var params = {
	                    id: _tuiCodeSnippet2.default.stamp(editingObj),
	                    type: editingObj.type,
	                    text: textContent
	                };

	                this.fire(events.TEXT_CHANGED, params);
	            }
	        }

	        /**
	         * Scroll event handler
	         * @private
	         */

	    }, {
	        key: '_onScroll',
	        value: function _onScroll() {
	            this._textarea.scrollLeft = 0;
	            this._textarea.scrollTop = 0;
	        }

	        /**
	         * Fabric scaling event handler
	         * @param {fabric.Event} fEvent - Current scaling event on selected object
	         * @private
	         */

	    }, {
	        key: '_onFabricScaling',
	        value: function _onFabricScaling(fEvent) {
	            var obj = fEvent.target;
	            var scalingSize = obj.getFontSize() * obj.getScaleY();

	            obj.setFontSize(scalingSize);
	            obj.setScaleX(1);
	            obj.setScaleY(1);
	        }

	        /**
	         * onSelectClear handler in fabric canvas
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onFabricSelectClear',
	        value: function _onFabricSelectClear(fEvent) {
	            var obj = this.getSelectedObj();

	            this.isPrevEditing = true;

	            this.setSelectedInfo(fEvent.target, false);

	            if (obj) {
	                // obj is empty object at initial time, will be set fabric object
	                if (obj.text === '') {
	                    obj.remove();
	                }
	            }
	        }

	        /**
	         * onSelect handler in fabric canvas
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
	         * @private
	         */

	    }, {
	        key: '_onFabricSelect',
	        value: function _onFabricSelect(fEvent) {
	            this.isPrevEditing = true;

	            this.setSelectedInfo(fEvent.target, true);
	        }

	        /**
	         * Fabric 'mousedown' event handler
	         * @param {fabric.Event} fEvent - Current mousedown event on selected object
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseDown',
	        value: function _onFabricMouseDown(fEvent) {
	            var obj = fEvent.target;

	            if (obj && !obj.isType('text')) {
	                return;
	            }

	            if (this.isPrevEditing) {
	                this.isPrevEditing = false;

	                return;
	            }

	            this._fireAddText(fEvent);
	        }

	        /**
	         * Fire 'addText' event if object is not selected.
	         * @param {fabric.Event} fEvent - Current mousedown event on selected object
	         * @private
	         */

	    }, {
	        key: '_fireAddText',
	        value: function _fireAddText(fEvent) {
	            var obj = fEvent.target;
	            var e = fEvent.e || {};
	            var originPointer = this.getCanvas().getPointer(e);

	            if (!obj) {
	                this.fire(events.ADD_TEXT, {
	                    originPosition: {
	                        x: originPointer.x,
	                        y: originPointer.y
	                    },
	                    clientPosition: {
	                        x: e.clientX || 0,
	                        y: e.clientY || 0
	                    }
	                });
	            }
	        }

	        /**
	         * Fabric mouseup event handler
	         * @param {fabric.Event} fEvent - Current mousedown event on selected object
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseUp',
	        value: function _onFabricMouseUp(fEvent) {
	            var newClickTime = new Date().getTime();

	            if (this._isDoubleClick(newClickTime)) {
	                if (!this.useItext) {
	                    this._changeToEditingMode(fEvent.target);
	                }
	                this.fire(events.TEXT_EDITING); // fire editing text event
	            }

	            this._lastClickTime = newClickTime;
	        }

	        /**
	         * Get state of firing double click event
	         * @param {Date} newClickTime - Current clicked time
	         * @returns {boolean} Whether double clicked or not
	         * @private
	         */

	    }, {
	        key: '_isDoubleClick',
	        value: function _isDoubleClick(newClickTime) {
	            return newClickTime - this._lastClickTime < DBCLICK_TIME;
	        }

	        /**
	         * Change state of text object for editing
	         * @param {fabric.Text} obj - Text object fired event
	         * @private
	         */

	    }, {
	        key: '_changeToEditingMode',
	        value: function _changeToEditingMode(obj) {
	            var ratio = this.getCanvasRatio();
	            var textareaStyle = this._textarea.style;

	            this.isPrevEditing = true;

	            obj.remove();

	            this._editingObj = obj;
	            this._textarea.value = obj.getText();

	            this._editingObjInfos = {
	                left: this._editingObj.getLeft(),
	                top: this._editingObj.getTop(),
	                width: this._editingObj.getWidth(),
	                height: this._editingObj.getHeight()
	            };

	            textareaStyle.display = 'block';
	            textareaStyle.left = obj.oCoords.tl.x / ratio + 'px';
	            textareaStyle.top = obj.oCoords.tl.y / ratio + 'px';
	            textareaStyle.width = Math.ceil(obj.getWidth() / ratio) + 'px';
	            textareaStyle.height = Math.ceil(obj.getHeight() / ratio) + 'px';
	            textareaStyle.transform = 'rotate(' + obj.getAngle() + 'deg)';
	            textareaStyle.color = obj.getFill();

	            textareaStyle['font-size'] = obj.getFontSize() / ratio + 'px';
	            textareaStyle['font-family'] = obj.getFontFamily();
	            textareaStyle['font-style'] = obj.getFontStyle();
	            textareaStyle['font-weight'] = obj.getFontWeight();
	            textareaStyle['text-align'] = obj.getTextAlign();
	            textareaStyle['line-height'] = obj.getLineHeight() + EXTRA_PIXEL_LINEHEIGHT;
	            textareaStyle['transform-origin'] = 'left top';

	            this._textarea.focus();
	        }
	    }]);

	    return Text;
	}(_component2.default);

	module.exports = Text;

/***/ }),
/* 115 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _component = __webpack_require__(107);

	var _component2 = _interopRequireDefault(_component);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview Add icon module
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var events = _consts2.default.eventNames;
	var rejectMessages = _consts2.default.rejectMessages;


	var pathMap = {
	    arrow: 'M 0 90 H 105 V 120 L 160 60 L 105 0 V 30 H 0 Z',
	    cancel: 'M 0 30 L 30 60 L 0 90 L 30 120 L 60 90 L 90 120 L 120 90 ' + 'L 90 60 L 120 30 L 90 0 L 60 30 L 30 0 Z'
	};

	/**
	 * Icon
	 * @class Icon
	 * @param {Graphics} graphics - Graphics instance
	 * @extends {Component}
	 * @ignore
	 */

	var Icon = function (_Component) {
	    _inherits(Icon, _Component);

	    function Icon(graphics) {
	        _classCallCheck(this, Icon);

	        /**
	         * Default icon color
	         * @type {string}
	         */
	        var _this = _possibleConstructorReturn(this, (Icon.__proto__ || Object.getPrototypeOf(Icon)).call(this, _consts2.default.componentNames.ICON, graphics));

	        _this._oColor = '#000000';

	        /**
	         * Path value of each icon type
	         * @type {Object}
	         */
	        _this._pathMap = pathMap;

	        /**
	         * Option to add icon to drag.
	         * @type {boolean}
	         */
	        _this.useDragAddIcon = graphics.useDragAddIcon;
	        return _this;
	    }

	    /**
	     * Add icon
	     * @param {string} type - Icon type
	     * @param {Object} options - Icon options
	     *      @param {string} [options.fill] - Icon foreground color
	     *      @param {string} [options.left] - Icon x position
	     *      @param {string} [options.top] - Icon y position
	     * @returns {Promise}
	     */


	    _createClass(Icon, [{
	        key: 'add',
	        value: function add(type, options) {
	            var _this2 = this;

	            return new _promise2.default(function (resolve, reject) {
	                var canvas = _this2.getCanvas();
	                var path = _this2._pathMap[type];
	                var selectionStyle = _consts2.default.fObjectOptions.SELECTION_STYLE;

	                if (!path) {
	                    reject(rejectMessages.invalidParameters);
	                }

	                var icon = _this2._createIcon(path);

	                icon.set(_tuiCodeSnippet2.default.extend({
	                    type: 'icon',
	                    fill: _this2._oColor
	                }, selectionStyle, options, _this2.graphics.controlStyle));

	                if (_this2.useDragAddIcon) {
	                    canvas.add(icon).setActiveObject(icon);
	                    canvas.on({
	                        'mouse:move': function mouseMove(fEvent) {
	                            canvas.selection = false;

	                            _this2.fire(events.ICON_CREATE_RESIZE, {
	                                moveOriginPointer: canvas.getPointer(fEvent.e)
	                            });
	                        },
	                        'mouse:up': function mouseUp(fEvent) {
	                            _this2.fire(events.ICON_CREATE_END, {
	                                moveOriginPointer: canvas.getPointer(fEvent.e)
	                            });

	                            canvas.defaultCursor = 'default';
	                            canvas.off('mouse:up');
	                            canvas.off('mouse:move');
	                            canvas.selection = true;
	                        }
	                    });
	                } else {
	                    canvas.add(icon).setActiveObject(icon);
	                }

	                resolve(_this2.graphics.createObjectProperties(icon));
	            });
	        }

	        /**
	         * Register icon paths
	         * @param {{key: string, value: string}} pathInfos - Path infos
	         */

	    }, {
	        key: 'registerPaths',
	        value: function registerPaths(pathInfos) {
	            var _this3 = this;

	            _tuiCodeSnippet2.default.forEach(pathInfos, function (path, type) {
	                _this3._pathMap[type] = path;
	            }, this);
	        }

	        /**
	         * Set icon object color
	         * @param {string} color - Color to set
	         * @param {fabric.Path}[obj] - Current activated path object
	         */

	    }, {
	        key: 'setColor',
	        value: function setColor(color, obj) {
	            this._oColor = color;

	            if (obj && obj.get('type') === 'icon') {
	                obj.setFill(this._oColor);
	                this.getCanvas().renderAll();
	            }
	        }

	        /**
	         * Get icon color
	         * @param {fabric.Path}[obj] - Current activated path object
	         * @returns {string} color
	         */

	    }, {
	        key: 'getColor',
	        value: function getColor(obj) {
	            return obj.fill;
	        }

	        /**
	         * Create icon object
	         * @param {string} path - Path value to create icon
	         * @returns {fabric.Path} Path object
	         */

	    }, {
	        key: '_createIcon',
	        value: function _createIcon(path) {
	            return new _fabric2.default.Path(path);
	        }
	    }]);

	    return Icon;
	}(_component2.default);

	module.exports = Icon;

/***/ }),
/* 116 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _tuiCodeSnippet = __webpack_require__(3);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	var _component = __webpack_require__(107);

	var _component2 = _interopRequireDefault(_component);

	var _mask = __webpack_require__(117);

	var _mask2 = _interopRequireDefault(_mask);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	var _blur = __webpack_require__(118);

	var _blur2 = _interopRequireDefault(_blur);

	var _sharpen = __webpack_require__(119);

	var _sharpen2 = _interopRequireDefault(_sharpen);

	var _emboss = __webpack_require__(120);

	var _emboss2 = _interopRequireDefault(_emboss);

	var _colorFilter = __webpack_require__(121);

	var _colorFilter2 = _interopRequireDefault(_colorFilter);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview Add filter module
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var rejectMessages = _consts2.default.rejectMessages;
	var filters = _fabric2.default.Image.filters;

	filters.Mask = _mask2.default;
	filters.Blur = _blur2.default;
	filters.Sharpen = _sharpen2.default;
	filters.Emboss = _emboss2.default;
	filters.ColorFilter = _colorFilter2.default;

	/**
	 * Filter
	 * @class Filter
	 * @param {Graphics} graphics - Graphics instance
	 * @extends {Component}
	 * @ignore
	 */

	var Filter = function (_Component) {
	    _inherits(Filter, _Component);

	    function Filter(graphics) {
	        _classCallCheck(this, Filter);

	        return _possibleConstructorReturn(this, (Filter.__proto__ || Object.getPrototypeOf(Filter)).call(this, _consts2.default.componentNames.FILTER, graphics));
	    }

	    /**
	     * Add filter to source image (a specific filter is added on fabric.js)
	     * @param {string} type - Filter type
	     * @param {Object} [options] - Options of filter
	     * @returns {Promise}
	     */


	    _createClass(Filter, [{
	        key: 'add',
	        value: function add(type, options) {
	            var _this2 = this;

	            return new _promise2.default(function (resolve, reject) {
	                var sourceImg = _this2._getSourceImage();
	                var canvas = _this2.getCanvas();
	                var imgFilter = _this2._getFilter(sourceImg, type);
	                if (!imgFilter) {
	                    imgFilter = _this2._createFilter(sourceImg, type, options);
	                }

	                if (!imgFilter) {
	                    reject(rejectMessages.invalidParameters);
	                }

	                _this2._changeFilterValues(imgFilter, options);

	                _this2._apply(sourceImg, function () {
	                    canvas.renderAll();
	                    resolve({
	                        type: type,
	                        action: 'add'
	                    });
	                });
	            });
	        }

	        /**
	         * Remove filter to source image
	         * @param {string} type - Filter type
	         * @returns {Promise}
	         */

	    }, {
	        key: 'remove',
	        value: function remove(type) {
	            var _this3 = this;

	            return new _promise2.default(function (resolve, reject) {
	                var sourceImg = _this3._getSourceImage();
	                var canvas = _this3.getCanvas();

	                if (!sourceImg.filters.length) {
	                    reject(rejectMessages.unsupportedOperation);
	                }

	                _this3._removeFilter(sourceImg, type);

	                _this3._apply(sourceImg, function () {
	                    canvas.renderAll();
	                    resolve({
	                        type: type,
	                        action: 'remove'
	                    });
	                });
	            });
	        }

	        /**
	         * Whether this has the filter or not
	         * @param {string} type - Filter type
	         * @returns {boolean} true if it has the filter
	         */

	    }, {
	        key: 'hasFilter',
	        value: function hasFilter(type) {
	            return !!this._getFilter(this._getSourceImage(), type);
	        }

	        /**
	         * Get a filter options
	         * @param {string} type - Filter type
	         * @returns {Object} filter options or null if there is no that filter
	         */

	    }, {
	        key: 'getOptions',
	        value: function getOptions(type) {
	            var sourceImg = this._getSourceImage();
	            var imgFilter = this._getFilter(sourceImg, type);
	            if (!imgFilter) {
	                return null;
	            }

	            return (0, _tuiCodeSnippet.extend)({}, imgFilter.options);
	        }

	        /**
	         * Change filter values
	         * @param {Object} imgFilter object of filter
	         * @param {Object} options object
	         * @private
	         */

	    }, {
	        key: '_changeFilterValues',
	        value: function _changeFilterValues(imgFilter, options) {
	            (0, _tuiCodeSnippet.forEach)(options, function (value, key) {
	                if (!(0, _tuiCodeSnippet.isUndefined)(imgFilter[key])) {
	                    imgFilter[key] = value;
	                }
	            });
	            (0, _tuiCodeSnippet.forEach)(imgFilter.options, function (value, key) {
	                if (!(0, _tuiCodeSnippet.isUndefined)(options[key])) {
	                    imgFilter.options[key] = options[key];
	                }
	            });
	        }

	        /**
	         * Apply filter
	         * @param {fabric.Image} sourceImg - Source image to apply filter
	         * @param {function} callback - Executed function after applying filter
	         * @private
	         */

	    }, {
	        key: '_apply',
	        value: function _apply(sourceImg, callback) {
	            sourceImg.applyFilters(callback);
	        }

	        /**
	         * Get source image on canvas
	         * @returns {fabric.Image} Current source image on canvas
	         * @private
	         */

	    }, {
	        key: '_getSourceImage',
	        value: function _getSourceImage() {
	            return this.getCanvasImage();
	        }

	        /**
	         * Create filter instance
	         * @param {fabric.Image} sourceImg - Source image to apply filter
	         * @param {string} type - Filter type
	         * @param {Object} [options] - Options of filter
	         * @returns {Object} Fabric object of filter
	         * @private
	         */

	    }, {
	        key: '_createFilter',
	        value: function _createFilter(sourceImg, type, options) {
	            var filterObj = void 0;
	            // capitalize first letter for matching with fabric image filter name
	            var fabricType = this._getFabricFilterType(type);
	            var ImageFilter = _fabric2.default.Image.filters[fabricType];
	            if (ImageFilter) {
	                filterObj = new ImageFilter(options);
	                filterObj.options = options;
	                sourceImg.filters.push(filterObj);
	            }

	            return filterObj;
	        }

	        /**
	         * Get applied filter instance
	         * @param {fabric.Image} sourceImg - Source image to apply filter
	         * @param {string} type - Filter type
	         * @returns {Object} Fabric object of filter
	         * @private
	         */

	    }, {
	        key: '_getFilter',
	        value: function _getFilter(sourceImg, type) {
	            var imgFilter = null;

	            if (sourceImg) {
	                var fabricType = this._getFabricFilterType(type);
	                var length = sourceImg.filters.length;

	                var item = void 0,
	                    i = void 0;

	                for (i = 0; i < length; i += 1) {
	                    item = sourceImg.filters[i];
	                    if (item.type === fabricType) {
	                        imgFilter = item;
	                        break;
	                    }
	                }
	            }

	            return imgFilter;
	        }

	        /**
	         * Remove applied filter instance
	         * @param {fabric.Image} sourceImg - Source image to apply filter
	         * @param {string} type - Filter type
	         * @private
	         */

	    }, {
	        key: '_removeFilter',
	        value: function _removeFilter(sourceImg, type) {
	            var fabricType = this._getFabricFilterType(type);
	            sourceImg.filters = (0, _tuiCodeSnippet.filter)(sourceImg.filters, function (value) {
	                return value.type !== fabricType;
	            });
	        }

	        /**
	         * Change filter class name to fabric's, especially capitalizing first letter
	         * @param {string} type - Filter type
	         * @example
	         * 'grayscale' -> 'Grayscale'
	         * @returns {string} Fabric filter class name
	         */

	    }, {
	        key: '_getFabricFilterType',
	        value: function _getFabricFilterType(type) {
	            return type.charAt(0).toUpperCase() + type.slice(1);
	        }
	    }]);

	    return Filter;
	}(_component2.default);

	module.exports = Filter;

/***/ }),
/* 117 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * Mask object
	 * @class Mask
	 * @extends {fabric.Image.filters.Mask}
	 * @ignore
	 */
	var Mask = _fabric2.default.util.createClass(_fabric2.default.Image.filters.Mask, /** @lends Mask.prototype */{
	    /**
	     * Apply filter to canvas element
	     * @param {Object} canvasEl - Canvas element to apply filter
	     * @override
	     */
	    applyTo: function applyTo(canvasEl) {
	        if (!this.mask) {
	            return;
	        }

	        var width = canvasEl.width,
	            height = canvasEl.height;

	        var maskCanvasEl = this._createCanvasOfMask(width, height);
	        var ctx = canvasEl.getContext('2d');
	        var maskCtx = maskCanvasEl.getContext('2d');
	        var imageData = ctx.getImageData(0, 0, width, height);

	        this._drawMask(maskCtx, canvasEl, ctx);
	        this._mapData(maskCtx, imageData, width, height);

	        ctx.putImageData(imageData, 0, 0);
	    },


	    /**
	     * Create canvas of mask image
	     * @param {number} width - Width of main canvas
	     * @param {number} height - Height of main canvas
	     * @returns {HTMLElement} Canvas element
	     * @private
	     */
	    _createCanvasOfMask: function _createCanvasOfMask(width, height) {
	        var maskCanvasEl = _fabric2.default.util.createCanvasElement();

	        maskCanvasEl.width = width;
	        maskCanvasEl.height = height;

	        return maskCanvasEl;
	    },


	    /**
	     * Draw mask image on canvas element
	     * @param {Object} maskCtx - Context of mask canvas
	     * @private
	     */
	    _drawMask: function _drawMask(maskCtx) {
	        var mask = this.mask;

	        var maskImg = mask.getElement();

	        var left = mask.getLeft();
	        var top = mask.getTop();
	        var angle = mask.getAngle();

	        maskCtx.save();
	        maskCtx.translate(left, top);
	        maskCtx.rotate(angle * Math.PI / 180);
	        maskCtx.scale(mask.scaleX, mask.scaleY);
	        maskCtx.drawImage(maskImg, -maskImg.width / 2, -maskImg.height / 2);
	        maskCtx.restore();
	    },


	    /**
	     * Map mask image data to source image data
	     * @param {Object} maskCtx - Context of mask canvas
	     * @param {Object} imageData - Data of source image
	     * @param {number} width - Width of main canvas
	     * @param {number} height - Height of main canvas
	     * @private
	     */
	    _mapData: function _mapData(maskCtx, imageData, width, height) {
	        var sourceData = imageData.data;
	        var maskData = maskCtx.getImageData(0, 0, width, height).data;
	        var channel = this.channel;

	        var len = imageData.width * imageData.height * 4;

	        for (var i = 0; i < len; i += 4) {
	            sourceData[i + 3] = maskData[i + channel]; // adjust value of alpha data
	        }
	    }
	}); /**
	     * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	     * @fileoverview Mask extending fabric.Image.filters.Mask
	     */


	module.exports = Mask;

/***/ }),
/* 118 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * Blur object
	 * @class Blur
	 * @extends {fabric.Image.filters.Convolute}
	 * @ignore
	 */
	var Blur = _fabric2.default.util.createClass(_fabric2.default.Image.filters.Convolute, /** @lends Convolute.prototype */{
	  /**
	   * Filter type
	   * @param {String} type
	   * @default
	   */
	  type: 'Blur',

	  /**
	   * constructor
	   * @override
	   */
	  initialize: function initialize() {
	    var matrix = [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9];
	    this.matrix = matrix;
	  }
	}); /**
	     * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	     * @fileoverview Blur extending fabric.Image.filters.Convolute
	     */


	module.exports = Blur;

/***/ }),
/* 119 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * Sharpen object
	 * @class Sharpen
	 * @extends {fabric.Image.filters.Convolute}
	 * @ignore
	 */
	var Sharpen = _fabric2.default.util.createClass(_fabric2.default.Image.filters.Convolute, /** @lends Convolute.prototype */{
	  /**
	   * Filter type
	   * @param {String} type
	   * @default
	   */
	  type: 'Sharpen',

	  /**
	   * constructor
	   * @override
	   */
	  initialize: function initialize() {
	    var matrix = [0, -1, 0, -1, 5, -1, 0, -1, 0];
	    this.matrix = matrix;
	  }
	}); /**
	     * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	     * @fileoverview Sharpen extending fabric.Image.filters.Convolute
	     */


	module.exports = Sharpen;

/***/ }),
/* 120 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * Emboss object
	 * @class Emboss
	 * @extends {fabric.Image.filters.Convolute}
	 * @ignore
	 */
	var Emboss = _fabric2.default.util.createClass(_fabric2.default.Image.filters.Convolute, /** @lends Convolute.prototype */{
	  /**
	   * Filter type
	   * @param {String} type
	   * @default
	   */
	  type: 'Emboss',

	  /**
	   * constructor
	   * @override
	   */
	  initialize: function initialize() {
	    var matrix = [1, 1, 1, 1, 0.7, -1, -1, -1, -1];
	    this.matrix = matrix;
	  }
	}); /**
	     * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	     * @fileoverview Emboss extending fabric.Image.filters.Convolute
	     */


	module.exports = Emboss;

/***/ }),
/* 121 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * ColorFilter object
	 * @class ColorFilter
	 * @extends {fabric.Image.filters.BaseFilter}
	 * @ignore
	 */
	var ColorFilter = _fabric2.default.util.createClass(_fabric2.default.Image.filters.BaseFilter, /** @lends BaseFilter.prototype */{
	    /**
	     * Filter type
	     * @param {String} type
	     * @default
	     */
	    type: 'ColorFilter',

	    /**
	     * Constructor
	     * @member fabric.Image.filters.ColorFilter.prototype
	     * @param {Object} [options] Options object
	     * @param {Number} [options.color='#FFFFFF'] Value of color (0...255)
	     * @param {Number} [options.threshold=45] Value of threshold (0...255)
	     * @override
	     */
	    initialize: function initialize(options) {
	        if (!options) {
	            options = {};
	        }
	        this.color = options.color || '#FFFFFF';
	        this.threshold = options.threshold || 45;
	        this.x = options.x || null;
	        this.y = options.y || null;
	    },


	    /**
	     * Applies filter to canvas element
	     * @param {Object} canvasEl Canvas element to apply filter to
	     */
	    applyTo: function applyTo(canvasEl) {
	        // eslint-disable-line
	        var context = canvasEl.getContext('2d');
	        var imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height);
	        var data = imageData.data;
	        var threshold = this.threshold;

	        var filterColor = _fabric2.default.Color.sourceFromHex(this.color);
	        var i = void 0,
	            len = void 0;

	        if (this.x && this.y) {
	            filterColor = this._getColor(imageData, this.x, this.y);
	        }

	        for (i = 0, len = data.length; i < len; i += 4) {
	            if (this._isOutsideThreshold(data[i], filterColor[0], threshold) || this._isOutsideThreshold(data[i + 1], filterColor[1], threshold) || this._isOutsideThreshold(data[i + 2], filterColor[2], threshold)) {
	                continue;
	            }
	            data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0;
	        }
	        context.putImageData(imageData, 0, 0);
	    },


	    /**
	     * Check color if it is within threshold
	     * @param {Number} color1 source color
	     * @param {Number} color2 filtering color
	     * @param {Number} threshold threshold
	     * @returns {boolean} true if within threshold or false
	     */
	    _isOutsideThreshold: function _isOutsideThreshold(color1, color2, threshold) {
	        var diff = color1 - color2;

	        return Math.abs(diff) > threshold;
	    },


	    /**
	     * Get color at (x, y)
	     * @param {Object} imageData of canvas
	     * @param {Number} x left position
	     * @param {Number} y top position
	     * @returns {Array} color array
	     */
	    _getColor: function _getColor(imageData, x, y) {
	        var color = [0, 0, 0, 0];
	        var data = imageData.data,
	            width = imageData.width;

	        var bytes = 4;
	        var position = (width * y + x) * bytes;

	        color[0] = data[position];
	        color[1] = data[position + 1];
	        color[2] = data[position + 2];
	        color[3] = data[position + 3];

	        return color;
	    }
	}); /**
	     * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	     * @fileoverview ColorFilter extending fabric.Image.filters.BaseFilter
	     */


	module.exports = ColorFilter;

/***/ }),
/* 122 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _fabric = __webpack_require__(105);

	var _fabric2 = _interopRequireDefault(_fabric);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _component = __webpack_require__(107);

	var _component2 = _interopRequireDefault(_component);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	var _shapeResizeHelper = __webpack_require__(123);

	var _shapeResizeHelper2 = _interopRequireDefault(_shapeResizeHelper);

	var _tuiCodeSnippet = __webpack_require__(3);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview Shape component
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var rejectMessages = _consts2.default.rejectMessages,
	    eventNames = _consts2.default.eventNames;

	var KEY_CODES = _consts2.default.keyCodes;

	var DEFAULT_TYPE = 'rect';
	var DEFAULT_OPTIONS = {
	    strokeWidth: 1,
	    stroke: '#000000',
	    fill: '#ffffff',
	    width: 1,
	    height: 1,
	    rx: 0,
	    ry: 0,
	    lockSkewingX: true,
	    lockSkewingY: true,
	    lockUniScaling: false,
	    bringForward: true,
	    isRegular: false
	};

	var shapeType = ['rect', 'circle', 'triangle'];

	/**
	 * Shape
	 * @class Shape
	 * @param {Graphics} graphics - Graphics instance
	 * @extends {Component}
	 * @ignore
	 */

	var Shape = function (_Component) {
	    _inherits(Shape, _Component);

	    function Shape(graphics) {
	        _classCallCheck(this, Shape);

	        /**
	         * Object of The drawing shape
	         * @type {fabric.Object}
	         * @private
	         */
	        var _this = _possibleConstructorReturn(this, (Shape.__proto__ || Object.getPrototypeOf(Shape)).call(this, _consts2.default.componentNames.SHAPE, graphics));

	        _this._shapeObj = null;

	        /**
	         * Type of the drawing shape
	         * @type {string}
	         * @private
	         */
	        _this._type = DEFAULT_TYPE;

	        /**
	         * Options to draw the shape
	         * @type {Object}
	         * @private
	         */
	        _this._options = (0, _tuiCodeSnippet.extend)({}, DEFAULT_OPTIONS);

	        /**
	         * Whether the shape object is selected or not
	         * @type {boolean}
	         * @private
	         */
	        _this._isSelected = false;

	        /**
	         * Pointer for drawing shape (x, y)
	         * @type {Object}
	         * @private
	         */
	        _this._startPoint = {};

	        /**
	         * Using shortcut on drawing shape
	         * @type {boolean}
	         * @private
	         */
	        _this._withShiftKey = false;

	        /**
	         * Event handler list
	         * @type {Object}
	         * @private
	         */
	        _this._handlers = {
	            mousedown: _this._onFabricMouseDown.bind(_this),
	            mousemove: _this._onFabricMouseMove.bind(_this),
	            mouseup: _this._onFabricMouseUp.bind(_this),
	            keydown: _this._onKeyDown.bind(_this),
	            keyup: _this._onKeyUp.bind(_this)
	        };
	        return _this;
	    }

	    /**
	     * Start to draw the shape on canvas
	     * @ignore
	     */


	    _createClass(Shape, [{
	        key: 'start',
	        value: function start() {
	            var canvas = this.getCanvas();

	            this._isSelected = false;

	            canvas.defaultCursor = 'crosshair';
	            canvas.selection = false;
	            canvas.uniScaleTransform = true;
	            canvas.on({
	                'mouse:down': this._handlers.mousedown
	            });

	            _fabric2.default.util.addListener(document, 'keydown', this._handlers.keydown);
	            _fabric2.default.util.addListener(document, 'keyup', this._handlers.keyup);
	        }

	        /**
	         * End to draw the shape on canvas
	         * @ignore
	         */

	    }, {
	        key: 'end',
	        value: function end() {
	            var canvas = this.getCanvas();

	            this._isSelected = false;

	            canvas.defaultCursor = 'default';

	            canvas.selection = true;
	            canvas.uniScaleTransform = false;
	            canvas.off({
	                'mouse:down': this._handlers.mousedown
	            });

	            _fabric2.default.util.removeListener(document, 'keydown', this._handlers.keydown);
	            _fabric2.default.util.removeListener(document, 'keyup', this._handlers.keyup);
	        }

	        /**
	         * Set states of the current drawing shape
	         * @ignore
	         * @param {string} type - Shape type (ex: 'rect', 'circle')
	         * @param {Object} [options] - Shape options
	         *      @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
	         *      @param {string} [options.stoke] - Shape outline color
	         *      @param {number} [options.strokeWidth] - Shape outline width
	         *      @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
	         *      @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
	         *      @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
	         *      @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
	         */

	    }, {
	        key: 'setStates',
	        value: function setStates(type, options) {
	            this._type = type;

	            if (options) {
	                this._options = (0, _tuiCodeSnippet.extend)(this._options, options);
	            }
	        }

	        /**
	         * Add the shape
	         * @ignore
	         * @param {string} type - Shape type (ex: 'rect', 'circle')
	         * @param {Object} options - Shape options
	         *      @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
	         *      @param {string} [options.stroke] - Shape outline color
	         *      @param {number} [options.strokeWidth] - Shape outline width
	         *      @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
	         *      @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
	         *      @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
	         *      @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
	         *      @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not
	         * @returns {Promise}
	         */

	    }, {
	        key: 'add',
	        value: function add(type, options) {
	            var _this2 = this;

	            return new _promise2.default(function (resolve) {
	                var canvas = _this2.getCanvas();
	                options = _this2._createOptions(options);
	                var shapeObj = _this2._createInstance(type, options);

	                _this2._bindEventOnShape(shapeObj);

	                canvas.add(shapeObj).setActiveObject(shapeObj);
	                resolve(_this2.graphics.createObjectProperties(shapeObj));
	            });
	        }

	        /**
	         * Change the shape
	         * @ignore
	         * @param {fabric.Object} shapeObj - Selected shape object on canvas
	         * @param {Object} options - Shape options
	         *      @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
	         *      @param {string} [options.stroke] - Shape outline color
	         *      @param {number} [options.strokeWidth] - Shape outline width
	         *      @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
	         *      @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
	         *      @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
	         *      @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
	         *      @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not
	         * @returns {Promise}
	         */

	    }, {
	        key: 'change',
	        value: function change(shapeObj, options) {
	            var _this3 = this;

	            return new _promise2.default(function (resolve, reject) {
	                if ((0, _tuiCodeSnippet.inArray)(shapeObj.get('type'), shapeType) < 0) {
	                    reject(rejectMessages.unsupportedType);
	                }

	                shapeObj.set(options);
	                _this3.getCanvas().renderAll();
	                resolve();
	            });
	        }

	        /**
	         * Create the instance of shape
	         * @param {string} type - Shape type
	         * @param {Object} options - Options to creat the shape
	         * @returns {fabric.Object} Shape instance
	         * @private
	         */

	    }, {
	        key: '_createInstance',
	        value: function _createInstance(type, options) {
	            var instance = void 0;

	            switch (type) {
	                case 'rect':
	                    instance = new _fabric2.default.Rect(options);
	                    break;
	                case 'circle':
	                    instance = new _fabric2.default.Ellipse((0, _tuiCodeSnippet.extend)({
	                        type: 'circle'
	                    }, options));
	                    break;
	                case 'triangle':
	                    instance = new _fabric2.default.Triangle(options);
	                    break;
	                default:
	                    instance = {};
	            }

	            return instance;
	        }

	        /**
	         * Get the options to create the shape
	         * @param {Object} options - Options to creat the shape
	         * @returns {Object} Shape options
	         * @private
	         */

	    }, {
	        key: '_createOptions',
	        value: function _createOptions(options) {
	            var selectionStyles = _consts2.default.fObjectOptions.SELECTION_STYLE;

	            options = (0, _tuiCodeSnippet.extend)({}, DEFAULT_OPTIONS, this._options, selectionStyles, options);

	            if (options.isRegular) {
	                options.lockUniScaling = true;
	            }

	            return options;
	        }

	        /**
	         * Bind fabric events on the creating shape object
	         * @param {fabric.Object} shapeObj - Shape object
	         * @private
	         */

	    }, {
	        key: '_bindEventOnShape',
	        value: function _bindEventOnShape(shapeObj) {
	            var self = this;
	            var canvas = this.getCanvas();

	            shapeObj.on({
	                added: function added() {
	                    self._shapeObj = this;
	                    _shapeResizeHelper2.default.setOrigins(self._shapeObj);
	                },
	                selected: function selected() {
	                    self._isSelected = true;
	                    self._shapeObj = this;
	                    canvas.uniScaleTransform = true;
	                    canvas.defaultCursor = 'default';
	                    _shapeResizeHelper2.default.setOrigins(self._shapeObj);
	                },
	                deselected: function deselected() {
	                    self._isSelected = false;
	                    self._shapeObj = null;
	                    canvas.defaultCursor = 'crosshair';
	                    canvas.uniScaleTransform = false;
	                },
	                modified: function modified() {
	                    var currentObj = self._shapeObj;

	                    _shapeResizeHelper2.default.adjustOriginToCenter(currentObj);
	                    _shapeResizeHelper2.default.setOrigins(currentObj);
	                },
	                scaling: function scaling(fEvent) {
	                    var pointer = canvas.getPointer(fEvent.e);
	                    var currentObj = self._shapeObj;

	                    canvas.setCursor('crosshair');
	                    _shapeResizeHelper2.default.resize(currentObj, pointer, true);
	                }
	            });
	        }

	        /**
	         * MouseDown event handler on canvas
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseDown',
	        value: function _onFabricMouseDown(fEvent) {
	            if (!fEvent.target) {
	                this._isSelected = false;
	                this._shapeObj = false;
	            }

	            if (!this._isSelected && !this._shapeObj) {
	                var canvas = this.getCanvas();
	                this._startPoint = canvas.getPointer(fEvent.e);

	                canvas.on({
	                    'mouse:move': this._handlers.mousemove,
	                    'mouse:up': this._handlers.mouseup
	                });
	            }
	        }

	        /**
	         * MouseDown event handler on canvas
	         * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseMove',
	        value: function _onFabricMouseMove(fEvent) {
	            var _this4 = this;

	            var canvas = this.getCanvas();
	            var pointer = canvas.getPointer(fEvent.e);
	            var startPointX = this._startPoint.x;
	            var startPointY = this._startPoint.y;
	            var width = startPointX - pointer.x;
	            var height = startPointY - pointer.y;
	            var shape = this._shapeObj;

	            if (!shape) {
	                this.add(this._type, {
	                    left: startPointX,
	                    top: startPointY,
	                    width: width,
	                    height: height
	                }).then(function (objectProps) {
	                    _this4.fire(eventNames.ADD_OBJECT, objectProps);
	                });
	            } else {
	                this._shapeObj.set({
	                    isRegular: this._withShiftKey
	                });
	                _shapeResizeHelper2.default.resize(shape, pointer);
	                canvas.renderAll();
	            }
	        }

	        /**
	         * MouseUp event handler on canvas
	         * @private
	         */

	    }, {
	        key: '_onFabricMouseUp',
	        value: function _onFabricMouseUp() {
	            var canvas = this.getCanvas();
	            var shape = this._shapeObj;

	            if (shape) {
	                _shapeResizeHelper2.default.adjustOriginToCenter(shape);
	            }

	            this.fire(eventNames.ADD_OBJECT_AFTER, this.graphics.createObjectProperties(shape));

	            canvas.off({
	                'mouse:move': this._handlers.mousemove,
	                'mouse:up': this._handlers.mouseup
	            });
	        }

	        /**
	         * Keydown event handler on document
	         * @param {KeyboardEvent} e - Event object
	         * @private
	         */

	    }, {
	        key: '_onKeyDown',
	        value: function _onKeyDown(e) {
	            if (e.keyCode === KEY_CODES.SHIFT) {
	                this._withShiftKey = true;

	                if (this._shapeObj) {
	                    this._shapeObj.isRegular = true;
	                }
	            }
	        }

	        /**
	         * Keyup event handler on document
	         * @param {KeyboardEvent} e - Event object
	         * @private
	         */

	    }, {
	        key: '_onKeyUp',
	        value: function _onKeyUp(e) {
	            if (e.keyCode === KEY_CODES.SHIFT) {
	                this._withShiftKey = false;

	                if (this._shapeObj) {
	                    this._shapeObj.isRegular = false;
	                }
	            }
	        }
	    }]);

	    return Shape;
	}(_component2.default);

	module.exports = Shape;

/***/ }),
/* 123 */
/***/ (function(module, exports) {

	'use strict';

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Shape resize helper
	 */
	var DIVISOR = {
	    rect: 1,
	    circle: 2,
	    triangle: 1
	};
	var DIMENSION_KEYS = {
	    rect: {
	        w: 'width',
	        h: 'height'
	    },
	    circle: {
	        w: 'rx',
	        h: 'ry'
	    },
	    triangle: {
	        w: 'width',
	        h: 'height'
	    }
	};

	/**
	 * Set the start point value to the shape object
	 * @param {fabric.Object} shape - Shape object
	 * @ignore
	 */
	function setStartPoint(shape) {
	    var originX = shape.getOriginX();
	    var originY = shape.getOriginY();
	    var originKey = originX.substring(0, 1) + originY.substring(0, 1);

	    shape.startPoint = shape.origins[originKey];
	}

	/**
	 * Get the positions of ratated origin by the pointer value
	 * @param {{x: number, y: number}} origin - Origin value
	 * @param {{x: number, y: number}} pointer - Pointer value
	 * @param {number} angle - Rotating angle
	 * @returns {Object} Postions of origin
	 * @ignore
	 */
	function getPositionsOfRotatedOrigin(origin, pointer, angle) {
	    var sx = origin.x;
	    var sy = origin.y;
	    var px = pointer.x;
	    var py = pointer.y;
	    var r = angle * Math.PI / 180;
	    var rx = (px - sx) * Math.cos(r) - (py - sy) * Math.sin(r) + sx;
	    var ry = (px - sx) * Math.sin(r) + (py - sy) * Math.cos(r) + sy;

	    return {
	        originX: sx > rx ? 'right' : 'left',
	        originY: sy > ry ? 'bottom' : 'top'
	    };
	}

	/**
	 * Whether the shape has the center origin or not
	 * @param {fabric.Object} shape - Shape object
	 * @returns {boolean} State
	 * @ignore
	 */
	function hasCenterOrigin(shape) {
	    return shape.getOriginX() === 'center' && shape.getOriginY() === 'center';
	}

	/**
	 * Adjust the origin of shape by the start point
	 * @param {{x: number, y: number}} pointer - Pointer value
	 * @param {fabric.Object} shape - Shape object
	 * @ignore
	 */
	function adjustOriginByStartPoint(pointer, shape) {
	    var centerPoint = shape.getPointByOrigin('center', 'center');
	    var angle = -shape.getAngle();
	    var originPositions = getPositionsOfRotatedOrigin(centerPoint, pointer, angle);
	    var originX = originPositions.originX,
	        originY = originPositions.originY;

	    var origin = shape.getPointByOrigin(originX, originY);
	    var left = shape.getLeft() - (centerPoint.x - origin.x);
	    var top = shape.getTop() - (centerPoint.x - origin.y);

	    shape.set({
	        originX: originX,
	        originY: originY,
	        left: left,
	        top: top
	    });

	    shape.setCoords();
	}

	/**
	 * Adjust the origin of shape by the moving pointer value
	 * @param {{x: number, y: number}} pointer - Pointer value
	 * @param {fabric.Object} shape - Shape object
	 * @ignore
	 */
	function adjustOriginByMovingPointer(pointer, shape) {
	    var origin = shape.startPoint;
	    var angle = -shape.getAngle();
	    var originPositions = getPositionsOfRotatedOrigin(origin, pointer, angle);
	    var originX = originPositions.originX,
	        originY = originPositions.originY;


	    shape.setPositionByOrigin(origin, originX, originY);
	}

	/**
	 * Adjust the dimension of shape on firing scaling event
	 * @param {fabric.Object} shape - Shape object
	 * @ignore
	 */
	function adjustDimensionOnScaling(shape) {
	    var type = shape.type,
	        scaleX = shape.scaleX,
	        scaleY = shape.scaleY;

	    var dimensionKeys = DIMENSION_KEYS[type];
	    var width = shape[dimensionKeys.w] * scaleX;
	    var height = shape[dimensionKeys.h] * scaleY;

	    if (shape.isRegular) {
	        var maxScale = Math.max(scaleX, scaleY);

	        width = shape[dimensionKeys.w] * maxScale;
	        height = shape[dimensionKeys.h] * maxScale;
	    }

	    var options = {
	        hasControls: false,
	        hasBorders: false,
	        scaleX: 1,
	        scaleY: 1
	    };

	    options[dimensionKeys.w] = width;
	    options[dimensionKeys.h] = height;

	    shape.set(options);
	}

	/**
	 * Adjust the dimension of shape on firing mouse move event
	 * @param {{x: number, y: number}} pointer - Pointer value
	 * @param {fabric.Object} shape - Shape object
	 * @ignore
	 */
	function adjustDimensionOnMouseMove(pointer, shape) {
	    var type = shape.type,
	        strokeWidth = shape.strokeWidth,
	        origin = shape.startPoint;

	    var divisor = DIVISOR[type];
	    var dimensionKeys = DIMENSION_KEYS[type];
	    var isTriangle = !!(shape.type === 'triangle');
	    var options = {};
	    var width = Math.abs(origin.x - pointer.x) / divisor;
	    var height = Math.abs(origin.y - pointer.y) / divisor;

	    if (width > strokeWidth) {
	        width -= strokeWidth / divisor;
	    }

	    if (height > strokeWidth) {
	        height -= strokeWidth / divisor;
	    }

	    if (shape.isRegular) {
	        width = height = Math.max(width, height);

	        if (isTriangle) {
	            height = Math.sqrt(3) / 2 * width;
	        }
	    }

	    options[dimensionKeys.w] = width;
	    options[dimensionKeys.h] = height;

	    shape.set(options);
	}

	module.exports = {
	    /**
	     * Set each origin value to shape
	     * @param {fabric.Object} shape - Shape object
	     */
	    setOrigins: function setOrigins(shape) {
	        var leftTopPoint = shape.getPointByOrigin('left', 'top');
	        var rightTopPoint = shape.getPointByOrigin('right', 'top');
	        var rightBottomPoint = shape.getPointByOrigin('right', 'bottom');
	        var leftBottomPoint = shape.getPointByOrigin('left', 'bottom');

	        shape.origins = {
	            lt: leftTopPoint,
	            rt: rightTopPoint,
	            rb: rightBottomPoint,
	            lb: leftBottomPoint
	        };
	    },


	    /**
	     * Resize the shape
	     * @param {fabric.Object} shape - Shape object
	     * @param {{x: number, y: number}} pointer - Mouse pointer values on canvas
	     * @param {boolean} isScaling - Whether the resizing action is scaling or not
	     */
	    resize: function resize(shape, pointer, isScaling) {
	        if (hasCenterOrigin(shape)) {
	            adjustOriginByStartPoint(pointer, shape);
	            setStartPoint(shape);
	        }

	        if (isScaling) {
	            adjustDimensionOnScaling(shape, pointer);
	        } else {
	            adjustDimensionOnMouseMove(pointer, shape);
	        }

	        adjustOriginByMovingPointer(pointer, shape);
	    },


	    /**
	     * Adjust the origin position of shape to center
	     * @param {fabric.Object} shape - Shape object
	     */
	    adjustOriginToCenter: function adjustOriginToCenter(shape) {
	        var centerPoint = shape.getPointByOrigin('center', 'center');
	        var originX = shape.getOriginX();
	        var originY = shape.getOriginY();
	        var origin = shape.getPointByOrigin(originX, originY);
	        var left = shape.getLeft() + (centerPoint.x - origin.x);
	        var top = shape.getTop() + (centerPoint.y - origin.y);

	        shape.set({
	            hasControls: true,
	            hasBorders: true,
	            originX: 'center',
	            originY: 'center',
	            left: left,
	            top: top
	        });

	        shape.setCoords(); // For left, top properties
	    }
	};

/***/ }),
/* 124 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _drawingMode = __webpack_require__(125);

	var _drawingMode2 = _interopRequireDefault(_drawingMode);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview CropperDrawingMode class
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var drawingModes = _consts2.default.drawingModes;

	var components = _consts2.default.componentNames;

	/**
	 * CropperDrawingMode class
	 * @class
	 * @ignore
	 */

	var CropperDrawingMode = function (_DrawingMode) {
	    _inherits(CropperDrawingMode, _DrawingMode);

	    function CropperDrawingMode() {
	        _classCallCheck(this, CropperDrawingMode);

	        return _possibleConstructorReturn(this, (CropperDrawingMode.__proto__ || Object.getPrototypeOf(CropperDrawingMode)).call(this, drawingModes.CROPPER));
	    }

	    /**
	    * start this drawing mode
	    * @param {Graphics} graphics - Graphics instance
	    * @override
	    */


	    _createClass(CropperDrawingMode, [{
	        key: 'start',
	        value: function start(graphics) {
	            var cropper = graphics.getComponent(components.CROPPER);
	            cropper.start();
	        }

	        /**
	         * stop this drawing mode
	         * @param {Graphics} graphics - Graphics instance
	         * @override
	         */

	    }, {
	        key: 'end',
	        value: function end(graphics) {
	            var cropper = graphics.getComponent(components.CROPPER);
	            cropper.end();
	        }
	    }]);

	    return CropperDrawingMode;
	}(_drawingMode2.default);

	module.exports = CropperDrawingMode;

/***/ }),
/* 125 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * @fileoverview DrawingMode interface
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      */


	var _errorMessage = __webpack_require__(71);

	var _errorMessage2 = _interopRequireDefault(_errorMessage);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	var createMessage = _errorMessage2.default.create;
	var errorTypes = _errorMessage2.default.types;

	/**
	 * DrawingMode interface
	 * @class
	 * @param {string} name - drawing mode name
	 * @ignore
	 */

	var DrawingMode = function () {
	  function DrawingMode(name) {
	    _classCallCheck(this, DrawingMode);

	    /**
	     * the name of drawing mode
	     * @type {string}
	     */
	    this.name = name;
	  }

	  /**
	   * Get this drawing mode name;
	   * @returns {string} drawing mode name
	   */


	  _createClass(DrawingMode, [{
	    key: 'getName',
	    value: function getName() {
	      return this.name;
	    }

	    /**
	    * start this drawing mode
	    * @param {Object} options - drawing mode options
	    * @abstract
	    */

	  }, {
	    key: 'start',
	    value: function start() {
	      throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'start'));
	    }

	    /**
	     * stop this drawing mode
	     * @abstract
	     */

	  }, {
	    key: 'stop',
	    value: function stop() {
	      throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'stop'));
	    }
	  }]);

	  return DrawingMode;
	}();

	module.exports = DrawingMode;

/***/ }),
/* 126 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _drawingMode = __webpack_require__(125);

	var _drawingMode2 = _interopRequireDefault(_drawingMode);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview FreeDrawingMode class
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var drawingModes = _consts2.default.drawingModes;

	var components = _consts2.default.componentNames;

	/**
	 * FreeDrawingMode class
	 * @class
	 * @ignore
	 */

	var FreeDrawingMode = function (_DrawingMode) {
	    _inherits(FreeDrawingMode, _DrawingMode);

	    function FreeDrawingMode() {
	        _classCallCheck(this, FreeDrawingMode);

	        return _possibleConstructorReturn(this, (FreeDrawingMode.__proto__ || Object.getPrototypeOf(FreeDrawingMode)).call(this, drawingModes.FREE_DRAWING));
	    }

	    /**
	    * start this drawing mode
	    * @param {Graphics} graphics - Graphics instance
	    * @param {{width: ?number, color: ?string}} [options] - Brush width & color
	    * @override
	    */


	    _createClass(FreeDrawingMode, [{
	        key: 'start',
	        value: function start(graphics, options) {
	            var freeDrawing = graphics.getComponent(components.FREE_DRAWING);
	            freeDrawing.start(options);
	        }

	        /**
	         * stop this drawing mode
	         * @param {Graphics} graphics - Graphics instance
	         * @override
	         */

	    }, {
	        key: 'end',
	        value: function end(graphics) {
	            var freeDrawing = graphics.getComponent(components.FREE_DRAWING);
	            freeDrawing.end();
	        }
	    }]);

	    return FreeDrawingMode;
	}(_drawingMode2.default);

	module.exports = FreeDrawingMode;

/***/ }),
/* 127 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _drawingMode = __webpack_require__(125);

	var _drawingMode2 = _interopRequireDefault(_drawingMode);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview LineDrawingMode class
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var drawingModes = _consts2.default.drawingModes;

	var components = _consts2.default.componentNames;

	/**
	 * LineDrawingMode class
	 * @class
	 * @ignore
	 */

	var LineDrawingMode = function (_DrawingMode) {
	    _inherits(LineDrawingMode, _DrawingMode);

	    function LineDrawingMode() {
	        _classCallCheck(this, LineDrawingMode);

	        return _possibleConstructorReturn(this, (LineDrawingMode.__proto__ || Object.getPrototypeOf(LineDrawingMode)).call(this, drawingModes.LINE_DRAWING));
	    }

	    /**
	    * start this drawing mode
	    * @param {Graphics} graphics - Graphics instance
	    * @param {{width: ?number, color: ?string}} [options] - Brush width & color
	    * @override
	    */


	    _createClass(LineDrawingMode, [{
	        key: 'start',
	        value: function start(graphics, options) {
	            var lineDrawing = graphics.getComponent(components.LINE);
	            lineDrawing.start(options);
	        }

	        /**
	         * stop this drawing mode
	         * @param {Graphics} graphics - Graphics instance
	         * @override
	         */

	    }, {
	        key: 'end',
	        value: function end(graphics) {
	            var lineDrawing = graphics.getComponent(components.LINE);
	            lineDrawing.end();
	        }
	    }]);

	    return LineDrawingMode;
	}(_drawingMode2.default);

	module.exports = LineDrawingMode;

/***/ }),
/* 128 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _drawingMode = __webpack_require__(125);

	var _drawingMode2 = _interopRequireDefault(_drawingMode);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview ShapeDrawingMode class
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var drawingModes = _consts2.default.drawingModes;

	var components = _consts2.default.componentNames;

	/**
	 * ShapeDrawingMode class
	 * @class
	 * @ignore
	 */

	var ShapeDrawingMode = function (_DrawingMode) {
	    _inherits(ShapeDrawingMode, _DrawingMode);

	    function ShapeDrawingMode() {
	        _classCallCheck(this, ShapeDrawingMode);

	        return _possibleConstructorReturn(this, (ShapeDrawingMode.__proto__ || Object.getPrototypeOf(ShapeDrawingMode)).call(this, drawingModes.SHAPE));
	    }

	    /**
	    * start this drawing mode
	    * @param {Graphics} graphics - Graphics instance
	    * @override
	    */


	    _createClass(ShapeDrawingMode, [{
	        key: 'start',
	        value: function start(graphics) {
	            var shape = graphics.getComponent(components.SHAPE);
	            shape.start();
	        }

	        /**
	         * stop this drawing mode
	         * @param {Graphics} graphics - Graphics instance
	         * @override
	         */

	    }, {
	        key: 'end',
	        value: function end(graphics) {
	            var shape = graphics.getComponent(components.SHAPE);
	            shape.end();
	        }
	    }]);

	    return ShapeDrawingMode;
	}(_drawingMode2.default);

	module.exports = ShapeDrawingMode;

/***/ }),
/* 129 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

	var _drawingMode = __webpack_require__(125);

	var _drawingMode2 = _interopRequireDefault(_drawingMode);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

	function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

	function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                * @fileoverview TextDrawingMode class
	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                */


	var drawingModes = _consts2.default.drawingModes;

	var components = _consts2.default.componentNames;

	/**
	 * TextDrawingMode class
	 * @class
	 * @ignore
	 */

	var TextDrawingMode = function (_DrawingMode) {
	    _inherits(TextDrawingMode, _DrawingMode);

	    function TextDrawingMode() {
	        _classCallCheck(this, TextDrawingMode);

	        return _possibleConstructorReturn(this, (TextDrawingMode.__proto__ || Object.getPrototypeOf(TextDrawingMode)).call(this, drawingModes.TEXT));
	    }

	    /**
	    * start this drawing mode
	    * @param {Graphics} graphics - Graphics instance
	    * @override
	    */


	    _createClass(TextDrawingMode, [{
	        key: 'start',
	        value: function start(graphics) {
	            var text = graphics.getComponent(components.TEXT);
	            text.start();
	        }

	        /**
	         * stop this drawing mode
	         * @param {Graphics} graphics - Graphics instance
	         * @override
	         */

	    }, {
	        key: 'end',
	        value: function end(graphics) {
	            var text = graphics.getComponent(components.TEXT);
	            text.end();
	        }
	    }]);

	    return TextDrawingMode;
	}(_drawingMode2.default);

	module.exports = TextDrawingMode;

/***/ }),
/* 130 */
/***/ (function(module, exports) {

	// removed by extract-text-webpack-plugin

/***/ }),
/* 131 */,
/* 132 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var componentNames = _consts2.default.componentNames,
	    commandNames = _consts2.default.commandNames; /**
	                                                   * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                   * @fileoverview Add an icon
	                                                   */

	var ICON = componentNames.ICON;


	var command = {
	    name: commandNames.ADD_ICON,

	    /**
	     * Add an icon
	     * @param {Graphics} graphics - Graphics instance
	     * @param {string} type - Icon type ('arrow', 'cancel', custom icon name)
	     * @param {Object} options - Icon options
	     *      @param {string} [options.fill] - Icon foreground color
	     *      @param {string} [options.left] - Icon x position
	     *      @param {string} [options.top] - Icon y position
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, type, options) {
	        var _this = this;

	        var iconComp = graphics.getComponent(ICON);

	        return iconComp.add(type, options).then(function (objectProps) {
	            _this.undoData.object = graphics.getObject(objectProps.id);

	            return objectProps;
	        });
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        graphics.remove(this.undoData.object);

	        return _promise2.default.resolve();
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 133 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var commandNames = _consts2.default.commandNames; /**
	                                                   * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                   * @fileoverview Add an image object
	                                                   */

	var command = {
	    name: commandNames.ADD_IMAGE_OBJECT,

	    /**
	     * Add an image object
	     * @param {Graphics} graphics - Graphics instance
	     * @param {string} imgUrl - Image url to make object
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, imgUrl) {
	        var _this = this;

	        return graphics.addImageObject(imgUrl).then(function (objectProps) {
	            _this.undoData.object = graphics.getObject(objectProps.id);

	            return objectProps;
	        });
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        graphics.remove(this.undoData.object);

	        return _promise2.default.resolve();
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 134 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var commandNames = _consts2.default.commandNames,
	    rejectMessages = _consts2.default.rejectMessages; /**
	                                                       * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                       * @fileoverview Add an object
	                                                       */

	var command = {
	    name: commandNames.ADD_OBJECT,

	    /**
	     * Add an object
	     * @param {Graphics} graphics - Graphics instance
	     * @param {Object} object - Fabric object
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, object) {
	        return new _promise2.default(function (resolve, reject) {
	            if (!graphics.contains(object)) {
	                graphics.add(object);
	                resolve(object);
	            } else {
	                reject(rejectMessages.addedObject);
	            }
	        });
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @param {Object} object - Fabric object
	     * @returns {Promise}
	     */
	    undo: function undo(graphics, object) {
	        return new _promise2.default(function (resolve, reject) {
	            if (graphics.contains(object)) {
	                graphics.remove(object);
	                resolve(object);
	            } else {
	                reject(rejectMessages.noObject);
	            }
	        });
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 135 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var componentNames = _consts2.default.componentNames,
	    commandNames = _consts2.default.commandNames; /**
	                                                   * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                   * @fileoverview Add a shape
	                                                   */

	var SHAPE = componentNames.SHAPE;


	var command = {
	    name: commandNames.ADD_SHAPE,

	    /**
	     * Add a shape
	     * @param {Graphics} graphics - Graphics instance
	     * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
	     * @param {Object} options - Shape options
	     *      @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
	     *      @param {string} [options.stroke] - Shape outline color
	     *      @param {number} [options.strokeWidth] - Shape outline width
	     *      @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
	     *      @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
	     *      @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
	     *      @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
	     *      @param {number} [options.left] - Shape x position
	     *      @param {number} [options.top] - Shape y position
	     *      @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, type, options) {
	        var _this = this;

	        var shapeComp = graphics.getComponent(SHAPE);

	        return shapeComp.add(type, options).then(function (objectProps) {
	            _this.undoData.object = graphics.getObject(objectProps.id);

	            return objectProps;
	        });
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        graphics.remove(this.undoData.object);

	        return _promise2.default.resolve();
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 136 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var componentNames = _consts2.default.componentNames,
	    commandNames = _consts2.default.commandNames; /**
	                                                   * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                   * @fileoverview Add a text object
	                                                   */

	var TEXT = componentNames.TEXT;


	var command = {
	    name: commandNames.ADD_TEXT,

	    /**
	     * Add a text object
	     * @param {Graphics} graphics - Graphics instance
	     * @param {string} text - Initial input text
	     * @param {Object} [options] Options for text styles
	     *     @param {Object} [options.styles] Initial styles
	     *         @param {string} [options.styles.fill] Color
	     *         @param {string} [options.styles.fontFamily] Font type for text
	     *         @param {number} [options.styles.fontSize] Size
	     *         @param {string} [options.styles.fontStyle] Type of inclination (normal / italic)
	     *         @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold)
	     *         @param {string} [options.styles.textAlign] Type of text align (left / center / right)
	     *         @param {string} [options.styles.textDecoraiton] Type of line (underline / line-throgh / overline)
	     *     @param {{x: number, y: number}} [options.position] - Initial position
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, text, options) {
	        var _this = this;

	        var textComp = graphics.getComponent(TEXT);

	        return textComp.add(text, options).then(function (objectProps) {
	            _this.undoData.object = graphics.getObject(objectProps.id);

	            return objectProps;
	        });
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        graphics.remove(this.undoData.object);

	        return _promise2.default.resolve();
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 137 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Apply a filter into an image
	 */
	var componentNames = _consts2.default.componentNames,
	    rejectMessages = _consts2.default.rejectMessages,
	    commandNames = _consts2.default.commandNames;
	var FILTER = componentNames.FILTER;


	var command = {
	    name: commandNames.APPLY_FILTER,

	    /**
	     * Apply a filter into an image
	     * @param {Graphics} graphics - Graphics instance
	     * @param {string} type - Filter type
	     * @param {Object} options - Filter options
	     *  @param {number} options.maskObjId - masking image object id
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, type, options) {
	        var filterComp = graphics.getComponent(FILTER);

	        if (type === 'mask') {
	            var maskObj = graphics.getObject(options.maskObjId);

	            if (!(maskObj && maskObj.isType('image'))) {
	                return Promise.reject(rejectMessages.invalidParameters);
	            }

	            options = {
	                mask: maskObj
	            };
	        }

	        if (type === 'mask') {
	            this.undoData.object = options.mask;
	            graphics.remove(options.mask);
	        } else {
	            this.undoData.options = filterComp.getOptions(type);
	        }

	        return filterComp.add(type, options);
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @param {string} type - Filter type
	     * @returns {Promise}
	     */
	    undo: function undo(graphics, type) {
	        var filterComp = graphics.getComponent(FILTER);

	        if (type === 'mask') {
	            var mask = this.undoData.object;
	            graphics.add(mask);
	            graphics.setActiveObject(mask);

	            return filterComp.remove(type);
	        }

	        // options changed case
	        if (this.undoData.options) {
	            return filterComp.add(type, this.undoData.options);
	        }

	        // filter added case
	        return filterComp.remove(type);
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 138 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var componentNames = _consts2.default.componentNames,
	    rejectMessages = _consts2.default.rejectMessages,
	    commandNames = _consts2.default.commandNames; /**
	                                                   * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                   * @fileoverview Change icon color
	                                                   */

	var ICON = componentNames.ICON;


	var command = {
	    name: commandNames.CHANGE_ICON_COLOR,

	    /**
	     * Change icon color
	     * @param {Graphics} graphics - Graphics instance
	     * @param {number} id - object id
	     * @param {string} color - Color for icon
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, id, color) {
	        var _this = this;

	        return new _promise2.default(function (resolve, reject) {
	            var iconComp = graphics.getComponent(ICON);
	            var targetObj = graphics.getObject(id);

	            if (!targetObj) {
	                reject(rejectMessages.noObject);
	            }

	            _this.undoData.object = targetObj;
	            _this.undoData.color = iconComp.getColor(targetObj);
	            iconComp.setColor(color, targetObj);
	            resolve();
	        });
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        var iconComp = graphics.getComponent(ICON);
	        var _undoData$object = this.undoData.object,
	            icon = _undoData$object.object,
	            color = _undoData$object.color;


	        iconComp.setColor(color, icon);

	        return _promise2.default.resolve();
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 139 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview change a shape
	 */
	var componentNames = _consts2.default.componentNames,
	    rejectMessages = _consts2.default.rejectMessages,
	    commandNames = _consts2.default.commandNames;
	var SHAPE = componentNames.SHAPE;


	var command = {
	    name: commandNames.CHANGE_SHAPE,

	    /**
	     * Change a shape
	     * @param {Graphics} graphics - Graphics instance
	     * @param {number} id - object id
	     * @param {Object} options - Shape options
	     *      @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
	     *      @param {string} [options.stroke] - Shape outline color
	     *      @param {number} [options.strokeWidth] - Shape outline width
	     *      @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
	     *      @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
	     *      @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
	     *      @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
	     *      @param {number} [options.left] - Shape x position
	     *      @param {number} [options.top] - Shape y position
	     *      @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, id, options) {
	        var _this = this;

	        var shapeComp = graphics.getComponent(SHAPE);
	        var targetObj = graphics.getObject(id);

	        if (!targetObj) {
	            return _promise2.default.reject(rejectMessages.noObject);
	        }

	        this.undoData.object = targetObj;
	        this.undoData.options = {};
	        _tuiCodeSnippet2.default.forEachOwnProperties(options, function (value, key) {
	            _this.undoData.options[key] = targetObj[key];
	        });

	        return shapeComp.change(targetObj, options);
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        var shapeComp = graphics.getComponent(SHAPE);
	        var _undoData = this.undoData,
	            shape = _undoData.object,
	            options = _undoData.options;


	        return shapeComp.change(shape, options);
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 140 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var componentNames = _consts2.default.componentNames,
	    rejectMessages = _consts2.default.rejectMessages,
	    commandNames = _consts2.default.commandNames; /**
	                                                   * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                   * @fileoverview Change a text
	                                                   */

	var TEXT = componentNames.TEXT;


	var command = {
	    name: commandNames.CHANGE_TEXT,

	    /**
	     * Change a text
	     * @param {Graphics} graphics - Graphics instance
	     * @param {number} id - object id
	     * @param {string} text - Changing text
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, id, text) {
	        var textComp = graphics.getComponent(TEXT);
	        var targetObj = graphics.getObject(id);

	        if (!targetObj) {
	            return _promise2.default.reject(rejectMessages.noObject);
	        }

	        this.undoData.object = targetObj;
	        this.undoData.text = textComp.getText(targetObj);

	        return textComp.change(targetObj, text);
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        var textComp = graphics.getComponent(TEXT);
	        var _undoData = this.undoData,
	            textObj = _undoData.object,
	            text = _undoData.text;


	        return textComp.change(textObj, text);
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 141 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Change text styles
	 */
	var componentNames = _consts2.default.componentNames,
	    rejectMessages = _consts2.default.rejectMessages,
	    commandNames = _consts2.default.commandNames;
	var TEXT = componentNames.TEXT;


	var command = {
	    name: commandNames.CHANGE_TEXT_STYLE,

	    /**
	     * Change text styles
	     * @param {Graphics} graphics - Graphics instance
	     * @param {number} id - object id
	     * @param {Object} styles - text styles
	     *     @param {string} [styles.fill] Color
	     *     @param {string} [styles.fontFamily] Font type for text
	     *     @param {number} [styles.fontSize] Size
	     *     @param {string} [styles.fontStyle] Type of inclination (normal / italic)
	     *     @param {string} [styles.fontWeight] Type of thicker or thinner looking (normal / bold)
	     *     @param {string} [styles.textAlign] Type of text align (left / center / right)
	     *     @param {string} [styles.textDecoraiton] Type of line (underline / line-throgh / overline)
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, id, styles) {
	        var _this = this;

	        var textComp = graphics.getComponent(TEXT);
	        var targetObj = graphics.getObject(id);

	        if (!targetObj) {
	            return _promise2.default.reject(rejectMessages.noObject);
	        }

	        this.undoData.object = targetObj;
	        this.undoData.styles = {};
	        _tuiCodeSnippet2.default.forEachOwnProperties(styles, function (value, key) {
	            _this.undoData.styles[key] = targetObj[key];
	        });

	        return textComp.setStyle(targetObj, styles);
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        var textComp = graphics.getComponent(TEXT);
	        var _undoData = this.undoData,
	            textObj = _undoData.object,
	            styles = _undoData.styles;


	        return textComp.setStyle(textObj, styles);
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 142 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var commandNames = _consts2.default.commandNames; /**
	                                                   * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                   * @fileoverview Clear all objects
	                                                   */

	var command = {
	    name: commandNames.CLEAR_OBJECTS,

	    /**
	     * Clear all objects without background (main) image
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    execute: function execute(graphics) {
	        var _this = this;

	        return new _promise2.default(function (resolve) {
	            _this.undoData.objects = graphics.removeAll();
	            resolve();
	        });
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     * @ignore
	     */
	    undo: function undo(graphics) {
	        graphics.add(this.undoData.objects);

	        return _promise2.default.resolve();
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 143 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Flip an image
	 */
	var componentNames = _consts2.default.componentNames,
	    commandNames = _consts2.default.commandNames;
	var FLIP = componentNames.FLIP;


	var command = {
	  name: commandNames.FLIP_IMAGE,

	  /**
	   * flip an image
	   * @param {Graphics} graphics - Graphics instance
	   * @param {string} type - 'flipX' or 'flipY' or 'reset'
	   * @returns {Promise}
	   */
	  execute: function execute(graphics, type) {
	    var flipComp = graphics.getComponent(FLIP);

	    this.undoData.setting = flipComp.getCurrentSetting();

	    return flipComp[type]();
	  },

	  /**
	   * @param {Graphics} graphics - Graphics instance
	   * @returns {Promise}
	   */
	  undo: function undo(graphics) {
	    var flipComp = graphics.getComponent(FLIP);

	    return flipComp.set(this.undoData.setting);
	  }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 144 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Load a background (main) image
	 */
	var componentNames = _consts2.default.componentNames,
	    commandNames = _consts2.default.commandNames;
	var IMAGE_LOADER = componentNames.IMAGE_LOADER;


	var command = {
	    name: commandNames.LOAD_IMAGE,

	    /**
	     * Load a background (main) image
	     * @param {Graphics} graphics - Graphics instance
	     * @param {string} imageName - Image name
	     * @param {string} imgUrl - Image Url
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, imageName, imgUrl) {
	        var loader = graphics.getComponent(IMAGE_LOADER);
	        var prevImage = loader.getCanvasImage();
	        var prevImageWidth = prevImage ? prevImage.width : 0;
	        var prevImageHeight = prevImage ? prevImage.height : 0;

	        this.undoData = {
	            name: loader.getImageName(),
	            image: prevImage,
	            objects: graphics.removeAll(true)
	        };

	        return loader.load(imageName, imgUrl).then(function (newImage) {
	            return {
	                oldWidth: prevImageWidth,
	                oldHeight: prevImageHeight,
	                newWidth: newImage.width,
	                newHeight: newImage.height
	            };
	        });
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        var loader = graphics.getComponent(IMAGE_LOADER);
	        var _undoData = this.undoData,
	            objects = _undoData.objects,
	            name = _undoData.name,
	            image = _undoData.image;


	        graphics.removeAll(true);
	        graphics.add(objects);

	        return loader.load(name, image);
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 145 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Remove a filter from an image
	 */
	var componentNames = _consts2.default.componentNames,
	    commandNames = _consts2.default.commandNames;
	var FILTER = componentNames.FILTER;


	var command = {
	  name: commandNames.REMOVE_FILTER,

	  /**
	   * Remove a filter from an image
	   * @param {Graphics} graphics - Graphics instance
	   * @param {string} type - Filter type
	   * @returns {Promise}
	   */
	  execute: function execute(graphics, type) {
	    var filterComp = graphics.getComponent(FILTER);

	    this.undoData.options = filterComp.getOptions(type);

	    return filterComp.remove(type);
	  },

	  /**
	   * @param {Graphics} graphics - Graphics instance
	   * @param {string} type - Filter type
	   * @returns {Promise}
	   */
	  undo: function undo(graphics, type) {
	    var filterComp = graphics.getComponent(FILTER);
	    var options = this.undoData.options;


	    return filterComp.add(type, options);
	  }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 146 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var commandNames = _consts2.default.commandNames,
	    rejectMessages = _consts2.default.rejectMessages; /**
	                                                       * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                       * @fileoverview Remove an object
	                                                       */

	var command = {
	    name: commandNames.REMOVE_OBJECT,

	    /**
	     * Remove an object
	     * @param {Graphics} graphics - Graphics instance
	     * @param {number} id - object id
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, id) {
	        var _this = this;

	        return new _promise2.default(function (resolve, reject) {
	            _this.undoData.objects = graphics.removeObjectById(id);
	            if (_this.undoData.objects.length) {
	                resolve();
	            } else {
	                reject(rejectMessages.noObject);
	            }
	        });
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        graphics.add(this.undoData.objects);

	        return _promise2.default.resolve();
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 147 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var commandNames = _consts2.default.commandNames; /**
	                                                   * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                   * @fileoverview Resize a canvas
	                                                   */

	var command = {
	    name: commandNames.RESIZE_CANVAS_DIMENSION,

	    /**
	     * resize the canvas with given dimension
	     * @param {Graphics} graphics - Graphics instance
	     * @param {{width: number, height: number}} dimension - Max width & height
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, dimension) {
	        var _this = this;

	        return new _promise2.default(function (resolve) {
	            _this.undoData.size = {
	                width: graphics.cssMaxWidth,
	                height: graphics.cssMaxHeight
	            };

	            graphics.setCssMaxDimension(dimension);
	            graphics.adjustCanvasDimension();
	            resolve();
	        });
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        graphics.setCssMaxDimension(this.undoData.size);
	        graphics.adjustCanvasDimension();

	        return _promise2.default.resolve();
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 148 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Rotate an image
	 */
	var componentNames = _consts2.default.componentNames,
	    commandNames = _consts2.default.commandNames;
	var ROTATION = componentNames.ROTATION;


	var command = {
	  name: commandNames.ROTATE_IMAGE,

	  /**
	   * Rotate an image
	   * @param {Graphics} graphics - Graphics instance
	   * @param {string} type - 'rotate' or 'setAngle'
	   * @param {number} angle - angle value (degree)
	   * @returns {Promise}
	   */
	  execute: function execute(graphics, type, angle) {
	    var rotationComp = graphics.getComponent(ROTATION);

	    this.undoData.angle = rotationComp.getCurrentAngle();

	    return rotationComp[type](angle);
	  },

	  /**
	   * @param {Graphics} graphics - Graphics instance
	   * @returns {Promise}
	   */
	  undo: function undo(graphics) {
	    var rotationComp = graphics.getComponent(ROTATION);
	    var angle = this.undoData.angle;


	    return rotationComp.setAngle(angle);
	  }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 149 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _tuiCodeSnippet = __webpack_require__(3);

	var _tuiCodeSnippet2 = _interopRequireDefault(_tuiCodeSnippet);

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	/**
	 * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	 * @fileoverview Set object properties
	 */
	var commandNames = _consts2.default.commandNames,
	    rejectMessages = _consts2.default.rejectMessages;


	var command = {
	    name: commandNames.SET_OBJECT_PROPERTIES,

	    /**
	     * Set object properties
	     * @param {Graphics} graphics - Graphics instance
	     * @param {number} id - object id
	     * @param {Object} props - properties
	     *     @param {string} [props.fill] Color
	     *     @param {string} [props.fontFamily] Font type for text
	     *     @param {number} [props.fontSize] Size
	     *     @param {string} [props.fontStyle] Type of inclination (normal / italic)
	     *     @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold)
	     *     @param {string} [props.textAlign] Type of text align (left / center / right)
	     *     @param {string} [props.textDecoraiton] Type of line (underline / line-throgh / overline)
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, id, props) {
	        var _this = this;

	        var targetObj = graphics.getObject(id);

	        if (!targetObj) {
	            return _promise2.default.reject(rejectMessages.noObject);
	        }

	        this.undoData.props = {};
	        _tuiCodeSnippet2.default.forEachOwnProperties(props, function (value, key) {
	            _this.undoData.props[key] = targetObj[key];
	        });

	        graphics.setObjectProperties(id, props);

	        return _promise2.default.resolve();
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @param {number} id - object id
	     * @returns {Promise}
	     */
	    undo: function undo(graphics, id) {
	        var props = this.undoData.props;


	        graphics.setObjectProperties(id, props);

	        return _promise2.default.resolve();
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ }),
/* 150 */
/***/ (function(module, exports, __webpack_require__) {

	'use strict';

	var _command = __webpack_require__(69);

	var _command2 = _interopRequireDefault(_command);

	var _promise = __webpack_require__(4);

	var _promise2 = _interopRequireDefault(_promise);

	var _consts = __webpack_require__(73);

	var _consts2 = _interopRequireDefault(_consts);

	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

	var commandNames = _consts2.default.commandNames,
	    rejectMessages = _consts2.default.rejectMessages; /**
	                                                       * @author NHN Ent. FE Development Team <dl_javascript@nhnent.com>
	                                                       * @fileoverview Set object properties
	                                                       */

	var command = {
	    name: commandNames.SET_OBJECT_POSITION,

	    /**
	     * Set object properties
	     * @param {Graphics} graphics - Graphics instance
	     * @param {number} id - object id
	     * @param {Object} posInfo - position object
	     *  @param {number} posInfo.x - x position
	     *  @param {number} posInfo.y - y position
	     *  @param {string} posInfo.originX - can be 'left', 'center', 'right'
	     *  @param {string} posInfo.originY - can be 'top', 'center', 'bottom'
	     * @returns {Promise}
	     */
	    execute: function execute(graphics, id, posInfo) {
	        var targetObj = graphics.getObject(id);

	        if (!targetObj) {
	            return _promise2.default.reject(rejectMessages.noObject);
	        }

	        this.undoData.objectId = id;
	        this.undoData.props = graphics.getObjectProperties(id, ['left', 'top']);

	        graphics.setObjectPosition(id, posInfo);
	        graphics.renderAll();

	        return _promise2.default.resolve();
	    },

	    /**
	     * @param {Graphics} graphics - Graphics instance
	     * @returns {Promise}
	     */
	    undo: function undo(graphics) {
	        var _undoData = this.undoData,
	            objectId = _undoData.objectId,
	            props = _undoData.props;


	        graphics.setObjectProperties(objectId, props);
	        graphics.renderAll();

	        return _promise2.default.resolve();
	    }
	};

	_command2.default.register(command);

	module.exports = command;

/***/ })
/******/ ])
});
;
