From 3c60ebcc97753f27297027aea870719ffd910973 Mon Sep 17 00:00:00 2001
From: Pierre Bondoerffer
Date: Fri, 17 Mar 2017 18:02:48 +0100
Subject: [PATCH 1/8] Update Spanish
---
customize.dist/translations/messages.es.js | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js
index 7ca34e7c7..7b572ab1f 100644
--- a/customize.dist/translations/messages.es.js
+++ b/customize.dist/translations/messages.es.js
@@ -239,7 +239,6 @@ define(function () {
out.login_unhandledError = "Un error inesperado se produjo :(";
out.register_importRecent = "Importar historial (recomendado)";
out.register_acceptTerms = "Accepto los términos de servicio";
- out.register_rememberPassword = "Me acordaré de mi cuenta y contraseña";
out.register_passwordsDontMatch = "Las contraseñas no corresponden";
out.register_mustAcceptTerms = "Tienes que acceptar los términos de servicio";
out.register_mustRememberPass = "No podemos reiniciar tu contraseña si la olvidas. ¡Es muy importante que la recuerdes! Marca la casilla para confirmarlo.";
@@ -347,5 +346,14 @@ define(function () {
out.feedback_privacy = "Nos importa tu privacidad, y al mismo tiempo queremos que CryptPad sea muy fácil de usar. Utilizamos esta página para conocer las funcionalidades que importan a nuestros usuarios, pidiendolo con un parametro que nos dice que accion fue realizada.";
out.feedback_optout = "Si quieres darte de baja, visita tus preferencias, donde podrás activar o desactivar feedback";
-return out;
+ out.fm_searchName = "Buscar";
+ out.fm_searchPlaceholder = "Buscar...";
+ out.fm_newButtonTitle = "Crear un nuevo pad o carpeta";
+ out.fm_openParent = "Mostrar en carpeta"
+ out.register_writtenPassword = "He escrito mi usuario y contraseña, continuar";
+ out.register_cancel = "Volver";
+ out.register_warning = "Zero Knowledge significa que no podemos recuperar tus datos si pierdes tu contraseña.";
+ out.register_alreadyRegistered = "Este usuario ya existe, ¿iniciar sesión?";
+
+ return out;
});
From 0efb3c993cb217d3f3c117f3d33747436fda0589 Mon Sep 17 00:00:00 2001
From: Pierre Bondoerffer
Date: Fri, 17 Mar 2017 18:10:37 +0100
Subject: [PATCH 2/8] fixed lint :(
---
customize.dist/translations/messages.es.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/customize.dist/translations/messages.es.js b/customize.dist/translations/messages.es.js
index 7b572ab1f..5e2c546cc 100644
--- a/customize.dist/translations/messages.es.js
+++ b/customize.dist/translations/messages.es.js
@@ -349,7 +349,7 @@ define(function () {
out.fm_searchName = "Buscar";
out.fm_searchPlaceholder = "Buscar...";
out.fm_newButtonTitle = "Crear un nuevo pad o carpeta";
- out.fm_openParent = "Mostrar en carpeta"
+ out.fm_openParent = "Mostrar en carpeta";
out.register_writtenPassword = "He escrito mi usuario y contraseña, continuar";
out.register_cancel = "Volver";
out.register_warning = "Zero Knowledge significa que no podemos recuperar tus datos si pierdes tu contraseña.";
From 4a173e30dcfef3bd393946f7a64e3c8b262658b0 Mon Sep 17 00:00:00 2001
From: ansuz
Date: Mon, 20 Mar 2017 17:51:56 +0100
Subject: [PATCH 3/8] set user's cursor to either the beginning or end of the
document
---
www/common/cursor.js | 54 +++++++++++++++++++++++++++++++++++++++++++-
www/pad/main.js | 10 +++++++-
2 files changed, 62 insertions(+), 2 deletions(-)
diff --git a/www/common/cursor.js b/www/common/cursor.js
index 7614e44c4..07e705244 100644
--- a/www/common/cursor.js
+++ b/www/common/cursor.js
@@ -11,7 +11,7 @@ define([
var verbose = function (x) { if (window.verboseMode) { console.log(x); } };
/* accepts the document used by the editor */
- return function (inner) {
+ var Cursor = function (inner) {
var cursor = {};
// there ought to only be one cursor at a time, so let's just
@@ -387,6 +387,58 @@ define([
}
};
+ cursor.lastTextNode = function () {
+ var lastEl = Tree.rightmostNode(inner);
+ if (lastEl && lastEl.nodeType === 3) { return lastEl; }
+
+ var firstEl = Tree.leftmostNode(inner);
+
+ while (lastEl !== firstEl) {
+ lastEl = Tree.previousNode(lastEl, inner);
+ if (lastEl && lastEl.nodeType === 3) { return lastEl; }
+ }
+
+ return lastEl;
+ };
+
+ cursor.firstTextNode = function () {
+ var firstEl = Tree.leftmostNode(inner);
+ if (firstEl && firstEl.nodeType === 3) { return firstEl; }
+
+ var lastEl = Tree.rightmostNode(inner);
+
+ while (firstEl !== lastEl) {
+ firstEl = Tree.nextNode(firstEl, inner);
+ if (firstEl && firstEl.nodeType === 3) { return firstEl; }
+ }
+ return firstEl;
+ };
+
+ cursor.setToStart = function () {
+ var el = cursor.firstTextNode();
+ if (!el) { return; }
+ fixStart(el, 0);
+ fixEnd(el, 0);
+ fixSelection(makeSelection(), makeRange());
+ return el;
+ };
+
+ cursor.setToEnd = function () {
+ var el = cursor.lastTextNode();
+ if (!el) { return; }
+
+ var offset = el.textContent.length;
+
+ fixStart(el, offset);
+ fixEnd(el, offset);
+ fixSelection(makeSelection(), makeRange());
+ return el;
+ };
+
return cursor;
};
+
+ Cursor.Tree = Tree;
+
+ return Cursor;
});
diff --git a/www/pad/main.js b/www/pad/main.js
index df854fd04..9d997aa7d 100644
--- a/www/pad/main.js
+++ b/www/pad/main.js
@@ -64,6 +64,7 @@ define([
logFights: true,
fights: [],
Cryptpad: Cryptpad,
+ Cursor: Cursor,
};
var emitResize = module.emitResize = function () {
@@ -131,7 +132,7 @@ define([
color: '#fff',
});
- var cursor = window.cursor = Cursor(inner);
+ var cursor = module.cursor = Cursor(inner);
var setEditable = module.setEditable = function (bool) {
if (bool) {
@@ -718,6 +719,13 @@ define([
realtimeOptions.onLocal();
module.$userNameButton.click();
}
+
+ editor.focus();
+ if (newPad) {
+ cursor.setToEnd();
+ } else {
+ cursor.setToFix();
+ }
});
};
From eeecb1baab2f380e29d9f7bc1b077fb763b57507 Mon Sep 17 00:00:00 2001
From: ansuz
Date: Mon, 20 Mar 2017 18:01:13 +0100
Subject: [PATCH 4/8] Make Canvas Work Again
---
.jshintignore | 1 +
www/examples/canvas/fabric.js | 26125 ++++++++++++++++++++++++++++++++
www/examples/canvas/main.js | 41 +-
3 files changed, 26153 insertions(+), 14 deletions(-)
create mode 100644 www/examples/canvas/fabric.js
diff --git a/.jshintignore b/.jshintignore
index ae60d4ba0..204535ea7 100644
--- a/.jshintignore
+++ b/.jshintignore
@@ -4,6 +4,7 @@ www/code/codemirror*
www/common/chainpad.js
storage/kad.js
www/common/otaml.js
+www/examples/canvas/fabric.js
server.js
NetFluxWebsocketSrv.js
diff --git a/www/examples/canvas/fabric.js b/www/examples/canvas/fabric.js
new file mode 100644
index 000000000..4c885f95f
--- /dev/null
+++ b/www/examples/canvas/fabric.js
@@ -0,0 +1,26125 @@
+/* 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("");
+
+ 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 dotted stroke in canvas
+ *
+ * @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) {
+ var that = this;
+ return function () { this.get(property); }; //return new Function('return this.get("' + property + '")');
+ })(propName);
+ }
+ if (!proto[setterName]) {
+ proto[setterName] = (function(property) {
+ var that = this;
+ return function () { return this.get(property); }; // 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 String#trim on MDN
+ */
+ 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, '>');
+ }
+
+ /**
+ * 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 Function#bind on MDN
+ * @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 Easing Equations by Robert Penner
+ * @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 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\n',
+ '\t\t\n\t\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(//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(//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\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 https://github.com/mjijackson
+ * @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 https://github.com/mjijackson
+ * @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 = [
+ '\n'
+ ];
+ }
+ else if (this.type === 'radial') {
+ markup = [
+ '\n'
+ ];
+ }
+
+ for (var i = 0; i < this.colorStops.length; i++) {
+ markup.push(
+ '\n'
+ );
+ }
+
+ markup.push((this.type === 'linear' ? '\n' : '\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:
+ *
+ *
+ *
+ *
+ *
+ *
+ * OR
+ *
+ *
+ *
+ *
+ *
+ *
+ * OR
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * OR
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+ 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);
+ }
+ //
element
+ else if (typeof this.source.src === 'string') {
+ source = this.source.src;
+ }
+ //
',
+ '
'
].join('');
out.codeInitialState = [
diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js
index b463b4853..5a3fcba4e 100644
--- a/customize.dist/translations/messages.js
+++ b/customize.dist/translations/messages.js
@@ -377,7 +377,8 @@ define(function () {
'',
'Go ahead, just start typing...',
- '
'
+ '',
+ '
'
].join('');
out.codeInitialState = [
From b542245086bc2fcb8357ad0bcd33b9096b7cce25 Mon Sep 17 00:00:00 2001
From: ansuz
Date: Mon, 20 Mar 2017 18:11:26 +0100
Subject: [PATCH 7/8] fix lint error
---
www/common/toolbar.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/www/common/toolbar.js b/www/common/toolbar.js
index 2c023f8bf..c12ac1a4f 100644
--- a/www/common/toolbar.js
+++ b/www/common/toolbar.js
@@ -343,10 +343,10 @@ define([
id: uid(),
});
var $a = $('', {id: 'newLag'});
- $s1 = $('', {'class': 'bar1'}).appendTo($a);
- $s2 = $('', {'class': 'bar2'}).appendTo($a);
- $s3 = $('', {'class': 'bar3'}).appendTo($a);
- $s4 = $('', {'class': 'bar4'}).appendTo($a);
+ $('', {'class': 'bar1'}).appendTo($a);
+ $('', {'class': 'bar2'}).appendTo($a);
+ $('', {'class': 'bar3'}).appendTo($a);
+ $('', {'class': 'bar4'}).appendTo($a);
return $a[0];
};
From acc094654d68965a1a4f542a81776042d3e36a55 Mon Sep 17 00:00:00 2001
From: ansuz
Date: Tue, 21 Mar 2017 10:18:50 +0100
Subject: [PATCH 8/8] remove funny file
---
q | 478 --------------------------------------------------------------
1 file changed, 478 deletions(-)
delete mode 100644 q
diff --git a/q b/q
deleted file mode 100644
index cbba0eb66..000000000
--- a/q
+++ /dev/null
@@ -1,478 +0,0 @@
-warning: LF will be replaced by CRLF in customize.dist/translations/messages.fr.js.
-The file will have its original line endings in your working directory.
-[1mdiff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js[m
-[1mindex 96e36a4..4ad51bd 100644[m
-[1m--- a/customize.dist/translations/messages.fr.js[m
-[1m+++ b/customize.dist/translations/messages.fr.js[m
-[36m@@ -79,12 +79,14 @@[m [mdefine(function () {[m
- [m
- out.printButton = "Imprimer";[m
- out.printButtonTitle = "Imprimer votre présentation ou l'enregistrer au format PDF";[m
-[31m- out.printOptions = "Options d'impression";[m
-[32m+[m[32m out.printOptions = "Options de mise en page";[m
- out.printSlideNumber = "Afficher le numéro des slides";[m
- out.printDate = "Afficher la date";[m
- out.printTitle = "Afficher le titre du pad";[m
- out.printCSS = "Personnaliser l'apparence (CSS):";[m
- [m
-[32m+[m[32m out.slideOptionsTitle = "Personnaliser la présentation";[m
-[32m+[m
- out.editShare = "Lien d'édition";[m
- out.editShareTitle = "Copier le lien d'édition dans le presse-papiers";[m
- out.editOpen = "Éditer dans un nouvel onglet";[m
-[1mdiff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js[m
-[1mindex b463b48..15540fd 100644[m
-[1m--- a/customize.dist/translations/messages.js[m
-[1m+++ b/customize.dist/translations/messages.js[m
-[36m@@ -81,12 +81,14 @@[m [mdefine(function () {[m
- [m
- out.printButton = "Print";[m
- out.printButtonTitle = "Print your slides or export them as a PDF file";[m
-[31m- out.printOptions = "Print options";[m
-[32m+[m[32m out.printOptions = "Layout options";[m
- out.printSlideNumber = "Display the slide number";[m
- out.printDate = "Display the date";[m
- out.printTitle = "Display the pad title";[m
- out.printCSS = "Custom style rules (CSS):";[m
- [m
-[32m+[m[32m out.slideOptionsTitle = "Customize your slides";[m
-[32m+[m
- out.editShare = "Editing link";[m
- out.editShareTitle = "Copy the editing link to clipboard";[m
- out.editOpen = "Open editing link in a new tab";[m
-[1mdiff --git a/www/slide/main.js b/www/slide/main.js[m
-[1mindex 50f216e..0bc6f5f 100644[m
-[1m--- a/www/slide/main.js[m
-[1m+++ b/www/slide/main.js[m
-[36m@@ -136,7 +136,8 @@[m [mdefine([[m
- [m
- var $modal = $pad.contents().find('#modal');[m
- var $content = $pad.contents().find('#content');[m
-[31m- var $print = $pad.contents().find('#print');[m
-[32m+[m[32m var $print = $pad.contents().find('#print');
var slideOptions = {};[m
-[32m+[m
- $( window ).resize(function() {[m
- // 20vh[m
- // 20 * 16 / 9vw[m
-[36m@@ -149,7 +150,13 @@[m [mdefine([[m
- // $print.css('font-size', (20*9/16)+'vw');[m
- });[m
- [m
-[31m- Slide.setModal($modal, $content, $pad, ifrw, initialState);[m
-[32m+[m[32m Slide.setModal(APP, $modal, $content, $pad, ifrw, slideOptions, initialState);[m
-[32m+[m
-[32m+[m[32m var setStyleState = function (state) {[m
-[32m+[m[32m $pad.contents().find('#print, #content').find('style').each(function (i, el) {[m
-[32m+[m[32m el.disabled = !state;[m
-[32m+[m[32m });[m
-[32m+[m[32m };[m
- [m
- var enterPresentationMode = function (shouldLog) {[m
- Slide.show(true, editor.getValue());[m
-[36m@@ -158,6 +165,7 @@[m [mdefine([[m
- }[m
- };[m
- var leavePresentationMode = function () {[m
-[32m+[m[32m setStyleState(false);[m
- Slide.show(false);[m
- };[m
- [m
-[36m@@ -229,7 +237,8 @@[m [mdefine([[m
- content: textValue,[m
- metadata: {[m
- users: userData,[m
-[31m- defaultTitle: defaultName[m
-[32m+[m[32m defaultTitle: defaultName,[m
-[32m+[m[32m slideOptions: slideOptions[m
- }[m
- };[m
- if (!initializing) {[m
-[36m@@ -370,6 +379,7 @@[m [mdefine([[m
- setTabTitle();[m
- $bar.find('.' + Toolbar.constants.title).find('span.title').text(data);[m
- $bar.find('.' + Toolbar.constants.title).find('input').val(data);[m
-[32m+[m[32m if (slideOptions.title) { Slide.updateOptions(); }[m
- });[m
- };[m
- [m
-[36m@@ -387,6 +397,15 @@[m [mdefine([[m
- }[m
- };[m
- [m
-[32m+[m[32m var updateOptions = function (newOpt) {[m
-[32m+[m[32m if (stringify(newOpt) !== stringify(slideOptions)) {[m
-[32m+[m[32m $.extend(slideOptions, newOpt);[m
-[32m+[m[32m // TODO: manage realtime + cursor in the "options" modal ??[m
-[32m+[m[32m console.log('updating options');[m
-[32m+[m[32m Slide.updateOptions();[m
-[32m+[m[32m }[m
-[32m+[m[32m };[m
-[32m+[m
- var updateDefaultTitle = function (defaultTitle) {[m
- defaultName = defaultTitle;[m
- $bar.find('.' + Toolbar.constants.title).find('input').attr("placeholder", defaultName);[m
-[36m@@ -409,6 +428,7 @@[m [mdefine([[m
- updateTitle(json.metadata.title || defaultName);[m
- titleUpdated = true;[m
- }[m
-[32m+[m[32m updateOptions(json.metadata.slideOptions);[m
- updateColors(json.metadata.color, json.metadata.backColor);[m
- }[m
- if (!titleUpdated) {[m
-[36m@@ -424,12 +444,14 @@[m [mdefine([[m
- };[m
- [m
- var createPrintDialog = function () {[m
-[31m- var printOptions = {[m
-[32m+[m[32m var slideOptionsTmp = {[m
- title: true,[m
- slide: true,[m
-[31m- date: true[m
-[32m+[m[32m date: true,[m
-[32m+[m[32m style: ''[m
- };[m
- [m
-[32m+[m[32m $.extend(slideOptionsTmp, slideOptions);[m[41m
[m
- var $container = $('');[m
- var $container2 = $('
').appendTo($container);[m
- var $div = $('
').appendTo($container2);[m
-[36m@@ -440,21 +462,21 @@[m [mdefine([[m
- $('', {type: 'checkbox', id: 'checkNumber', checked: 'checked'}).on('change', function () {[m
- var c = this.checked;[m
- console.log(c);[m
-[31m- printOptions.slide = c;[m
-[32m+[m[32m slideOptionsTmp.slide = c;[m
- }).appendTo($p).css('width', 'auto');[m
- $('