You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2017/03/20 03:07:47 UTC
svn commit: r1787683 [2/3] - in
/openmeetings/application/trunk/openmeetings-web: ./
src/main/java/org/apache/openmeetings/web/common/tree/
src/main/java/org/apache/openmeetings/web/room/
src/main/java/org/apache/openmeetings/web/room/sidebar/ src/main...
Added: openmeetings/application/trunk/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js?rev=1787683&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js (added)
+++ openmeetings/application/trunk/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/fabric.js Mon Mar 20 03:07:47 2017
@@ -0,0 +1,26638 @@
+/* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */
+ /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
+/* Licensed MIT https://github.com/kangax/fabric.js/blob/master/LICENSE */
+var fabric = fabric || { version: "1.7.8" };
+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(
+ decodeURIComponent("%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E")
+ );
+
+ 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 = { };
+fabric.iMatrix = [1, 0, 0, 1, 0, 0];
+
+/**
+ * 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);
+ }
+};
+
+
+/**
+ * @namespace fabric.CommonMethods
+ */
+fabric.CommonMethods = {
+
+ /**
+ * Sets object's properties from options
+ * @param {Object} [options] Options object
+ */
+ _setOptions: function(options) {
+ for (var prop in options) {
+ this.set(prop, options[prop]);
+ }
+ },
+
+ /**
+ * @private
+ * @param {Object} [filler] Options object
+ * @param {String} [property] property to set the Gradient to
+ */
+ _initGradient: function(filler, property) {
+ if (filler && filler.colorStops && !(filler instanceof fabric.Gradient)) {
+ this.set(property, new fabric.Gradient(filler));
+ }
+ },
+
+ /**
+ * @private
+ * @param {Object} [filler] Options object
+ * @param {String} [property] property to set the Pattern to
+ * @param {Function} [callback] callback to invoke after pattern load
+ */
+ _initPattern: function(filler, property, callback) {
+ if (filler && filler.source && !(filler instanceof fabric.Pattern)) {
+ this.set(property, new fabric.Pattern(filler, callback));
+ }
+ else {
+ callback && callback();
+ }
+ },
+
+ /**
+ * @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);
+ }
+ },
+
+ /**
+ * @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;
+ },
+
+ _set: function(key, value) {
+ this[key] = value;
+ },
+
+ /**
+ * 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;
+ },
+
+ /**
+ * Basic getter
+ * @param {String} property Property name
+ * @return {*} value of a property
+ */
+ get: function(property) {
+ return this[property];
+ }
+};
+
+
+(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,
+ forceAsync = true;
+
+ 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);
+ klass.fromObject(o, function (obj, error) {
+ error || (enlivenedObjects[index] = obj);
+ reviver && reviver(o, obj, error);
+ onLoaded();
+ }, forceAsync);
+ });
+ },
+
+ /**
+ * Create and wait for loading of patterns
+ * @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.
+ */
+ enlivenPatterns: function(patterns, callback) {
+ patterns = patterns || [];
+
+ function onLoaded() {
+ if (++numLoadedPatterns === numPatterns) {
+ callback && callback(enlivenedPatterns);
+ }
+ }
+
+ var enlivenedPatterns = [],
+ numLoadedPatterns = 0,
+ numPatterns = patterns.length;
+
+ if (!numPatterns) {
+ callback && callback(enlivenedPatterns);
+ return;
+ }
+
+ patterns.forEach(function (p, index) {
+ if (p && p.source) {
+ new fabric.Pattern(p, function(pattern) {
+ enlivenedPatterns[index] = pattern;
+ onLoaded();
+ });
+ }
+ else {
+ enlivenedPatterns[index] = p;
+ onLoaded();
+ }
+ });
+ },
+
+ /**
+ * Groups SVG elements (usually those retrieved from SVG document)
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} elements SVG elements to group
+ * @param {Object} [options] Options object
+ * @param {String} path Value to set sourcePath to
+ * @return {fabric.Object|fabric.PathGroup}
+ */
+ groupSVGElements: function(elements, options, path) {
+ var object;
+
+ object = new fabric.PathGroup(elements, options);
+
+ if (typeof path !== 'undefined') {
+ object.setSourcePath(path);
+ }
+ return object;
+ },
+
+ /**
+ * Populates an object with properties of another object
+ * @static
+ * @memberOf fabric.util
+ * @param {Object} source Source object
+ * @param {Object} destination Destination object
+ * @return {Array} properties Propertie names to include
+ */
+ populateWithProperties: function(source, destination, properties) {
+ if (properties && Object.prototype.toString.call(properties) === '[object Array]') {
+ for (var i = 0, len = properties.length; i < len; i++) {
+ if (properties[i] in source) {
+ destination[properties[i]] = source[properties[i]];
+ }
+ }
+ }
+ },
+
+ /**
+ * Draws a dashed line between two points
+ *
+ * This method is used to draw dashed line around selection area.
+ * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
+ *
+ * @param {CanvasRenderingContext2D} ctx context
+ * @param {Number} x start x coordinate
+ * @param {Number} y start y coordinate
+ * @param {Number} x2 end x coordinate
+ * @param {Number} y2 end y coordinate
+ * @param {Array} da dash array pattern
+ */
+ drawDashedLine: function(ctx, x, y, x2, y2, da) {
+ var dx = x2 - x,
+ dy = y2 - y,
+ len = sqrt(dx * dx + dy * dy),
+ rot = atan2(dy, dx),
+ dc = da.length,
+ di = 0,
+ draw = true;
+
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.moveTo(0, 0);
+ ctx.rotate(rot);
+
+ x = 0;
+ while (len > x) {
+ x += da[di++ % dc];
+ if (x > len) {
+ x = len;
+ }
+ ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
+ draw = !draw;
+ }
+
+ ctx.restore();
+ },
+
+ /**
+ * Creates canvas element and initializes it via excanvas if necessary
+ * @static
+ * @memberOf fabric.util
+ * @param {CanvasElement} [canvasEl] optional canvas element to initialize;
+ * when not given, element is created implicitly
+ * @return {CanvasElement} initialized canvas element
+ */
+ createCanvasElement: function(canvasEl) {
+ canvasEl || (canvasEl = fabric.document.createElement('canvas'));
+ /* eslint-disable camelcase */
+ if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
+ G_vmlCanvasManager.initElement(canvasEl);
+ }
+ /* eslint-enable camelcase */
+ return canvasEl;
+ },
+
+ /**
+ * Creates image element (works on client and node)
+ * @static
+ * @memberOf fabric.util
+ * @return {HTMLImageElement} HTML image element
+ */
+ createImage: function() {
+ return fabric.isLikelyNode
+ ? new (require('canvas').Image)()
+ : fabric.document.createElement('img');
+ },
+
+ /**
+ * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array
+ * @static
+ * @memberOf fabric.util
+ * @param {Object} klass "Class" to create accessors for
+ */
+ createAccessors: function(klass) {
+ var proto = klass.prototype, i, propName,
+ capitalizedPropName, setterName, getterName;
+
+ for (i = proto.stateProperties.length; i--; ) {
+
+ propName = proto.stateProperties[i];
+ capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
+ setterName = 'set' + capitalizedPropName;
+ getterName = 'get' + capitalizedPropName;
+
+ // using `new Function` for better introspection
+ if (!proto[getterName]) {
+ proto[getterName] = (function(property) {
+ return new Function('return this.get("' + property + '")');
+ })(propName);
+ }
+ if (!proto[setterName]) {
+ proto[setterName] = (function(property) {
+ return new Function('value', 'return this.set("' + property + '", value)');
+ })(propName);
+ }
+ }
+ },
+
+ /**
+ * @static
+ * @memberOf fabric.util
+ * @param {fabric.Object} receiver Object implementing `clipTo` method
+ * @param {CanvasRenderingContext2D} ctx Context to clip
+ */
+ clipContext: function(receiver, ctx) {
+ ctx.save();
+ ctx.beginPath();
+ receiver.clipTo(ctx);
+ ctx.clip();
+ },
+
+ /**
+ * Multiply matrix A by matrix B to nest transformations
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} a First transformMatrix
+ * @param {Array} b Second transformMatrix
+ * @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices
+ * @return {Array} The product of the two transform matrices
+ */
+ multiplyTransformMatrices: function(a, b, is2x2) {
+ // Matrix multiply a * b
+ return [
+ a[0] * b[0] + a[2] * b[1],
+ a[1] * b[0] + a[3] * b[1],
+ a[0] * b[2] + a[2] * b[3],
+ a[1] * b[2] + a[3] * b[3],
+ is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4],
+ is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5]
+ ];
+ },
+
+ /**
+ * Decomposes standard 2x2 matrix into transform componentes
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} a transformMatrix
+ * @return {Object} Components of transform
+ */
+ qrDecompose: function(a) {
+ var angle = atan2(a[1], a[0]),
+ denom = pow(a[0], 2) + pow(a[1], 2),
+ scaleX = sqrt(denom),
+ scaleY = (a[0] * a[3] - a[2] * a [1]) / scaleX,
+ skewX = atan2(a[0] * a[2] + a[1] * a [3], denom);
+ return {
+ angle: angle / PiBy180,
+ scaleX: scaleX,
+ scaleY: scaleY,
+ skewX: skewX / PiBy180,
+ skewY: 0,
+ translateX: a[4],
+ translateY: a[5]
+ };
+ },
+
+ customTransformMatrix: function(scaleX, scaleY, skewX) {
+ var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1],
+ scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)];
+ return fabric.util.multiplyTransformMatrices(scaleMatrix, skewMatrixX, true);
+ },
+
+ resetObjectTransform: function (target) {
+ target.scaleX = 1;
+ target.scaleY = 1;
+ target.skewX = 0;
+ target.skewY = 0;
+ target.flipX = false;
+ target.flipY = false;
+ target.setAngle(0);
+ },
+
+ /**
+ * Returns string representation of function body
+ * @param {Function} fn Function to get body of
+ * @return {String} Function body
+ */
+ getFunctionBody: function(fn) {
+ return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];
+ },
+
+ /**
+ * Returns true if context has transparent pixel
+ * at specified location (taking tolerance into account)
+ * @param {CanvasRenderingContext2D} ctx context
+ * @param {Number} x x coordinate
+ * @param {Number} y y coordinate
+ * @param {Number} tolerance Tolerance
+ */
+ isTransparent: function(ctx, x, y, tolerance) {
+
+ // If tolerance is > 0 adjust start coords to take into account.
+ // If moves off Canvas fix to 0
+ if (tolerance > 0) {
+ if (x > tolerance) {
+ x -= tolerance;
+ }
+ else {
+ x = 0;
+ }
+ if (y > tolerance) {
+ y -= tolerance;
+ }
+ else {
+ y = 0;
+ }
+ }
+
+ var _isTransparent = true, i, temp,
+ imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1),
+ l = imageData.data.length;
+
+ // Split image data - for tolerance > 1, pixelDataSize = 4;
+ for (i = 3; i < l; i += 4) {
+ temp = imageData.data[i];
+ _isTransparent = temp <= 0;
+ if (_isTransparent === false) {
+ break; // Stop if colour found
+ }
+ }
+
+ imageData = null;
+
+ return _isTransparent;
+ },
+
+ /**
+ * Parse preserveAspectRatio attribute from element
+ * @param {string} attribute to be parsed
+ * @return {Object} an object containing align and meetOrSlice attribute
+ */
+ parsePreserveAspectRatioAttribute: function(attribute) {
+ var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid',
+ aspectRatioAttrs = attribute.split(' '), align;
+
+ if (aspectRatioAttrs && aspectRatioAttrs.length) {
+ meetOrSlice = aspectRatioAttrs.pop();
+ if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') {
+ align = meetOrSlice;
+ meetOrSlice = 'meet';
+ }
+ else if (aspectRatioAttrs.length) {
+ align = aspectRatioAttrs.pop();
+ }
+ }
+ //divide align in alignX and alignY
+ alignX = align !== 'none' ? align.slice(1, 4) : 'none';
+ alignY = align !== 'none' ? align.slice(5, 8) : 'none';
+ return {
+ meetOrSlice: meetOrSlice,
+ alignX: alignX,
+ alignY: alignY
+ };
+ },
+
+ /**
+ * Clear char widths cache for a font family.
+ * @memberOf fabric.util
+ * @param {String} [fontFamily] font family to clear
+ */
+ clearFabricFontCache: function(fontFamily) {
+ if (!fontFamily) {
+ fabric.charWidthsCache = { };
+ }
+ else if (fabric.charWidthsCache[fontFamily]) {
+ delete fabric.charWidthsCache[fontFamily];
+ }
+ }
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function() {
+
+ var arcToSegmentsCache = { },
+ segmentToBezierCache = { },
+ boundsOfCurveCache = { },
+ _join = Array.prototype.join;
+
+ /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp
+ * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here
+ * http://mozilla.org/MPL/2.0/
+ */
+ function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
+ var argsString = _join.call(arguments);
+ if (arcToSegmentsCache[argsString]) {
+ return arcToSegmentsCache[argsString];
+ }
+
+ var PI = Math.PI, th = rotateX * PI / 180,
+ sinTh = Math.sin(th),
+ cosTh = Math.cos(th),
+ fromX = 0, fromY = 0;
+
+ rx = Math.abs(rx);
+ ry = Math.abs(ry);
+
+ var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5,
+ py = -cosTh * toY * 0.5 + sinTh * toX * 0.5,
+ rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,
+ pl = rx2 * ry2 - rx2 * py2 - ry2 * px2,
+ root = 0;
+
+ if (pl < 0) {
+ var s = Math.sqrt(1 - pl / (rx2 * ry2));
+ rx *= s;
+ ry *= s;
+ }
+ else {
+ root = (large === sweep ? -1.0 : 1.0) *
+ Math.sqrt( pl / (rx2 * py2 + ry2 * px2));
+ }
+
+ var cx = root * rx * py / ry,
+ cy = -root * ry * px / rx,
+ cx1 = cosTh * cx - sinTh * cy + toX * 0.5,
+ cy1 = sinTh * cx + cosTh * cy + toY * 0.5,
+ mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),
+ dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);
+
+ if (sweep === 0 && dtheta > 0) {
+ dtheta -= 2 * PI;
+ }
+ else if (sweep === 1 && dtheta < 0) {
+ dtheta += 2 * PI;
+ }
+
+ // Convert into cubic bezier segments <= 90deg
+ var segments = Math.ceil(Math.abs(dtheta / PI * 2)),
+ result = [], mDelta = dtheta / segments,
+ mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),
+ th3 = mTheta + mDelta;
+
+ for (var i = 0; i < segments; i++) {
+ result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);
+ fromX = result[i][4];
+ fromY = result[i][5];
+ mTheta = th3;
+ th3 += mDelta;
+ }
+ arcToSegmentsCache[argsString] = result;
+ return result;
+ }
+
+ function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
+ var argsString2 = _join.call(arguments);
+ if (segmentToBezierCache[argsString2]) {
+ return segmentToBezierCache[argsString2];
+ }
+
+ var costh2 = Math.cos(th2),
+ sinth2 = Math.sin(th2),
+ costh3 = Math.cos(th3),
+ sinth3 = Math.sin(th3),
+ toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
+ toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
+ cp1X = fromX + mT * ( -cosTh * rx * sinth2 - sinTh * ry * costh2),
+ cp1Y = fromY + mT * ( -sinTh * rx * sinth2 + cosTh * ry * costh2),
+ cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3),
+ cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3);
+
+ segmentToBezierCache[argsString2] = [
+ cp1X, cp1Y,
+ cp2X, cp2Y,
+ toX, toY
+ ];
+ return segmentToBezierCache[argsString2];
+ }
+
+ /*
+ * Private
+ */
+ function calcVectorAngle(ux, uy, vx, vy) {
+ var ta = Math.atan2(uy, ux),
+ tb = Math.atan2(vy, vx);
+ if (tb >= ta) {
+ return tb - ta;
+ }
+ else {
+ return 2 * Math.PI - (ta - tb);
+ }
+ }
+
+ /**
+ * Draws arc
+ * @param {CanvasRenderingContext2D} ctx
+ * @param {Number} fx
+ * @param {Number} fy
+ * @param {Array} coords
+ */
+ fabric.util.drawArc = function(ctx, fx, fy, coords) {
+ var rx = coords[0],
+ ry = coords[1],
+ rot = coords[2],
+ large = coords[3],
+ sweep = coords[4],
+ tx = coords[5],
+ ty = coords[6],
+ segs = [[], [], [], []],
+ segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
+
+ for (var i = 0, len = segsNorm.length; i < len; i++) {
+ segs[i][0] = segsNorm[i][0] + fx;
+ segs[i][1] = segsNorm[i][1] + fy;
+ segs[i][2] = segsNorm[i][2] + fx;
+ segs[i][3] = segsNorm[i][3] + fy;
+ segs[i][4] = segsNorm[i][4] + fx;
+ segs[i][5] = segsNorm[i][5] + fy;
+ ctx.bezierCurveTo.apply(ctx, segs[i]);
+ }
+ };
+
+ /**
+ * Calculate bounding box of a elliptic-arc
+ * @param {Number} fx start point of arc
+ * @param {Number} fy
+ * @param {Number} rx horizontal radius
+ * @param {Number} ry vertical radius
+ * @param {Number} rot angle of horizontal axe
+ * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points
+ * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction
+ * @param {Number} tx end point of arc
+ * @param {Number} ty
+ */
+ fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) {
+
+ var fromX = 0, fromY = 0, bound, bounds = [],
+ segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
+
+ for (var i = 0, len = segs.length; i < len; i++) {
+ bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]);
+ bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy });
+ bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy });
+ fromX = segs[i][4];
+ fromY = segs[i][5];
+ }
+ return bounds;
+ };
+
+ /**
+ * Calculate bounding box of a beziercurve
+ * @param {Number} x0 starting point
+ * @param {Number} y0
+ * @param {Number} x1 first control point
+ * @param {Number} y1
+ * @param {Number} x2 secondo control point
+ * @param {Number} y2
+ * @param {Number} x3 end of beizer
+ * @param {Number} y3
+ */
+ // taken from http://jsbin.com/ivomiq/56/edit no credits available for that.
+ function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {
+ var argsString = _join.call(arguments);
+ if (boundsOfCurveCache[argsString]) {
+ return boundsOfCurveCache[argsString];
+ }
+
+ var sqrt = Math.sqrt,
+ min = Math.min, max = Math.max,
+ abs = Math.abs, tvalues = [],
+ bounds = [[], []],
+ a, b, c, t, t1, t2, b2ac, sqrtb2ac;
+
+ b = 6 * x0 - 12 * x1 + 6 * x2;
+ a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
+ c = 3 * x1 - 3 * x0;
+
+ for (var i = 0; i < 2; ++i) {
+ if (i > 0) {
+ b = 6 * y0 - 12 * y1 + 6 * y2;
+ a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
+ c = 3 * y1 - 3 * y0;
+ }
+
+ if (abs(a) < 1e-12) {
+ if (abs(b) < 1e-12) {
+ continue;
+ }
+ t = -c / b;
+ if (0 < t && t < 1) {
+ tvalues.push(t);
+ }
+ continue;
+ }
+ b2ac = b * b - 4 * c * a;
+ if (b2ac < 0) {
+ continue;
+ }
+ sqrtb2ac = sqrt(b2ac);
+ t1 = (-b + sqrtb2ac) / (2 * a);
+ if (0 < t1 && t1 < 1) {
+ tvalues.push(t1);
+ }
+ t2 = (-b - sqrtb2ac) / (2 * a);
+ if (0 < t2 && t2 < 1) {
+ tvalues.push(t2);
+ }
+ }
+
+ var x, y, j = tvalues.length, jlen = j, mt;
+ while (j--) {
+ t = tvalues[j];
+ mt = 1 - t;
+ x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
+ bounds[0][j] = x;
+
+ y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
+ bounds[1][j] = y;
+ }
+
+ bounds[0][jlen] = x0;
+ bounds[1][jlen] = y0;
+ bounds[0][jlen + 1] = x3;
+ bounds[1][jlen + 1] = y3;
+ var result = [
+ {
+ x: min.apply(null, bounds[0]),
+ y: min.apply(null, bounds[1])
+ },
+ {
+ x: max.apply(null, bounds[0]),
+ y: max.apply(null, bounds[1])
+ }
+ ];
+ boundsOfCurveCache[argsString] = result;
+ return result;
+ }
+
+ fabric.util.getBoundsOfCurve = getBoundsOfCurve;
+
+})();
+
+
+(function() {
+
+ var slice = Array.prototype.slice;
+
+ /* _ES5_COMPAT_START_ */
+
+ if (!Array.prototype.indexOf) {
+ /**
+ * Finds index of an element in an array
+ * @param {*} searchElement
+ * @return {Number}
+ */
+ Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
+ if (this === void 0 || this === null) {
+ throw new TypeError();
+ }
+ var t = Object(this), len = t.length >>> 0;
+ if (len === 0) {
+ return -1;
+ }
+ var n = 0;
+ if (arguments.length > 0) {
+ n = Number(arguments[1]);
+ if (n !== n) { // shortcut for verifying if it's NaN
+ n = 0;
+ }
+ else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) {
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ }
+ }
+ if (n >= len) {
+ return -1;
+ }
+ var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
+ for (; k < len; k++) {
+ if (k in t && t[k] === searchElement) {
+ return k;
+ }
+ }
+ return -1;
+ };
+ }
+
+ if (!Array.prototype.forEach) {
+ /**
+ * Iterates an array, invoking callback for each element
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Array}
+ */
+ Array.prototype.forEach = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ fn.call(context, this[i], i, this);
+ }
+ }
+ };
+ }
+
+ if (!Array.prototype.map) {
+ /**
+ * Returns a result of iterating over an array, invoking callback for each element
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Array}
+ */
+ Array.prototype.map = function(fn, context) {
+ var result = [];
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ result[i] = fn.call(context, this[i], i, this);
+ }
+ }
+ return result;
+ };
+ }
+
+ if (!Array.prototype.every) {
+ /**
+ * Returns true if a callback returns truthy value for all elements in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Boolean}
+ */
+ Array.prototype.every = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this && !fn.call(context, this[i], i, this)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ }
+
+ if (!Array.prototype.some) {
+ /**
+ * Returns true if a callback returns truthy value for at least one element in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Boolean}
+ */
+ Array.prototype.some = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this && fn.call(context, this[i], i, this)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
+
+ if (!Array.prototype.filter) {
+ /**
+ * Returns the result of iterating over elements in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Array}
+ */
+ Array.prototype.filter = function(fn, context) {
+ var result = [], val;
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ val = this[i]; // in case fn mutates this
+ if (fn.call(context, val, i, this)) {
+ result.push(val);
+ }
+ }
+ }
+ return result;
+ };
+ }
+
+ if (!Array.prototype.reduce) {
+ /**
+ * Returns "folded" (reduced) result of iterating over elements in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @return {*}
+ */
+ Array.prototype.reduce = function(fn /*, initial*/) {
+ var len = this.length >>> 0,
+ i = 0,
+ rv;
+
+ if (arguments.length > 1) {
+ rv = arguments[1];
+ }
+ else {
+ do {
+ if (i in this) {
+ rv = this[i++];
+ break;
+ }
+ // if array contains no values, no initial value to return
+ if (++i >= len) {
+ throw new TypeError();
+ }
+ }
+ while (true);
+ }
+ for (; i < len; i++) {
+ if (i in this) {
+ rv = fn.call(null, rv, this[i], i, this);
+ }
+ }
+ return rv;
+ };
+ }
+
+ /* _ES5_COMPAT_END_ */
+
+ /**
+ * Invokes method on all items in a given array
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} method Name of a method to invoke
+ * @return {Array}
+ */
+ function invoke(array, method) {
+ var args = slice.call(arguments, 2), result = [];
+ for (var i = 0, len = array.length; i < len; i++) {
+ result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
+ }
+ return result;
+ }
+
+ /**
+ * Finds maximum value in array (not necessarily "first" one)
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} byProperty
+ * @return {*}
+ */
+ function max(array, byProperty) {
+ return find(array, byProperty, function(value1, value2) {
+ return value1 >= value2;
+ });
+ }
+
+ /**
+ * Finds minimum value in array (not necessarily "first" one)
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} byProperty
+ * @return {*}
+ */
+ function min(array, byProperty) {
+ return find(array, byProperty, function(value1, value2) {
+ return value1 < value2;
+ });
+ }
+
+ /**
+ * @private
+ */
+ function fill(array, value) {
+ var k = array.length;
+ while (k--) {
+ array[k] = value;
+ }
+ return array;
+ }
+
+ /**
+ * @private
+ */
+ function find(array, byProperty, condition) {
+ if (!array || array.length === 0) {
+ return;
+ }
+
+ var i = array.length - 1,
+ result = byProperty ? array[i][byProperty] : array[i];
+ if (byProperty) {
+ while (i--) {
+ if (condition(array[i][byProperty], result)) {
+ result = array[i][byProperty];
+ }
+ }
+ }
+ else {
+ while (i--) {
+ if (condition(array[i], result)) {
+ result = array[i];
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @namespace fabric.util.array
+ */
+ fabric.util.array = {
+ fill: fill,
+ invoke: invoke,
+ min: min,
+ max: max
+ };
+
+})();
+
+
+(function() {
+ /**
+ * Copies all enumerable properties of one js object to another
+ * Does not clone or extend fabric.Object subclasses.
+ * @memberOf fabric.util.object
+ * @param {Object} destination Where to copy to
+ * @param {Object} source Where to copy from
+ * @return {Object}
+ */
+
+ function extend(destination, source, deep) {
+ // JScript DontEnum bug is not taken care of
+ // the deep clone is for internal use, is not meant to avoid
+ // javascript traps or cloning html element or self referenced objects.
+ if (deep) {
+ if (!fabric.isLikelyNode && source instanceof Element) {
+ // avoid cloning deep images, canvases,
+ destination = source;
+ }
+ else if (source instanceof Array) {
+ destination = [];
+ for (var i = 0, len = source.length; i < len; i++) {
+ destination[i] = extend({ }, source[i], deep);
+ }
+ }
+ else if (source && typeof source === 'object') {
+ for (var property in source) {
+ if (source.hasOwnProperty(property)) {
+ destination[property] = extend({ }, source[property], deep);
+ }
+ }
+ }
+ else {
+ // this sounds odd for an extend but is ok for recursive use
+ destination = source;
+ }
+ }
+ else {
+ for (var property in source) {
+ destination[property] = source[property];
+ }
+ }
+ return destination;
+ }
+
+ /**
+ * Creates an empty object and copies all enumerable properties of another object to it
+ * @memberOf fabric.util.object
+ * @param {Object} object Object to clone
+ * @return {Object}
+ */
+ function clone(object, deep) {
+ return extend({ }, object, deep);
+ }
+
+ /** @namespace fabric.util.object */
+ fabric.util.object = {
+ extend: extend,
+ clone: clone
+ };
+
+})();
+
+
+(function() {
+
+ /* _ES5_COMPAT_START_ */
+ if (!String.prototype.trim) {
+ /**
+ * Trims a string (removing whitespace from the beginning and the end)
+ * @function external:String#trim
+ * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a>
+ */
+ String.prototype.trim = function () {
+ // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
+ return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
+ };
+ }
+ /* _ES5_COMPAT_END_ */
+
+ /**
+ * Camelizes a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to camelize
+ * @return {String} Camelized version of a string
+ */
+ function camelize(string) {
+ return string.replace(/-+(.)?/g, function(match, character) {
+ return character ? character.toUpperCase() : '';
+ });
+ }
+
+ /**
+ * Capitalizes a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to capitalize
+ * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized
+ * and other letters stay untouched, if false first letter is capitalized
+ * and other letters are converted to lowercase.
+ * @return {String} Capitalized version of a string
+ */
+ function capitalize(string, firstLetterOnly) {
+ return string.charAt(0).toUpperCase() +
+ (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
+ }
+
+ /**
+ * Escapes XML in a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to escape
+ * @return {String} Escaped version of a string
+ */
+ function escapeXml(string) {
+ return string.replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+ }
+
+ /**
+ * 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, options) {
+ // since ie10 or ie9 can use addEventListener but they do not support options, i need to check
+ element.addEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler, options) {
+ element.removeEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options);
+ };
+ }
+
+ 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() {
+ // Calculate an in-between color. Returns a "rgba()" string.
+ // Credit: Edwin Martin <ed...@bitstorm.org>
+ // http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js
+ function calculateColor(begin, end, pos) {
+ var color = 'rgba('
+ + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ','
+ + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ','
+ + parseInt((begin[2] + pos * (end[2] - begin[2])), 10);
+
+ color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1);
+ color += ')';
+ return color;
+ }
+
+ /**
+ * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed.
+ * @memberOf fabric.util
+ * @param {String} fromColor The starting color in hex or rgb(a) format.
+ * @param {String} toColor The starting color in hex or rgb(a) format.
+ * @param {Number} [duration] Duration of change (in ms).
+ * @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 {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used.
+ */
+ function animateColor(fromColor, toColor, duration, options) {
+ var startColor = new fabric.Color(fromColor).getSource(),
+ endColor = new fabric.Color(toColor).getSource();
+
+ options = options || {};
+
+ fabric.util.animate(fabric.util.object.extend(options, {
+ duration: duration || 500,
+ startValue: startColor,
+ endValue: endColor,
+ byValue: endColor,
+ easing: function (currentTime, startValue, byValue, duration) {
+ var posValue = options.colorEasing
+ ? options.colorEasing(currentTime, duration)
+ : 1 - Math.cos(currentTime / duration * (Math.PI / 2));
+ return calculateColor(startValue, byValue, posValue);
+ }
+ }));
+ }
+
+ fabric.util.animateColor = animateColor;
+
+})();
+
+
+(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) {
[... 23684 lines stripped ...]